paulgorman.org/technical

Go Web Programming

(September 2019)

HTTP Requests and Responses

A client makes a request to a server. The server sends a response.

The HTTP protocol specifies that request/response exchange for web traffic. HTTP is a stateless, application-level protocol.

An client HTTP request consists of a few lines of text:

  1. a request line
  2. zero or more request headers
  3. an empty line
  4. optionally, a message body

Like:

GET / HTTP/1.1
Host: example.org
User-Agent: curl/7.64.0
Accept: */*

…

The request line has:

  1. a request method (GET, POST, PUT, etc.)
  2. a URI
  3. the HTTP version used

The methods GET, HEAD, OPTIONS, and TRACE are considered “safe” because they’re not supposed to change anything on the server. The methods POST, PUT, and DELETE change stuff on the server, so they’re not “safe”.

A method is idempotent if calling it two or more times results in the same state as calling it once.

PUT and DELETE are idempotent but not safe. POST is not safe, and may or may not be idempotent, depending on what the server does.

The request headers are colon-separated name-value pairs. RFC 7231 describes a core set of header fields, but arbitrary custom headers abound. Common headers include “Content-Length” to specify the length of the body, “Referrer” to name the previous page that linked to this URI, and the client “User-Agent”.

A server HTTP response has:

  1. a status line
  2. zero or more response headers
  3. an empty line
  4. optionally, a message body
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Sat, 14 Sep 2019 01:57:22 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 7574
Last-Modified: Tue, 30 Apr 2019 21:29:18 GMT
Connection: keep-alive
ETag: "5cc8be2e-1d9b"
Accept-Ranges: bytes

<!DOCTYPE html>
…

The status line of the response gives status code (“200”) and a reason phrase (“OK”).

Various response headers follow the status line, including ones like “Set-Cookie”.

A URI:

<schema name>:<hierarchical parts>[?<query>][#<fragment>]
https://example.com/foo/bar.html?q1=bam&q2=bat#biff

While earlier versions of HTTP are text-based, HTTP/2 is a binary protocol. HTTP/2 is more efficient in terms of overhead, and designed for multiplexed communication between client and server. Go 1.6+ defaults to HTTP/2 for HTTPS connections.

A Web App

A web app:

  1. receives an HTTP request from a client

  2. parses the request and does any work required

  3. generates HTML and returns an HTTP response to the client

  4. A handler processes the client request, and call the template engine. In MVC parlance, the handler acts as both the controller and the model.

  5. A template engine generates HTML for the response.

In a Go web app, the handler name generally corresponds to the hierarchical parts of the URI — sometimes with the first part of the URI hierarchy corresponding to a Go module, and the next part corresponding to a submodule, and the final leafe corresponding to the handler.

When the server receives a request, the multiplexer inspects the request’s URI, and dispatches the request to the matching handler.

func main() {
	mux := http.NewServeMux()
	files := http.FileServer(http.Dir("/public"))
	mux.Handle("/static/", http.StripPrefix("/static/", files))
	mux.HandleFunc("/", index)
	server := &http.Server{
		Addr:    "0.0.0.0:8080",
		Handler: mux,
	}
	server.ListenAndServe()
}
package main

import (
	"html/template"
	"log"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	tmpl, err := template.New("hello").Parse("Hello, {{.}}!")
	if err != nil {
		log.Println(err)
	}
	err = tmpl.Execute(w, r.URL.Path[1:])
	if err != nil {
		log.Println(err)
	}
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}