Aaron’s Desk Chair Adventures

Code. Engineering Management. Fun.

Go Handlers and State

Today I’d like to get a bit technical about things I’ve been learning and working on in the Go language

Sometimes it’s helpful to pass additional state into a web handler function. For all the examples below, lets assume you want to have a toggle for authentication that is checked once within the handler function. These functions have a specific signature and make it difficult to both add parameters and remain compatible with other libraries. There are many ways to accomplish this, including middleware with some combination of middleware and context libraries. This has the advantage of allowing each request to modify the stored context value without interfering with other handlers. It’s a bit complicated if you happen to forget to turn on the middleware and requires a diligent programmer to not hit panics inside the handler by implementing functions like DoAuth below.

type key int

const AuthKey key = 0

func UseAuth(w http.ResponseWriter, r *http.Request, next http.HandlerFunc){
    context.Set(r, AuthKey, true)
    next(w, r)
    context.Clear(r)
}

func DoAuth(r *http.Request)(bool, error){
    // in case middle-ware isn't turned on...
    if doAuth, ok := context.Get(r, AuthKey); ok{
        return doAuth, nil
    }
    return false, errors.New("middleware off")
}

func AllTheInfo(w http.ResponseWriter, r *http.Request){
    if doAuth, err := DoAuth(r); err == nil && doAuth{
        // do something here
    }
}

func main(){
    n := negroni.New()
    n.Use(UseAuth)

    mux = http.NewServeMux()
    mux.HandleFunc("/", AllTheInfo)

    n.UseHandler(mux)
    n.Run(":8000")
}

A simpler, but potentially less flexible approach is to use controllers. It’s important to remember and keep in mind that controller variables should not be modified inside the handler as it will modify the controller for all other handlers that make use of it. For example, it may seem tempting to set some state based on the current request, but this is not recommended out of the box. Some examples of packages that get around this limitation by creating a controller each request include gocraft/web and this controller library.

type Controller struct{
    DoAuth bool
}

func (c *Controller) AllTheInfo(w http.ResponseWriter, r *http.Request){
    if c.DoAuth{
        // do something here
    }
}

func main(){
    c := &Controller{DoAuth: true}
    http.HandleFunc("/", c.AllTheInfo)
    http.ListenAndServe(":8000", nil)
}

Finally, a very light-weight approach I like to make use of is closures. Closures allow the state to be modified from within the handler and are also granular enough that you can tweak them for every route without initializing additional controllers or wrapping every route with middleware incurring context look-up and reflection overhead.

func CheckAllTheInfo(DoAuth bool) func(w ResponseWriter, r *Request){
    return func(w ResponseWriter, r *Request){
        if DoAuth{
            // do something here
        }
    }
}

func main(){
    http.HandleFunc("/", CheckAllTheInfo(true))
    http.ListenAndServe(":8000", nil)
}

Overall, there’s a lot of ways to accomplish injecting state for web handler functions in Go. There are a number of other middleware packages besides negroni and there are even context objects build into the standard library now. I use combinations of all of these as one doesn’t scratch all the itches I have. You can also combine these, for example you could attach a context object to a controller that is safe to modify or wrap a controller method in a closure.