Gin middleware examples

I've been playing around with Gin web framework in Go for a while for small side projects and its been amazing so far. Gin attracted me by its simplicity and compatibility with a default net/http library and somewhat similarity with Sinatra, which is a minimalistic web framework for Ruby. So far i've written a few open source projects powered by Gin:

While most of those projects are pretty simple under the hood, i started to explore more on how to bring some of my experience with Sinatra into Go. In particular i was interested how to write middleware handlers. You can check Gin's documentation, there's a few small samples.

Here's the baseline app:

package main

import(
  "github.com/gin-gonic/gin"
)

func GetDummyEndpoint(c *gin.Context) {
  resp := map[string]string{"hello":"world"}
  c.JSON(200, resp)
}

func main() {
  api := gin.Default()
  api.GET("/dummy", GetDummyEndpoint)
  api.Run(":5000")
}

Now, lets add some middleware:

func DummyMiddleware(c *gin.Context) {
  fmt.Println("Im a dummy!")

  // Pass on to the next-in-chain
  c.Next()
}

func main() {
  // Insert this middleware definition before any routes
  api.Use(DummyMiddleware)
  // ... more code
}

In example above there's a call c.Next(). It means that after our middleware is done executing we can pass on request handler to the next func in the chain. As you can see, middleware functions are no different from regular endpoint functions as they only take one argument *gin.Context. However, there's also another way of defining middleware functions, like this one:

func DummyMiddleware() gin.HandlerFunc {
  // Do some initialization logic here
  // Foo()
  return func(c *gin.Context) {
    c.Next()
  }
}

func main() {
  // ...
  api.Use(DummyMiddleware())
  // ...
}

The difference between those two ways of defining middleware functions is that you can do some initialization logic in later example. Say you need to fetch some data from a third-party service, but you cannot do that on per-request basis. When middleware gets loaded into request chain, whatever you define before the return statement (Foo() in example) will be executed only once. This could be useful if you want to have condition checking, like return one middleware function if one header is present, or another one if its not. Lets move onto examples!

API authentication middleware

If you're building an API with Gin, you will probably want to add some sort of authentication mechanism into your application. Easiest solution is to check if client has provided an additional url parameter, like api_token. Then it should be validated on each request before anything else.

func respondWithError(c *gin.Context, code int, message interface{}) {
  c.AbortWithStatusJSON(code, gin.H{"error": message})
}

func TokenAuthMiddleware() gin.HandlerFunc {
  requiredToken := os.Getenv("API_TOKEN")

  // We want to make sure the token is set, bail if not
  if requiredToken == "" {
    log.Fatal("Please set API_TOKEN environment variable")
  }

  return func(c *gin.Context) {
    token := c.Request.FormValue("api_token")

    if token == "" {
      respondWithError(c, 401, "API token required")
      return
    }

    if token != requiredToken {
      respondWithError(c, 401, "Invalid API token")
      return
    }

    c.Next()
  }
}

Example above will check for presence of api_token parameter on every request and validate it against one defined as API_TOKEN environment variable. The important part is if you need to terminate request chain, you can call c.Abort. This will prevent any handlers in the chain to take place.

Code revision middleware

This type of middleware usually injects special headers into request response that could provide some idea on which git commit your application is running. In Ruby world that git sha is usually stored in REVISION or COMMIT file in the release directory, created by capistrano or other deployment tools. In fact, i created rack middleware just for that.

func RevisionMiddleware() gin.HandlerFunc {
  // Revision file contents will be only loaded once per process
  data, err := ioutil.ReadFile("REVISION")

  // If we cant read file, just skip to the next request handler
  // This is pretty much a NOOP middlware :)
  if err != nil {
    // Make sure to log error so it could be spotted
    log.Println("revision middleware error:", err)

    return func(c *gin.Context) {
      c.Next()
    }
  }

  // Clean up the value since it could contain line breaks
  revision := strings.TrimSpace(string(data))

  // Set out header value for each response
  return func(c *gin.Context) {
    c.Writer.Header().Set("X-Revision", revision)
    c.Next()
  }
}

As a result you'll get a new header in http response:

X-Revision: d4b371692d361869183d92d84caa5edb8835cf7d

Request ID middleware

Ofter API services inject a special header X-Request-Id to response headers that could be used to track incoming requests for monitoring/debugging purposes. Value of request id header is usually formatted as UUID V4.

// ...
import github.com/satori/go.uuid
// ...

func RequestIdMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    c.Writer.Header().Set("X-Request-Id", uuid.NewV4().String())
    c.Next()
  }
}

After you make a request to your service, you'll see a new header in the response, similar to this one:

X-Request-Id: ea9ef5f9-107b-4a4e-9295-57d701d85a92