DEV Community

Cover image for Dealing with Context in Go for API's
Gayan Hewa
Gayan Hewa

Posted on

Dealing with Context in Go for API's

The Go context is a really interesting topic. Context provides a lot of benefits when it comes to concurrent programming. The context package was written by Sameer Ajmani and was merged upstream into Go from version 1.7 and now is a part of the standard library. Before this, there were alternative ways of dealing with context as well.

In my case, I am looking into a specific scenario in this article. Whenever we build public/private API's we would always have a bunch of middleware dealing with stuff like CORS, Authentication headers, etc The simplest way for most applications is to use a session driven layer to share this information with other parts of the application. But usually, the stateless application is request scoped, and most of the information that is used for a particular request would "ideally" die down once the request responds.

Gorilla Context Package

The Gorilla Context Package is an alternative package that lets you deal with the request-scoped context, but this doesn't play nice with the native context integration from Go 1.7 up.

You can dive into the doc's here https://www.gorillatoolkit.org/pkg/context below is an extract from the examples in the documentation.

type key int

const mykey key = 0

// GetMyKey returns a value for this package from the request values.
func GetMyKey(r *http.Request) SomeType {
    if rv := context.Get(r, mykey); rv != nil {
        return rv.(SomeType)
    }
    return nil
}

// SetMyKey sets a value for this package in the request values.
func SetMyKey(r *http.Request, val SomeType) {
    context.Set(r, mykey, val)
}

Native Context Package

The original article published by the Author can be found here https://blog.golang.org/context this explains the inner workings of the context package.

For our scenario, we are interested in the https://golang.org/pkg/net/http/#Request.WithContext method since we are focused on using the context in conjunction with middleware.

func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        userID := 1234
        teamID := 6789

        ctx = context.WithValue(r.Context(), authContext, &AuthenticatedUser{
            UserID: userID,
            TeamID: teamID,
        })
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

So what does this do, it simply gets the context from the current request r.Context() and update the context and reassign it to ctx and then pass it through the chain next.ServeHTTP(w, r.WithContext(ctx))

With this, trough out the lifecycle of the request we will have access to the userID, teamID values. Allowing us to do all sorts of fun stuff based on these values.

And you can easily access the values by calling r.Context().Value(authContext)

In the above example, authContext is simply a type of its own.

type AuthUserCtx string
var authContext AuthUserCtx = AuthUserCtx("authContext")

Dont's

William Kennedy has a great article on the semantics of using the context package. The key takeaways for me for my use case was the following. Do check out the article to get a good idea about the rest of the scenarios https://www.ardanlabs.com/blog/2019/09/context-package-semantics-in-go.html

  • Avoid using the context to pass optional parameters to the handlers.
  • Avoid embedding it into Structs, context should ideally be passed into whoever needs to access it.

Feel free to share any other use case or interesting problems you have run into while using Go context.

Top comments (0)