paulgorman.org/technical

Go net/http

The following are my notes on https://golang.org/pkg/net/http/, comprised largely of direct (if condensed) quotations.

func ListenAndServe(addr string, handler Handler) error

ListenAndServe starts an HTTP server with a given address and handler. The handler is usually nil, which means to use DefaultServeMux. Handle and HandleFunc add handlers to DefaultServeMux:

http.Handle("/foo", fooHandler)

http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

log.Fatal(http.ListenAndServe(":8080", nil))

What is a Handler? A Handler is an interface, so a handler implements a ServeHTTP function that accepts a ResponseWriter and a pointer to a Request. https://golang.org/pkg/net/http/#Handler A Handler responds to an HTTP request. Except for reading the body, handlers should not modify the provided Request. Cautious handlers should read the Request.Body first, and then reply. Note that the signature the Handler interface demands only a ServeHTTP function (which both ServeMux and HandlerFunc implement).

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

What is DefaultServeMux? A ServeMux struct simply maps patterns to handlers. ServeMux is itself a Handler, since it implements the Handler interface with a ServeHTTP function. It’s a Handler that chooses other, more specialized Handlers based on the URL pattern. DefaultServeMux is the ServerMux struct created by default in ListenAndServe(). https://golang.org/pkg/net/http/#ServeMux https://golang.org/src/net/http/server.go#L2066 ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.

type ServeMux struct {
	// contains filtered or unexported fields
}

What is ServeHTTP? ServeHTTP is a function that fulfills the Handler interface requirements. https://golang.org/src/net/http/server.go Generally, ServeHTTP writes reply headers and data to the ResponseWriter and then returns. Both ServeMux and HandlerFunc implement a ServeHTTP function:

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

So, generally we’ll use ListenAndServe with the nil as its second argument, thereby accepting DefaultServeMux as our master Handler. But sometimes we might not want to match simple patterns with other handlers. Perhaps we don’t want to do pattern matching for various Handlers, and only ever do one thing with one Handler. Perhaps we want pattern matching to handlers using fancy regular expressions. In such cases, we provide ListenAndServe with a custom Handler as its second argument.

// This example uses a (trivial) custom handler rather than DefaultServeMux.
package main

import (
	"fmt"
	"log"
	"net/http"
)

type FmtHandler int

func (h FmtHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	msg := "Hark, a request!"
	fmt.Fprintln(w, msg)
	log.Println(msg, r.URL)
}

func main() {
	var h FmtHandler
	log.Fatal(http.ListenAndServe(":8000", h))
}

What’s a ResponseWriter? https://golang.org/pkg/net/http/#ResponseWriter A ResponseWriter interface is used by an HTTP handler to construct an HTTP response. A ResponseWriter may not be used after the Handler.ServeHTTP method has returned.

type ResponseWriter interface {
	// Header returns the header map that will be sent by WriteHeader.
	Header() Header
	// Write writes the data to the connection as part of an HTTP reply.
	Write([]byte) (int, error)
	// WriteHeader sends an HTTP response header with status code.
	WriteHeader(int)
}

What’s a Request? https://golang.org/pkg/net/http/#Request A Request represents an HTTP request received by a server or to be sent by a client.

type Request struct {
	// Method specifies the HTTP method (GET, POST, PUT, etc.).
	Method string
	// URL specifies either the URI being requested (for server requests) or the URL to access (for client requests).
	URL *url.URL
	// The protocol version for incoming server requests.
	Proto      string // "HTTP/1.0"
	ProtoMajor int    // 1
	ProtoMinor int    // 0
	// Header contains the request header fields either received by the server or to be sent by the client.
	Header Header
	// Body is the request's body.
	Body io.ReadCloser
	// For server requests it is unused. GetBody defines an optional func to return a new copy of
	GetBody func() (io.ReadCloser, error)
	// ContentLength records the length of the associated content.
	ContentLength int64
	// TransferEncoding can usually be ignored. It lists the transfer encodings from outermost to innermost.
	TransferEncoding []string
	// Whether to close the connection after replying to this request. For server requests, the HTTP server handles this automatically.
	Close bool
	// For server requests Host specifies the host on which the URL is sought.
	Host string
	// Form contains the parsed form data, including both the URL
	// field's query parameters and the POST or PUT form data.
	// This field is only available after ParseForm is called.
	Form url.Values
	// PostForm contains the parsed form data from POST, PATCH, or PUT body parameters after ParseForm is called.
	PostForm url.Values
	// MultipartForm is the parsed multipart form, including file uploads, after ParseMultipartForm is called
	MultipartForm *multipart.Form
	// Trailer specifies additional headers that are sent after the request body.
	Trailer Header
	// RemoteAddr allows HTTP servers and other software to record the network address that sent the request.
	RemoteAddr string
	// RequestURI is the unmodified Request-URI sent by the client
	RequestURI string
	// TLS allows HTTP servers to record info about the TLS connection on which the request was received.
	TLS *tls.ConnectionState
	// Cancel is an optional channel whose closure indicates that the client request should be regarded as canceled.
	Cancel <-chan struct{}
	// Response is the redirect response which caused this request to be created. Only populated during client redirects.
	Response *Response
}

Interesting Things in net/http

Be aware of http.Error, and note the existence of the http.Status* constants.

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	t, err := template.ParseFiles(tmpl + ".html")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	err = t.Execute(w, p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Jump to a different handler using http.Redirect.

func saveHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/save/"):]
	body := r.FormValue("body")
	p := &Page{Title: title, Body: []byte(body)}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

net/http/util

https://golang.org/pkg/net/http/httputil/ httputil.DumpRequest() is useful for debugging. It returns the given request in its HTTP/1.x wire representation.

Package httputil also offers ReverseProxy, a HTTP Handler that sends an incoming request to another server, and proxies the response back to the client.

net/http/httptest

https://golang.org/pkg/net/http/httptest/ Package httptest provides utilities for HTTP testing.

Middleware and Chaining Handlers

Imagine we want each handler to do some preliminary logging. Without middleware, we must duplicate the logging logic in each handler. With middleware, we chain handlers — a middleware handler takes a handler as an argument and returns another handler. The net/http functions http.StripPrefix and http.TimeoutHandler are built-in example of middleware. A middleware function may look as simple as:

type SaveMessage struct {
	DbFile string
	After  http.Handler
}

func (h *SaveMessage) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	doStuf()
	h.After.ServeHTTP(w, r)
}

func main() {
	http.Handle("/save", &SaveMessage{*dbFile, &ListMessages{*dbFile}})
	log.Fatal(http.ListenAndServe(*addr+":"+*port, nil))
}

Alice is a minimal middleware package that may be worth using. https://github.com/justinas/alice

Mon Sep 25 12:59:20 EDT 2017