'How to pass context in golang request to middleware

I am trying to understand how the context introduced in Golang 1.7 works and what would be the appropriate way to pass it to middleware and to a HandlerFunc. Should the context get initialized in the main function and passed to the checkAuth function then? And how to pass it to Hanlder and the ServeHTTP function? I read Go concurrency patterns and How to use Context but I struggle to adapt those patterns to my code.

func checkAuth(authToken string) util.Middleware {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if r.Header.Get("Auth") != authToken {
                util.SendError(w, "...", http.StatusForbidden, false)
                return
            }
            h.ServeHTTP(w, r)
        })
    }
}

// Handler is a struct
type Handler struct {
    ...
    ...
}

// ServeHTTP is the handler response to an HTTP request
func (h *HandlerW) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    decoder := json.NewDecoder(r.Body)

    // decode request / context and get params
    var p params
    err := decoder.Decode(&p)
    if err != nil {
       ...
        return
    }

    // perform GET request and pass context
    ...


}


func main() {
    router := mux.NewRouter()

    // How to pass context to authCheck?
    authToken, ok := getAuthToken()
    if !ok {
        panic("...")
    }
    authCheck := checkAuth(authToken)

    // initialize middleware handlers
    h := Handler{
       ...
   } 

   // chain middleware handlers and pass context
   router.Handle("/hello", util.UseMiddleware(authCheck, Handler, ...))
}


Solution 1:[1]

If you look at the first example at that Go Concurrency Patterns blog post, you'll notice that they're "deriving" their contexts from the Background context. That, combined with the Context and WithContext methods on your Request object, gives you what you need.

I just figured this out (and it wasn't my first run at reading those docs); when you "derive" a context, you're making another one with one change. I was already wrapping the http.Handler (actually using httprouter.Handle). The cool thing about Request.Context is that it never returns nil; if no other context has been created, it returns the background context.

To specify a timeout, within your handler (just above your "// perform GET request" comment), you can do something like:

ctx, cancel := context.WithTimeout(r.Context(), time.Duration(60*time.Second))
defer cancel()
r = r.WithContext(ctx)

The first line creates the context and gives you the cancel hook, which you defer; any derived contexts (AKA the ones to which you add your variables) will be cancelled when this deferred call (line 2) is executed, once the request has been served. Finally, line 3 replaces the request, which now contains your updated context.

In your authorization checker, once you have established that the user is valid, you can add the user's information to the context before calling ServeHTTP. Keys for the context can't use built-in types, but you can create a new type that's simply an alias to the built-in type. It's a good idea to define constants for your key values as well. An example:

type ContextKey string

const ContextUserKey ContextKey = "user"

// Then, just above your call to ServeHTTP...

ctx := context.WithValue(r.Context(), ContextUserKey, "theuser")
h.ServeHTTP(w, r.WithContext(ctx))

This passes the now-twice-derived context (that now has the timeout and the user ID) to the handler.

Finally, the last piece of the puzzle - how to get the user ID from within the handler. This is the most straightforward part; we simply use the Value method on the value returned from the request's Context method. The type is interface{}, so you'll need a type assertion if you want to treat it as a string (as this example does).

user := r.Context().Value(ContextUserKey)
doSomethingForThisUser(user.(string))

You're not limited to one change per method; as long as you keep deriving the same context, it'll all get cleaned up once the request has been served, when the initially derived context (the one, in this example, where we specified the timeout) is cancelled when the deferred cancel() call fires.

Solution 2:[2]

You can improve Daniels solution by adding a funtion to retrieve the value from the context in a typesafe manner:

type ContextKey string

const ContextUserKey ContextKey = "user"

func UserFromContext(ctx context.Context) string {
    return ctx.Value(ContextUserKey).(string)
}

// Then, just above your call to ServeHTTP... 

ctx := context.WithValue(r.Context(), userKey, "theuser")
h.ServeHTTP(w, r.WithContext(ctx))

The handler does not have to cast the type and it even does not need to know the context key.

Solution 3:[3]

Just ran into this issue for one of my projects. I was able to solve it by using the BaseContext field inside the server struct. Initializing the BaseContext with a custom context enables the server to have this custom context for all incoming requests. Below is some sample code:

import "net/http"

type ServerHandler struct {
}

server := &http.Server{Addr: localhost:9001,
    Handler:     ServerHandler,
    BaseContext: func(_ net.Listener) context.Context { return <custom context> }}


server.ListenAndServe()

By initializing the BaseContext with your custom context, you can pass your custom context to the ServeHTTP method. All incoming requests would have the custom context.

func (handler ServerHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
    //Access your context here as request.Context()
}

Find the definition of the struct here: https://cs.opensource.google/go/go/+/refs/tags/go1.17:src/net/http/server.go;l=2611

Solution 4:[4]

If anyone trying to add context value in the handler function and handles it in the middleware.

net/http provides Request.Clone and Request.WithContext methods to modify the request context but both return with a new request pointer which can not change the original *http.Request

instead of doing so, you can try this:

func someHandler(w http.ResponseWriter, r *http.Request){
    ctx := r.Context()
    req := r.WithContext(context.WithValue(ctx, "key", "val"))
    *r = *req
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Daniel
Solution 2
Solution 3 codeara
Solution 4