preface

Gateway middleware is the event flow processing execution channel. It is a request flow control, also known as middleware, and is often used as a checkpoint “security check” for core components such as request header/request body validation, caching, and flow limiting.

The module is combed

We first compare the Go standard library server and Gin framework startup process, and then start from Gin.

Go Standard library Server

As we know, the Go library starts HTTP listener functions by following the following steps:

  • Description of the process

Register Handler → ListenAndServer → for poll Accept → Perform processing → call ServerHTTP() function of internal implementation body

func main(a) {
    hf := func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello there, from %q", html.EscapeString(r.URL.Path))
    }

    http.HandleFunc("/bar", hf)
    log.Fatal(http.ListenAndServe(": 8080".nil))}Copy the code

First, HandleFunc() encapsulates a structure called ServeMux, with a core of muxEntry as a mapping between the handler and the URL request path.

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry // Handler registers the dictionary to return the corresponding Handler. }// Handler is mapped to the location URL
type muxEntry struct {
    h       Handler
    pattern string
}
Copy the code

When the program starts, the main function above ties hf to the “/bar” path.

Then, with a simple implementation of multiplexing, we know that the cost of creating coroutines in Go is actually not very high, and that a request cycle usually doesn’t last very long.

As can be seen from the source code, each time Go accepts an HTTP connection, it is essentially a new Go coroutine for processing. The whole flow chart is as follows:

What encapsulation does Gin do

OK, now that we know the main flow, all of the HTTP Server implementation structure in Go is essentially

  1. Use the URL to locate the registered logical function
  2. Take out thehandler()Function is called.

Gin framework is also the main process, the optimization point is that in Gin URL and Handler fun are bound to a prefix tree, exactly as HttpRouter. Take a look at Httprouter before we talk about the Gin Web Framework


Introduction to middleware —- GIN common cases

Next, we will implement several simple middleware templates:

The Header interceptor

We design a Header that must have the desired entry to the request. If we lose the required Header, no further calls are made and the HTTP request is interrupted.

Usage template:

func init(a) {
    // Set the gin startup default port
    if err := os.Setenv("PORT"."8099"); err ! =nil {
        panic(err)
    }
}

var (
    H_KEY    = "h-key"  // Service header key
)

// Declare a simple handler for pingpong as the request acceptance behavior
func helloFunc(c *gin.Context) {
    const TAG = "PingPong"
    c.JSON(comdel.Success, comdel.SimpleResponse(200, TAG))
    return
}

func main(a) {
    // Create gin instance
    r := gin.Default()
    / / check the header
    r.POST("/hello-with-header", middle.HeaderCheck(H_KEY), helloFunc)
    e := r.Run()
    fmt.Printf("Server stop with err: %v\n", e)
}
Copy the code

It registers/hell-with-header to middle.HeaderCheck(H_KEY), which is equivalent to using HeaderCheck as an upstream interceptor for helloFunc().

Then look at the specific code of the middleware:

func HeaderCheck(key string) func(c *gin.Context) {
    return func(c *gin.Context) {
        // Get the header value
        kh := c.GetHeader(key)
        if kh == "" {
            / / the header
            c.JSON(http.StatusOK, &comdel.Response{Code: comdel.Unknown, Msg: "lacking necessary header"})
            // The request is invalid, terminate the current process
            c.Abort()
            return
        }
        // The request is normal
        c.Next()
    }
}
Copy the code

Demo Results:You can seeHeaderThe key we expected was missing, so it was intercepted by the middleware.

So in theory you can do whatever logic you want to check in the middleware. Accept any structure of the target type, check it against the target type when the HTTP request comes in, and use ShouldBindBodyWith() of the GIN framework to determine whether the current structure is valid.

Structure verification (reflection, runtime detection)

Pre-knowledge: We all know that as a static language, Go’s generics are not very mature, so if the program wants to represent any type passed in at run time, it needs to use the reflection feature to determine the type of unknown parameters at run time

  1. withinterface{}For compatible input parameter types.
  2. If the type passed in is non-nil, it is genericinterface{}Restore the parameter type
  3. Restore to the original structure passed inginBuilt-in check functionShouldBindBodyWith()

Now write a runtime body structure inspection, pass in the structure of the expected target type, and validate the field

// Construct returns the error format accordingly
func NewBindFailedResponse(tag string) *Response {
    return &Response{Code: WrongArgs, Msg: "wrong argument", Tag: tag}
}

// reqVal represents a specific struct type
func ReqCheck(reqVal interface{}) func(ctx *gin.Context) {
    var reqType reflect.Type = nil
    ifreqVal ! =nil {
        // Retrieve the original value type from interface{}
        value := reflect.Indirect(reflect.ValueOf(reqVal))
        // Run time: Get the checkbody original type
        reqType = value.Type()
    }

    return func(c *gin.Context) {
        tag := c.Request.RequestURI

        var req interface{} = nil
        ifreqType ! =nil {
            // Primitive type
            req = reflect.New(reqType).Interface()
            // Primitive type verification
            iferr := c.ShouldBindBodyWith(req, binding.JSON); err ! =nil {
                Struct binding error
                c.JSON(http.StatusOK, NewBindFailedResponse(tag))
                // Terminate the execution chain
                c.Abort()
                return}}// No need to check, execute chain down
        c.Next()
    }
}
Copy the code

Example program:

// Declare the request parameter structure, id must be passed
type PingReq struct {
    Name string `json:"name"`
    // tag Required Indicates that the ID field is required
    Id   string `json:"id" binding:"required"`
}

/* Inject ReqCheck to detect request body interception */ before routing /hello-with-req helloFunc() processing logic
/ /...
r.POST("/hello-with-req", middle.ReqCheck(bizmod.PingReq{}), helloFunc)
/ /...
Copy the code

Example request:

Pass in convention legal arguments:

Incoming convention violation missing ID parameter:

As you can see, both branches of the program meet our expectations, so we can use them latermiddle.ReqCheck(tar $Type)This middleware intercepts requests and allows the program to generalize$TypeOnly based on incoming at run time$TypeType check,Reduce duplication of code in downstream business functions.

The subsequent core business only needs to be carried out by helloFunc, which is equivalent to uniformly handling and eliminating the hidden danger of passengers entering and leaving the railway station at the security check, and the passengers who board the train must be those who have passed the security check.

conclusion

Going back to the previous description, middleware is ideally suited for accessing generic modules such as signature verification/gateway traffic limiting/log reporting/caching, etc.

Opportunities for further implementation examples and analysis of more specific business scenarios will follow in this series.