Share Values Between Middlewares

We saw in the last part that with a simple function like func (http.Handler) http.Handler we could create middlewares and share pieces of code between our routes and even our different apps. We took two examples: logging and panic recovery. But most middlewares will be more complex than that. And for some cases we will need to pass values to the next middlewares.

For example with an authentication middleware that would check if a user exists in the database and retrieve it. We need this information down the middleware stack and in the handler processing the request. This article will show you the different value sharing solutions also known as contexts.

This is the third article in the five-part series "Build Your Own Web Framework in Go":

How developers are handling it

In Ruby, most developers and frameworks are using Rack. Each middleware gets an env hash map containing request information and in which you can set anything.

In Node.js, there is Connect.js. Each middleware has two objects, one for the request and one for the response. Objects in Javascript can act as hash maps so developers are using the request object to store anything they want.

For a statically-typed language, it's not that different. I used Netty in Java a few times and it uses maps too. Each handler will have access to a context object containing an attribute map.

But how Go packages/frameworks are handling contexts? Here's a few examples.

Gorilla

gorilla/context defines a map of requests each containing a map[interface{}]interface{}] used to store information during the request processing. It uses a mutex to keep it threadsafe. Here's how it looks like:

var (
  mutex sync.RWMutex
  data  = make(map[*http.Request]map[interface{}]interface{})
)

And here's how to get and set a value:

func myHandler(w http.ResponseWriter, r *http.Request) {
  context.Set(r, "foo", "bar")
}

func myOtherHandler(w http.ResponseWriter, r *http.Request) {
  val := context.Get(r, "foo").(string)
}

The map is not cleared automatically. Each request will add an entry and the map will grow indefinitely. So Gorilla also has a handler to clear the map from the current request we must use before the end of the processing (it is automatic when used in conjonction to gorilla/mux).

Pros:

  • Handlers and middlewares can stay as they are, it's just a call inside them.

Cons:

  • I have made some micro-benchmarks and it's the slowest solution. The performance hit is minor in a real-world application, though.
  • We are using a mutex here. Some will say it's not idiomatic, some say the opposite. The Go documentation is not against mutexes.
  • Since we are using interface{} we must assert the value is of type x.

Goji

Goji uses a struct containing URL params and has a map[string]interface{} named Env. it is passed to each middleware without the use of a mutex.

func myMiddleware(c *web.C, h http.Handler) http.Handler {
  fn := func (w http.ResponseWriter, r *http.Request) {
    c.Env["name"] = "world"
    h.ServeHTTP(w, r)
  }

  return http.HandlerFunc(fn)
}

func hello(c web.C, w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %s!", c.Env["name"].(string))
}

Pros:

  • No mutex.

Cons:

  • Not compatible with our middleware system. It's not completely different though.
  • Type assertions needed.

gocraft/web

gocraft/web takes any struct we define in our code we want and uses it as a context. No map, no type assertion. It's great for performance but it's the least compatible way to do it. You'll have to create a new context struct for each app and you'll not be able to reuse your middlewares.

Pros:

  • No mutex.
  • No type assertions.

Cons:

  • Not compatible with our middleware system.
  • Code wrote for this framework can't be reuse without lots of changes.

go.net/context

This is the package used internally at Google to handle contexts. You can read their blog post about it. It is a really nice solution and they really want to unify all the contexts packages available. The problem is it doesn't play nice with our middleware system.

Global view

Here's a table representing the different solutions:

package mutex map struct
go.net/context n y* n
goji n y n
gin n y n
martini n y n
gorilla y y n
tigertonic y n y
gocraft/web n n y

* go.net/context does not use a map but it's very similar.

You see that most contexts are maps or something similar. All the solutions have advantages and problems. Some might say type assertion is to be avoided as much as possible, but the code overhead in using structs for contexts is similar (though performances are better without type assertion).

Contexts with maps and mutexes are 10% less performant than those without mutexes. struct contexts are the fastest. But in real-life scenarios with, let's say, 10ms to process each request, it is less than 1%, not a big change. You might opt-in for performances if it really matters to you.

I think maps are the most flexible solution. The two types of context I've shown in the comparison using maps are gorilla/context and Goji. gorilla/context is a solution given by one of the Go creators. And Goji contexts are the easiest to implement when starting from scratch. For this article, we will use gorilla/context.

Depending on if you want to share your middleware with the rest of the world, you may adopt a more standard interface. For example, nosurf uses its own context based on the same concept as gorilla/context and has a standard middleware interface. You can use it in almost any project built with any framework without issues. That's why the gorilla/context system is better, it's easier to reuse middlewares.

Integrate contexts into our own framework

Since we're using gorilla/context, we almost don't need to change anything to our existing code. When presenting gorilla/context, I have said that the map of requests storing contexts is not cleared automatically so for each new request there will be a new entry in the map and it will grow endlessly. the package has a ClearHandler handler to fix the issue.

func main() {
  commonHandlers := alice.New(context.ClearHandler, loggingHandler, recoverHandler)
  http.Handle("/about", commonHandlers.ThenFunc(aboutHandler))
  http.Handle("/", commonHandlers.ThenFunc(indexHandler))
  http.ListenAndServe(":8080", nil)
}

Now we can add a middleware storing something we need in our main handler. Let's create an authentication middleware. This middleware will get the Authorization header containing the token and will search for a user. If the authentication fails, the middleware will not execute the next midlewares and will return an error. If it succeeds, it will store the user in the context and execute the next middleware.

func authHandler(next http.Handler) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    authToken := r.Header().Get("Authorization")
    user, err := getUser(authToken)

    if err != nil {
      http.Error(w, http.StatusText(401), 401)
      return
    }

    context.Set(r, "user", user)
    next.ServeHTTP(w, r)
  }

  return http.HandlerFunc(fn)
}

func adminHandler(w http.ResponseWriter, r *http.Requests) {
  user := context.Get(r, "user")
  json.NewEncoder(w).Encode(user)
}

func main() {
  commonHandlers := alice.New(context.ClearHandler, loggingHandler, recoverHandler)
  http.Handle("/admin", commonHandlers.Append(authHandler).ThenFunc(adminHandler))
  http.Handle("/about", commonHandlers.ThenFunc(aboutHandler))
  http.Handle("/", commonHandlers.ThenFunc(indexHandler))
  http.ListenAndServe(":8080", nil)
}

getUser is the function finding the user. Let's say the user is a map[string]interface{} to simplify our example. In our admin handler we get the user we stored in the authentication middleware, we encode it and write it in the response writer.

Application-level values

There's still a problem with this approach. getUser will certainly want an access to a database. Our context is bound to a request, storing a DB connection reference in the context of each request isn't great. It would be better to store this reference somewhere and all the requests will access it. We could use global/package level variables as such:

var dbConn *sql.DB

func main() {
  dbConn := sql.Open("postgres", "...")
}

We could access dbConn in the entire application which would solve the problem. *sql.DB is a pool so it's safe for concurrent use. But in my experience global variables is really bad for maintenance. You don't control what can modify them and it is difficult to track their state. Refactoring can become a real pain, even with perfect testing.

Another solution would be to have a struct with all the values we need to use and create handlers and middlewares as methods of this struct. To solve our problem, it would be a struct with a db field containg our connection pool. This struct would have methods for our authHandler and adminHandler. The code from above will only sligthly change to use db for getUser function.

type appContext struct {
  db *sql.DB
}

func (c *appContext) authHandler(next http.Handler) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    authToken := r.Header.Get("Authorization")
    user, err := getUser(c.db, authToken)

    if err != nil {
      http.Error(w, http.StatusText(401), 401)
      return
    }

    context.Set(r, "user", user)
    next.ServeHTTP(w, r)
  }

  return http.HandlerFunc(fn)
}

func (c *appContext) adminHandler(w http.ResponseWriter, r *http.Request) {
  user := context.Get(r, "user")
  // Maybe other operations on the database
  json.NewEncoder(w).Encode(user)
}

func main() {
  db := sql.Open("postgres", "...")
  appC := appContext{db}
  commonHandlers := alice.New(context.ClearHandler, loggingHandler, recoverHandler)
  http.Handle("/admin", commonHandlers.Append(appC.authHandler).ThenFunc(appC.adminHandler))
  http.Handle("/about", commonHandlers.ThenFunc(aboutHandler))
  http.Handle("/", commonHandlers.ThenFunc(indexHandler))
  http.ListenAndServe(":8080", nil)
}

It is integrated very nicely with our middleware system and the code has not really changed. This approach uses a struct, like gocraft/web, but only for application-level values and is not tied to any specific code making it reusable across apps and even other frameworks.

We could alternatively make getUser a method of appContext to be cleaner. Or wrap *sql.DB in a custom struct and add getUser as a method of this custom struct so we could call it simply with c.db.getUser(token).

Wrap up

We now have a way to pass our authenticated user and other stored values by middlewares into the main handler. It doesn't require much change to our application and we can mix both standard middlewares and middleware with contexts without much more code. The only missing part is now the router, subject of the next part. But with all we made until now, integrate a router to our framework will be very easy.

Resources

There have been a number of articles on this subject that I recommend you to read: