Detailed explanation of gin framework Middleware

Posted by alemapo on Wed, 26 Jan 2022 00:53:39 +0100

Detailed explanation of gin framework Middleware

gin framework involves middleware. There are four common methods, namely c.Next(), c.Abort(), c.Set(), c.Get().

1, Registration of Middleware

The middleware design in gin framework is very ingenious. We can start with our most commonly used R: = gin The Default function of Default() starts to look. After it constructs a new engine internally, it registers the Logger middleware and Recovery middleware through the Use() function:

func SetupRouter() *gin.Engine {
	r := gin.Default()
	// Load static file
	r.Static("/static", "static")
	// Load template
	r.LoadHTMLGlob("./templates/*")
	r.GET("/", controller.IndexHandler)

	// route
	TodoRouters(r)

	return r
}

func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())  // Two middleware registered by default
	return engine
}

Continue to look at the code of the Use() function:

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)  // In fact, it is the Use function of the RouterGroup that is called
	engine.rebuild404Handlers()
	engine.rebuild405Handlers()
	return engine
}

As can be seen from the code below, registering middleware is actually adding middleware functions to group In handlers:

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

When registering a route, we will combine the function of the corresponding route with the previous middleware function:

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := group.calculateAbsolutePath(relativePath)
	handlers = group.combineHandlers(handlers)  // Combine the request processing function with the middleware function
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

The function content of the combined operation is as follows. Pay attention to how to splice two slices to get a new slice.

const abortIndex int8 = math.MaxInt8 / 2

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	finalSize := len(group.Handlers) + len(handlers)
	if finalSize >= int(abortIndex) {  // There is a maximum limit
		panic("too many handlers")
	}
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers)
	copy(mergedHandlers[len(group.Handlers):], handlers)
	return mergedHandlers
}

In other words, we will combine a routing middleware function and processing function to form a processing function chain, HandlersChain, which is essentially a slice composed of HandlerFunc:

type HandlersChain []HandlerFunc

2, Execution of Middleware

We have seen the following logic in the above route matching:

value := root.getValue(rPath, c.Params, unescape)
if value.handlers != nil {
  c.handlers = value.handlers
  c.Params = value.params
  c.fullPath = value.fullPath
  c.Next()  // Execution function chain
  c.writermem.WriteHeaderNow()
  return
}

c.Next() is a key step, and its code is very simple:

func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

As can be seen from the above code, the HandlersChain chain is traversed through the index, so as to call each function of the route in turn (middleware or request processing function).

We can invoke c.Next() in the middleware function to implement nested calls (calling func2 in func1, calling func3 in func2).

Or interrupt the whole call chain by calling c.Abort() and return from the current function.

func (c *Context) Abort() {
	c.index = abortIndex  // Set the index directly to the maximum limit value to exit the loop
}

3, c.Set()/c.Get()

c.Set() and c.Get() are mostly used to transfer data between multiple functions through C. for example, we can obtain the relevant information of the current request (userID, etc.) in the authentication middleware, store it in C through c.Set(), and then obtain the user of the current request through c.Get() in the subsequent functions dealing with business logic. C is like a rope that strings all the functions related to the request.

4, Summary

  1. gin framework uses prefix tree for routing. The process of route registration is the process of constructing prefix tree, and the process of route matching is the process of finding prefix tree.
  2. The middleware functions and processing functions of the gin framework exist in a call chain in the form of slices. We can call them sequentially or nested with the help of c.Next() method.
  3. With the help of c.Set() and c.Get() methods, we can transfer data in different middleware functions.