20
votes

I am designing my handlers to return a http.Handler. Here's the design of my handlers:

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

My middleware is designed to accept an http.Handler and then call the handler once the middleware has finished performing its operations. Here's the design of my middleware:

 func Middleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Middleware operations

    next.ServeHTTP(w, r)
  })
}

Considering the design of my middleware and handlers, what is the proper way of passing information from the middleware to the handler? The information that I am trying to pass from my middleware to the handlers is a JSON web token parsed from the request body. If I do not pass the parsed JWT to the handler, then I will need to parse the JWT again in my handlers. Parsing the request body for a JWT in both the middleware and handler seems wasteful. Just in case this information is relevant, I am using the standard net/http library with gorilla mux.

3

3 Answers

20
votes

Since you're already using Gorilla take a look at the context package.

(This is nice if you don't want to change your method signatures.)

import (
    "github.com/gorilla/context"
)

...

func Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Middleware operations
        // Parse body/get token.
        context.Set(r, "token", token)

        next.ServeHTTP(w, r)
    })
}

...

func Handler() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := context.Get(r, "token")
    })
}

Update

The Gorilla context package is now in maintenance mode
per the repo:

Note gorilla/context, having been born well before context.Context existed, does not play well with the shallow copying of the request that http.Request.WithContext (added to net/http Go 1.7 onwards) performs.

Using gorilla/context may lead to memory leaks under those conditions, as the pointers to each http.Request become "islanded" and will not be cleaned up when the response is sent.

You should use the http.Request.Context() feature in Go 1.7.

8
votes

The proper way to pass request scoped data would now be the context package in the standard library.

https://golang.org/pkg/context/

You can access it with request.Context on an http.Request.

7
votes

A first approach, similar to the question, is in codemodus/chain by Daved.

Package chain aids the composition of Handler wrapper chains that carry request-scoped data.

It uses the notion of Context, coupled with a Context handler:

func ctxHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
    // ...

    if s, ok := getMyString(ctx); ok {
        // s = "Send this down the line."
    }

    // ...
}

Another approach: You can have a look at "Custom Handlers and Avoiding Globals in Go Web Applications", by Matt Silverlock (elithrar). (full example here)

The idea is to define ServeHTTP on a type which include the relevant context.

// We've turned our original appHandler into a struct with two fields:
// - A function type similar to our original handler type (but that now takes an *appContext)
// - An embedded field of type *appContext
type appHandler struct {
    *appContext
    h func(*appContext, http.ResponseWriter, *http.Request) (int, error)
}

// Our ServeHTTP method is mostly the same, and also has the ability to
// access our *appContext's fields (templates, loggers, etc.) as well.
func (ah appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // Updated to pass ah.appContext as a parameter to our handler type.
    status, err := ah.h(ah.appContext, w, r)
    if err != nil {
        log.Printf("HTTP %d: %q", status, err)
        switch status {
        case http.StatusNotFound:
            http.NotFound(w, r)
            // And if we wanted a friendlier error page, we can
            // now leverage our context instance - e.g.
            // err := ah.renderTemplate(w, "http_404.tmpl", nil)
        case http.StatusInternalServerError:
            http.Error(w, http.StatusText(status), status)
        default:
            http.Error(w, http.StatusText(status), status)
        }
    }
}

In the appContext struct, you would put any data you want to pass around.