Passing Parameters in Golang HTTP Context
When developing HTTP APIs, we may have to process the same request-specific data throughout middlewares. Since it’s a quite common pattern, I decide to figure it out and share how I solve it.
Example
Here we are using Gin, a web framework written in golang featuring performance and good productivity.
Suppose we have an api endpoint /api/hello
handled by HelloHandleFunc
. For each incoming request, we want to authenticate its JWT (in Authentication
header) and pass the decoded user ID to HelloHandleFunc
. This website clearly illustrates what JWT is.
The main idea is to make use of context.Context
in http.Request
. The context of each request controls the entire request life cycle. Thus, we can set request-specific data (key-value pairs) in the context using context.WithValue()
and retrives it using Context.Value()
.
The following code shows the implementation of AddUserID()
middleware, which summarizes the above procedure:
func extractToken(r *http.Request) string {
bearToken := r.Header.Get(conf.JWTAuthHeader)
strArr := strings.Split(bearToken, " ")
if len(strArr) == 2 {
return strArr[1]
}
return ""
}
func AddUserID() gin.HandlerFunc {
return func(c *gin.Context) {
accessToken := extractToken(c.Request)
if accessToken == "" {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
userID := DecodeJWT(accessToken)
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), "user_id", userID))
c.Next()
}
}
Retrieve user_id
from the context in HelloHandleFunc
:
func HelloHandleFunc(c *gin.Context) {
customerID, ok := c.Request.Context().Value("user_id").(string)
if !ok {
c.Abort()
return
}
c.String(http.StatusOK, fmt.Printf("hi, %s!", customerID))
return
}
Register the route with AddUserID()
middleware enabled:
router := gin.New()
group := router.Group("/api/hello")
group.Use(AddUserID())
group.GET("/hello", HelloHandleFunc)
Start the server:
router.Run(":8080")