Using go to process Middleware

Time:2020-2-13
  • brief introduction
  • Middleware of gin
  • Create Middleware
  • summary
  • Code of current part

brief introduction

When developing web applications, many places need to use middleware to uniformly handle some tasks,
Such as logging, login verification, etc

Gin also provides middleware functions

Middleware of gin

At the beginning of project creation, some middleware has been imported, which was not introduced at that time

g.Use(gin.Logger())
g.Use(gin.Recovery())
g.Use(middleware.NoCache())
g.Use(middleware.Options())
g.Use(middleware.Secure())

The first two are the middleware of gin, which are logging and error recovery
The next three are to set some headers, specifically to prevent cache responses and respond to options requests,
And browser security settings

//Block cached response
func NoCache() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        ctx.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value")
        ctx.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
        ctx.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
        ctx.Next()
    }
}

//Respond to options request and exit
func Options() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        if ctx.Request.Method != "OPTIONS" {
            ctx.Next()
        } else {
            ctx.Header("Access-Control-Allow-Origin", "*")
            ctx.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
            ctx.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
            ctx.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
            ctx.Header("Content-Type", "application/json")
            ctx.AbortWithStatus(200)
        }
    }
}

//Security settings
func Secure() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        ctx.Header("Access-Control-Allow-Origin", "*")
        ctx.Header("X-Frame-Options", "DENY")
        ctx.Header("X-Content-Type-Options", "nosniff")
        ctx.Header("X-XSS-Protection", "1; mode=block")
        if ctx.Request.TLS != nil {
            ctx.Header("Strict-Transport-Security", "max-age=31536000")
        }

        // Also consider adding Content-Security-Policy headers
        // ctx.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com")
    }
}

The middleware structure of gin is a returnfunc(ctx *gin.Context)Function,
Also known asgin.HandlerFuncIn essence, it is no different from ordinary handler,
gin.HandlerFuncyesfunc(*Context)Alias.

Middleware can be defined in three places

  • Global Middleware
  • Group Middleware
  • Single routing Middleware

It should be noted that when goroutine is used in middleware and handler,
You should use a read-only copy of gin.context, for examplecCp := context.Copy().

Another point is to pay attention to the order of middleware

The official example is as follows:

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()

        // Set example variable
        c.Set("example", "12345")

        // before request

        c.Next()

        // after request
        latency := time.Since(t)
        log.Print(latency)

        // access the status we are sending
        status := c.Writer.Status()
        log.Println(status)
    }
}

Create Middleware

After introducing the middleware knowledge of gin, middleware can be used according to requirements

Implement a middleware setting in each requestX-Request-IdHead.

//Set x-request-id in request header
func RequestId() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        requestId := ctx.Request.Header.Get("X-Request-Id")

        if requestId == "" {
            requestId = uuid.NewV4().String()
        }

        ctx.Set("X-Request-Id", requestId)

        ctx.Header("X-Request-Id", requestId)
        ctx.Next()
    }
}

Set the header and save it in the context. After setting the unique ID,
You can track a series of requests

Then we can implement a middleware for logging. Although gin has brought its own middleware for logging,
But self realization can be more personalized

//Define the log component to record every request
func Logging() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        path := ctx.Request.URL.Path
        method := ctx.Request.Method
        ip := ctx.ClientIP()

        //Record only specific routes
        reg := regexp.MustCompile("(/v1/user|/login)")
        if !reg.MatchString(path) {
            return
        }

        var bodyBytes []byte
        if ctx.Request.Body != nil {
            bodyBytes, _ = ioutil.ReadAll(ctx.Request.Body)
        }
        //Write back after reading
        ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

        blw := &bodyLogWriter{
            body:           bytes.NewBufferString(""),
            ResponseWriter: ctx.Writer,
        }
        ctx.Writer = blw

        start := time.Now()
        ctx.Next()
        //Calculation delay, slightly different from gin. Logger
        //This is because middleware is similar to stack, first in first out, and CTX. Next () is the turning point
        //So the gin. Logger is placed at the top, recording the total time
        //Logging is put at the end to record the actual running time, excluding the time of other middleware
        end := time.Now()
        latency := end.Sub(start)

        code, message := -1, ""
        var response handler.Response
        if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil {
            logrus.Errorf(
                "Response body cannot be resolved to model.response struct, body: `% s', err: `% v '",
                blw.body.Bytes(),
                err,
            )
            code = errno.InternalServerError.Code
            message = err.Error()
        } else {
            code = response.Code
            message = response.Message
        }

        logrus.WithFields(logrus.Fields{
            "latency": fmt.Sprintf("%s", latency),
            "ip":      ip,
            "method":  method,
            "path":    path,
            "code":    code,
            "message": message,
        }). Info ("record request")
    }
}

When registering middleware, you willLoggingAt the end of the global middleware,
Put gin. Logger () at the beginning of the global middleware
By comparing the delay, you can find that when the handler processes faster,
Middleware takes up a large proportion of the total request time

Therefore, although middleware is very practical, it needs to control the number of global middleware

summary

Middleware is very practical, basically web framework will be implemented

Code of current part

As version V0.8.0