thoughts from places


HTTP handlers in Go

Some notes on the net/http package in Go.

N.B. All code samples are also on Github

Any type that satisfies the http.Handler interface can be used to respond to HTTP requests. In order to satisfy this interface a type needs to have a method called ServeHTTP that accepts an http.ResponseWriter and a pointer to an http.Request (N.B. Types that end in er are an interface. Although this is just a convention so packages outside the standard library might not always follow this.)

The http.ListenAndServe function takes an object whose type satisfies the http.Handler interface. This function is basically an infinite loop that prepares the arguments to be passed to ServeHTTP, and then calls it when requests come in. It also does some post-processing like setting the Content-Length header. (how many bytes the body is)

Here is a basic example of a type that implements the http.Handler interface:

package main

import "net/http"

// Empty struct type
type foo struct{}

func (f foo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("foo"))
}

func main() {
  http.ListenAndServe(":8000", foo{})
}

The output of this program is:

$ curl -i localhost:8000
HTTP/1.1 200 OK
Date: Mon, 30 Mar 2015 11:35:10 GMT
Content-Length: 3
Content-Type: text/plain; charset=utf-8

foo

Multiplexers

At the moment this ServeHTTP method will handle any request that comes in regardless of the path or HTTP method used.

In order to use more than one ServeHTTP method we need to use the multiplexer design pattern.

A multiplexer (mux for short) is something that takes an input and dispatches it to the relevant handler. For example the postal system is a multiplexer. It takes an input in the form of a letter with an address on it and delivers it to the address.

Go has a basic multiplexer built into the net/http package. It will associate the path of a URL to a handler. Gorilla mux is a much more powerful multiplexer that can match by path, HTTP verb and lots of other properties of a request.

Below is a simple example of what a multiplexer does. It's basically just a key value lookup.

// string is the URL path and http.Handler is any type that has a ServeHTTP method.
type multiplexer map[string]http.Handler

func (m multiplexer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  if handler, ok := m[r.RequestURI]; ok {
    handler.ServeHTTP(w, r)
  } else {
    w.WriteHeader(http.StatusNotFound)
  }
}

var mux = multiplexer{
  "/":         foo{},
  "/about/":   page{"about"},
  "/contact/": page{"contact"},
}

func main() {
  http.ListenAndServe(":8000", mux)
}

View the full code on Gitub

Use a function as a handler

Use http.HandlerFunc to wrap a function so that it implements the http.Handler interface. This saves you from having to create a custom type. An example of this is shown below in the middleware section.

Middleware

A function that takes a handler and returns a handler can be used to wrap other handlers and thus be used as middleware. The middleware example below redirects to the same URL with a trailing slash if it doesn't have one. Note the h.ServeHTTP(w, r) line. This calls the ServeHTTP method from the multiplexer.

func appendTrailingSlash(h http.Handler) http.Handler {

  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

    if !strings.HasSuffix(r.URL.Path, "/") {
      http.Redirect(w, r, r.URL.Path+"/", http.StatusFound)
    } else {
      h.ServeHTTP(w, r)
    }
  })
}

func main() {
  // Wrap mux with middleware
  http.ListenAndServe(":8000", appendTrailingSlash(mux))
}

View the full code on Gitub

Testing handlers

Use httptest.NewServer to create a test server and then simply send requests to it and make assertions on the response.

I recommend using an assertion library like testify as using if statements to make assertions is quite verbose.

Example:

package main

import (
  "net/http"
  "net/http/httptest"
  "testing"
)

func TestAppendTrailingSlashMiddleware(t *testing.T) {
  server := httptest.NewServer(handlers)

  resp, _ := http.Get(server.URL + "/about")

  if resp.Request.URL.Path != "/about/" {
    t.Error("Didn't redirect to URL with trailing slash.")
  }
}

View the full code on Gitub

You don't need to start an HTTP server to test handlers. You can simulate what the ListenAndServe function does by creating a request and response manually and passing them into your handler. To do this you need to use http.NewRequest and httptest.NewRecorder.

Hope you found this useful. If you have any thoughts about this and want to contact me send an email to stdin@neillyons.io


Neil Lyons

Freelance Web Developer based in the UK.