How request context works
Every http.Request
that our middleware and handlers process has a context.Context
object embedded in it, which we can use to store information during the lifetime of the request.
As I’ve already hinted at, in a web application a common use-case for this is to pass information between your pieces of middleware and other handlers.
In our case, we want to use it to check if a user is authenticated once in some middleware, and if they are, then make this information available to all our other middleware and handlers.
Let’s start with some theory and explain the syntax for working with request context. Then, in the next chapter, we’ll get a bit more concrete again and demonstrate how to practically use it in our application.
The request context syntax
The basic code for adding information to a request’s context looks like this:
// Where r is a *http.Request... ctx := r.Context() ctx = context.WithValue(ctx, "isAuthenticated", true) r = r.WithContext(ctx)
Let’s step through this line-by-line.
- First, we use the
r.Context()
method to retrieve the existing context from a request and assign it to thectx
variable. - Then we use the
context.WithValue()
method to create a new copy of the existing context, containing the key"isAuthenticated"
and a value oftrue
. - Then finally we use the
r.WithContext()
method to create a copy of the request containing our new context.
I should also point out that, for clarity, I made that code snippet a bit more verbose than it needs to be. It’s more typical to write it like this:
ctx = context.WithValue(r.Context(), "isAuthenticated", true) r = r.WithContext(ctx)
So that’s how you add data to a request’s context. But what about retrieving it again?
The important thing to explain is that, behind the scenes, request context values are stored with the type any
. And that means that, after retrieving them from the context, you’ll need to assert them to their original type before you use them.
To retrieve a value we need to use the r.Context().Value()
method, like so:
isAuthenticated, ok := r.Context().Value("isAuthenticated").(bool) if !ok { return errors.New("could not convert value to bool") }
Avoiding key collisions
In the code samples above, I’ve used the string "isAuthenticated"
as the key for storing and retrieving the data from a request’s context. But this isn’t recommended because there’s a risk that other third-party packages used by your application will also want to store data using the key "isAuthenticated"
— and that would cause a naming collision.
To avoid this, it’s good practice to create your own custom type which you can use for your context keys. Extending our sample code, it’s much better to do something like this:
// Declare a custom "contextKey" type for your context keys. type contextKey string // Create a constant with the type contextKey that we can use. const isAuthenticatedContextKey = contextKey("isAuthenticated") ... // Set the value in the request context, using our isAuthenticatedContextKey // constant as the key. ctx := r.Context() ctx = context.WithValue(ctx, isAuthenticatedContextKey, true) r = r.WithContext(ctx) ... // Retrieve the value from the request context using our constant as the key. isAuthenticated, ok := r.Context().Value(isAuthenticatedContextKey).(bool) if !ok { return errors.New("could not convert value to bool") }