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:
- pgweb - PostgreSQL web interface
- omxremote - GUI and API for Raspberry Pi's omxplayer
- envd - API to serve environment variables over HTTP
- hipache-api - HTTP API for Hipache
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.文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
Here's the baseline app:
文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
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") }
文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
Now, lets add some middleware:文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
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:文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
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!文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
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.文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
func respondWithError(code, message, *gin.Context) { resp := map[string]string{"error": message} c.JSON(code, resp) c.Abort(code) } func TokenAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { token := c.Request.FormValue("api_token") if token == "" { respondWithError(401, "API token required", c) return } if token != os.Getenv("API_TOKEN") { respondWithError(401, "Invalid API token", c) 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 other handlers from execution.文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
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.文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
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 { 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:文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
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.文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
// ...
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:文章源自运维生存时间-https://www.ttlsa.com/golang/gin-middleware-example/
X-Request-Id: ea9ef5f9-107b-4a4e-9295-57d701d85a92

评论