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
- Use the URL to locate the registered logical function
- Take out the
handler()
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 seeHeader
The 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
- with
interface{}
For compatible input parameter types. - If the type passed in is non-nil, it is generic
interface{}
Restore the parameter type - Restore to the original structure passed in
gin
Built-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$Type
Only based on incoming at run time$Type
Type 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.