introduce

  • Gin implements the most important routing module based on Httprouter and uses a data structure similar to dictionary tree to store the mapping of routes to handle methods. It is also the reason for the high performance of the framework. Interested students can consult by themselves
  • This article provides online mind mapping with articles to see more with less
  • EngineContainer objects, the basis of the entire framework
  • Engine.treesIs responsible for storing route and handle method mapping, using dictionary tree structure
  • Engine.RouterGroupHandlers store all middleware
  • ContextContext object, responsible for processingRequests and responses, in which thehandlersIt stores the middleware and processing methods used to process the request

Initialize the container

Instantiate Engine by calling the gin.New() method. There are many arguments, but we only need to note RouterGroup,trees, and engine.pool.new

  • engine.pool.NewResponsible for creatingContextObject, usingsync.PoolReduce resource consumption due to frequent context instantiations,
Handlers: func New() *Engine {debugPrintWARNINGNew() Engine := &engine { RouterGroup{ Handlers: nil, basePath: "/", root: true, }, FuncMap: template.FuncMap{}, RedirectTrailingSlash: true, RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, AppEngine: defaultAppEngine, UseRawPath: false, RemoveExtraSlash: false, UnescapePathValues: true, MaxMultipartMemory: DefaultMultipartMemory, //trees is the most important point!!!! Trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);" . } engine. RouterGroup. Engine = engine / / the context is realized by using the sync/pool pool here, reduce the consumption of resources caused by frequent context instantiation engine. The pool. The New = func () interface{} { return engine.allocateContext() } return engine }Copy the code

Registered middleware

Gin’s high performance is mainly dependent on trees. The contents of each node can be thought of as a dictionary tree of key->value. Key is a route and value is a []HandlerFunc, which stores the sequential execution of middleware and Handle controller methods.

Register global middleware

Gin.Use() calls RouterGroup. use() to write to RouterGroup.Handlers

func (engine *Engine) Use(middleware ... HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...) 
	engine.rebuild404Handlers() // Register the 404 handler
	engine.rebuild405Handlers() // Register the 405 processing method
	return engine
}

The 'Handlers' field is an array used to store middleware
func (group *RouterGroup) Use(middleware ... HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}
Copy the code

Register routing group middleware

  • throughGroup()Method returns a newly generatedRouterGroupPointer used to separate each routing group from the middleware loaded differently
  • Notice hereHandlers: group.combineHandlers(handlers)This line of code copies a copy of the global middleware to the newly generatedRouterGroup.Handlers, and then the route can be written to the tree node during registration
group := g.Group("/test_group")
group.Use(middleware.Test())
{
	// The final route is written to the tree node along with the middleware and handle methods
	group.GET("/test",handler.TestTool)
}

// Returns a RouterGroup pointer
func (group *RouterGroup) Group(relativePath string, handlers ... HandlerFunc) *RouterGroup {
	return &RouterGroup{
		// Copy a copy of the global middleware
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}
Copy the code

Register routing and middleware

Either request will eventually call RouterGroup.handle, which has two main functions

  • Handles the format of routes by spelling them into ‘/’ characters

  • Handlers make a copy of the RouterGroup.Handlers make a list with the corresponding handle for the route and put it in the tree

  • Finally, call trees. AddRoute to add a node

g.GET("/test_tool", middleware.Test(),handler.TestTool)

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
 	// The root directory and the route are combined to spell the route as a route beginning with the '/' character
	absolutePath := group.calculateAbsolutePath(relativePath) 
	Handlers make a list of routerGroups. Handlers and put them in the tree
	handlers = group.combineHandlers(handlers) 
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

// Call 'trees' to add nodes
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
	assert1(path[0] = ='/'."path must begin with '/'") assert1(method ! =""."HTTP method can not be empty")
	assert1(len(handlers) > 0."there must be at least one handler")

	debugPrintRoute(method, path, handlers)
	root := engine.trees.get(method)
	if root == nil {
		root = new(node)
		root.fullPath = "/"
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
	root.addRoute(path, handlers)
}
Copy the code

Start the

The service is started by calling net/ HTTP, and since Engine implements the ServeHTTP method, you just need to pass the Engine object directly to initialize and start it

g.Run()
func (engine *Engine) Run(addr ...string) (err error) {
	defer func(a) { debugPrintError(err) }()
	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine)
	return
}
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
// An interface from the net/ HTTP definition, which can be used as a function to handle requests as long as it is implemented
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
// Implements the ServeHTTP method
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()
	engine.handleHTTPRequest(c)
	engine.pool.Put(c)
}
Copy the code

Handle the request

  • Just be careful herehandleHTTPRequest(c *Context) The method is good
  • Through the request method and route to find the corresponding tree node, obtain the stored[]HandlerFuncList, by callingc.Next()Handle the request
  • By moving the subscript recursively, the final result is returned
func (engine *Engine) handleHTTPRequest(c *Context) { ... // t := engine.trees for i, tl := 0, len(t); i < tl; i++ { ... // Find route in tree 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() c.writermem.WriteHeaderNow() return } ... }... } func (c *Context) Next() {c.index++ for C.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } }Copy the code

feeling

  • Gin framework source code is relatively easy to understand, this is precisely its advantage, Golang language itself is relatively mature, the framework is only a convenient scaffolding for you to do the project, you can completely according to your needs to customize your own exclusiveginFramework, including logging, caching, queues, and so on
  • The core is routing storage tree, learn algorithm, data structure is the key

Reference documentation

Gin Chinese document https://gin-gonic.com/zh-cn/docs/introduction/

The gin project is at https://github.com/gin-gonic/gin

httprouter https://github.com/julienschmidt/httprouter