Basic use
Gin is a high-performance golang web framework. Its code is actually very few. Compared with spring, understanding gin is really chopping melons and vegetables
Let's take a look at the basic usage of gin
import "github.com/gin-gonic/gin" func Init() { r := gin.Default() initRoute(r) r.Run() } func initRoute(r *gin.Engine) { r.GET("ping", handler) } func handler(ctx *gin.Context) { ctx.String(200, "Hello World!") }
In this example, we start a simple web service using Gin Default creates an engine object. Engine is the core of Gin core, which will be explained in detail later. Then register a route through initRoute. When you access localhost:8080/ping, you will return Hello World!.
Finally, call r.run to enable the service to start. With a few lines of code, you can start a web service through gin. We mainly explore its implementation principle. We won't elaborate on more language features and usage of gin here. More examples can be found here Gin Examples.
Engine
Explanation on the official website: Engine is an example of the framework, which includes multiplexers, middleware and various configurations.
type Engine struct { // Routing group RouterGroup // If set to true, if / foo / does not match the route, it will automatically redirect to / foo RedirectTrailingSlash bool // Similar to RedirectTrailingSlash, make some modifications to the path RedirectFixedPath bool HandleMethodNotAllowed bool ForwardedByClientIP bool // DEPRECATED AppEngine bool // Via URL Rawpath to get parameters UseRawPath bool // If true, the path value will be unescaped. // If UseRawPath is false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. UnescapePathValues bool RemoveExtraSlash bool // List of headers used to obtain the client IP when // `(*gin.Engine).ForwardedByClientIP` is `true` and // `(*gin.Context).Request.RemoteAddr` is matched by at least one of the // network origins of list defined by `(*gin.Engine).SetTrustedProxies()`. RemoteIPHeaders []string // If set to a constant of value gin.Platform*, trusts the headers set by // that platform, for example to determine the client IP TrustedPlatform string // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm // method call. MaxMultipartMemory int64 delims render.Delims secureJSONPrefix string HTMLRender render.HTMLRender FuncMap template.FuncMap allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain noMethod HandlersChain pool sync.Pool trees methodTrees maxParams uint16 maxSections uint16 trustedProxies []string trustedCIDRs []*net.IPNet }
With so many fields in the Engine, there are still many ways not to post them here. Don't worry. We peel the cocoon bit by bit to uncover the true face of gin
Simply put, a gin service corresponds to an Engine instance. r = gin.Default() actually creates an Engine object, and its source code is not complex
func Default() *Engine { engine := New() // Registration Middleware engine.Use(Logger(), Recovery()) return engine } func New() *Engine { engine := &Engine{ RouterGroup: RouterGroup{ Handlers: nil, basePath: "/", root: true, }, FuncMap: template.FuncMap{}, RedirectTrailingSlash: true, ...... } engine.RouterGroup.engine = engine // pool stores the context object engine.pool.New = func() interface{} { return engine.allocateContext() } return engine }
First, a New Engine comes out, initializes some infrastructure, and then injects two middleware logger s and recovery through the Use method. Their types are HandlerFunc. Loggers are used for logging. Recovery is used to recover when the service panic is recover, and then returns 500 to avoid direct service crash.
Interestingly, there is a field pool in engine, and the type is sync Pool. When creating the engine, we also initialized the pool. About sync Pool can see my other article [todo]. You can see that the New method of pool creates and returns a context, which is an object that carries context information for every request. It can be imagined that the context is very heavy. If each request creates and destroys a context object, the GC will not be able to support it, How to become a high-performance wen server? So Gin through sync Pool to reuse context and optimize GC.
route
No matter how the web service framework is implemented, its most basic function is routing. In short, it parses the url and then dispatches different handler s to process the corresponding requests.
After creating the engine, we also tell it how to handle the routing information. Registering routes is very simple. Engine supports six RESTful methods.
func initRoute(r *gin.Engine) { r.GET("ping", pingHandler) }
In fact, the Engine implements routing processing by inheriting the RouterGroup (which can be found in the structure of the Engine). All routing processing is completed in the RouterGroup. Take the Get source code as an example
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodGet, relativePath, handlers) }
After registering a route through GET, we will GET an IRoutes object. IRoutes itself is an interface and defines a series of methods, including GET, POST... Etc. it is not difficult to find that RouterGroup implements IRoutes. In addition, when registering a route, not only one processing method can be inserted into a path. The three points in the source code represent that we can pass in multiple handler s, which will be called one by one when the request arrives. GET directly calls the internal group Handle, how is it implemented
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
There are three steps
1. Calculate absolute path
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string { return joinPaths(group.basePath, relativePath) }
In fact, it is the string join, which is combined with basePath and the currently registered path. Where does basePath come from and why join?
When we create the Engine, we actually register a path, which is "/". All other subsequent registered paths are hung under it. If we regard it as the root node of a tree, there are more child nodes under it, and a complete path is the combination of all paths passing from the root node to the child nodes. In fact, Gin uses the tree structure for routing management, which will be discussed later.
2. Add handlers
The handlers passed in before are path, but other people's basePath also has handlers. basePath is the prefix of path. When you initiate a request, should all basePath handlers be reviewed. The addition process is not responsible, that is, reopen an array space, copy both basePath and path handlers here, and then return the array address.
3. Associate absolute paths and handlers
This step is more important and the logic is more complex. First look at the outermost code and filter out some irrelevant code
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { ...... 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) ...... }
First, get the root node according to the method. What are the methods? GET, POST, PUT, DELETE…
If the root node does not exist, create one and join the entine In trees, entine Trees are methodTrees. The definition of methodTrees is [] methodTree, so entity Trees is an array of methedTree. What's a metedtree?
type methodTree struct { method string root *node } type node struct { path string indices string wildChild bool nType nodeType priority uint32 children []*node // child nodes, at most 1 :param style node at the end of the array handlers HandlersChain fullPath string }
The link is a little long. Draw a picture more clearly
To be continued