Go net/http ======================================== (20??, revised 2020) Practical basics ---------------------------------------- A basic server: ``` package main import ( "fmt" "html" "io" "log" "net/http" "os" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Println(r) io.Copy(os.Stdout, r.Body) // ioutil.ReadAll is also useful to read a Request Body. fmt.Printf("\n---\n") fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) }) log.Fatal(http.ListenAndServe(":8000", nil)) } ``` What is an http.ReponseWriter? ``` $ go doc http ResponseWriter ``` ResponseWriter is an interface with three methods, including: ``` Write([]byte) (int, error) ``` So, ResponseWriter is an io.Writer. ``` $ go doc io Writer package io // import "io" type Writer interface { Write(p []byte) (n int, err error) } Writer is the interface that wraps the basic Write method. Write writes len(p) bytes from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p). Write must not modify the slice data, even temporarily. Implementations must not retain p. ``` Above, when we call fmt.Fprintf, we're passing the io.Writer w to Fprintf. Fprintf takes care of making a []byte to feed into the Writer. Basically, we need a function f (like Fprintf) to pour bucket of bytes b into writer w. ``` $ go doc fmt Fprintf package fmt // import "fmt" func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) Fprintf formats according to a format specifier and writes to w. It returns the number of bytes written and any write error encountered. ``` Request.Body is an io.ReadCloser. If we want to print it, we need something that accepts an io.Reader, like io.Copy: ``` func Copy(dst Writer, src Reader) (written int64, err error) ``` What is an http.Request? ``` $ go doc http Request ``` Request is a struct that includes the request Method, URL, Header, Body, etc. A Request can be received by a server or sent by a client. The fields differ slightly between a client Request and a server Request. Request has a number of associated methods (AddCookie, ParseForm, BasicAuth, etc.) including Write. Request, like ResponseWriter, is an io.Writer. A client. This client makes two requests: a basic GET, and a POST that sets a custom header. ``` package main import ( "bytes" "fmt" "io/ioutil" "log" "net/http" ) func main() { url := "http://localhost:8000/" res, err := http.Get(url) if err != nil { log.Fatal(err) } resBody, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { log.Fatal(err) } fmt.Printf("%s", resBody) reqBody := []byte(`{ "id": "001", "thing": { "color": "inky", "size": "cyclopean" } }`) req, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBody)) if err != nil { log.Fatal(err) } req.Header.Set("Content-type", "application/json; charset=utf-8") client := &http.Client{} resp, err := client.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() fmt.Println(resp) } ``` NewRequest wants an io.Reader for it's third argument (the body), so it's not enough to pass a byte slice. We feed the []byte to bytes.NewBuffer, which an io.Reader (and an io.Writer!). This satisfies NewRequest. ``` $ go doc http NewRequest package http // import "net/http" func NewRequest(method, url string, body io.Reader) (*Request, error) NewRequest wraps NewRequestWithContext using the background context. ``` Review of the docs ---------------------------------------- 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 ## Links ## - https://stackoverflow.com/questions/32487032/how-to-compose-a-new-servemux-into-a-larger-struct - https://www.youtube.com/watch?v=Ppw5UluP2R8 - https://scene-si.org/2017/09/27/things-to-know-about-http-in-go/ - https://cryptic.io/go-http/ - https://www.nicolasmerouze.com/middlewares-golang-best-practices-examples/ - https://justinas.org/writing-http-middleware-in-go/ Mon Sep 25 12:59:20 EDT 2017