Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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, ...))
}
like image 218
user937284 Avatar asked Oct 09 '16 17:10

user937284


People also ask

What is a Golang context?

In this blog, we cover Golang contexts with use cases, examples, and in-depth theory. Golang Context is a tool that is used to share request-scoped data, cancellation signals, and timeouts or deadlines across API layers or processes in a program. It is one of the most important tools while working with concurrent programming in Go.

What is JWT authentication in Golang?

Similarly, JWT (JSON Web Tokens) are turning into an increasingly popular way of authenticating users. In this post I shall go over how to create an authentication middleware for Golang that can restrict certain parts of your web app to require authentication.

What is middleware and how does it work?

Middleware is a term used in many different ways within software development, but we’re referring to it as a way to wrap requests and responses in simple abstracted functions which can be applied to some routes easily.

Should I use Gorilla/context or a more standard middleware interface?

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.


3 Answers

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.

like image 178
Daniel Avatar answered Oct 01 '22 08:10

Daniel


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.

like image 27
jabbrwcky Avatar answered Oct 02 '22 08:10

jabbrwcky


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
}
like image 5
Zchary Avatar answered Oct 03 '22 08:10

Zchary