Gin 源码阅读(一)
source link: https://studygolang.com/articles/26120
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
初始化 Engine 对象
从官方提供的 demo
代码来逐行解析 gin
源码架构
r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run(":9999")
首先是 gin.Default()
,如下
func Default() *Engine { // debug 信息 debugPrintWARNINGDefault() // 返回了一个 Engine 结构体对象 engine := New() // engine 使用两个全局中间件 // 第一个是 logger,核心功能是通过 fmt.Fprint(out, formatter(param)) 来输出日志 // 第二个是 recovery,核心功能是通过 defer func() { if err := recover(); err != nil { ... } } 来处理 panic 错误 // 并将 panic 类型的错误转化为 500 服务器错误抛出 c.AbortWithStatus(http.StatusInternalServerError) // go 的 recover() 函数可以捕获抛出的 panic 类型错误 engine.Use(Logger(), Recovery()) return engine } // New 函数返回一个默认配置的 Engine 结构体对象 func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ // 路由集合 RouterGroup: 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: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", } // 依赖注入 engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { return engine.allocateContext() } return engine } // 注册全局中间件 func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { // 注册中间件 engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine } func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { // 将全局中间件添加到 group.Handlers group.Handlers = append(group.Handlers, middleware...) return group.returnObj() }
从上面可以看出, gin.Default
其实是返回了一个 gin
自定义的 Engine
结构体实例,并添加了两个默认中间件 logger
和 recovery
分别用于记录日志和捕获 panic
错误,随后的操作都是对这个 Engine
实例的操作。
添加路由
接下来逐行解析路由挂载,首先是 r.GET("/ping", ...gin.HandlerFunc)
// Engine.GET 直接访问到 RouterGroup.GET 是因为 /** type Engine struct { RouterGroup ... } */ // RouterGroup 通过结构体语法直接挂载在了 Engine 结构体下,所以可以直接通过 Engine 访问 RouterGroup 的方法 func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { // http.MethodGet 是一个字符串常量 "GET" return group.handle(http.MethodGet, relativePath, handlers) } // group.handle func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { // 拼接绝对路径 absolutePath := group.calculateAbsolutePath(relativePath) // 将所有的 HandlerFunc 组合在一起(其中包括中间件和主函数) handlers = group.combineHandlers(handlers) // 为当前方法和路由添加处理函数集合(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) // 最后返回 Engine 对象,可进行链式操作 return group.returnObj() } // 合并 handler func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) // 最大数量为 63 if finalSize >= int(abortIndex) { panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) // 合并全局中间件(group.Handlers)和当前路由中间件和处理函数(handlers) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers } // 注册路由 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 节点添加子节点 node root.addRoute(path, handlers) } // node 结构 type node struct { path string indices string // 每种方法(如 GET)是一个父节点 // 每个路径(如 /ping)都是一个子节点,通过 addRoute 添加到 children 中 children []*node handlers HandlersChain priority uint32 nType nodeType maxParams uint8 wildChild bool fullPath string }
gin
对于路由定义的处理分为两步:
-
将全局中间件和单路由中间件及处理函数进行合并,得到一个
gin.HandlerFunc
数组; -
将这个路由信息生成一个
node
节点,挂载在Engine
的trees
节点树中,不同的方法(如 GET、PUT...)成为父树,而不同的路径(如 /foo、/bar)则是子树,将handlers
及其他信息挂载在这个node
节点中,以便在未来使用。
启动应用
从 r.Run(":9999")
来进行解析,然后再回到 func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong"})}
。
func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() // 处理地址 address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) // http.ListenAndServe 监听端口,传入 engine // http.ListenAndServe 的处理函数需要实现 ServeHTTP 方法,所以我们需要看 Engine 的 ServeHTTP 方法 err = http.ListenAndServe(address, engine) return } func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // 对 *gin.Context 进行初始化 // *gin.Context 是 gin 的核心,太庞大了,这里不做展开 c := engine.pool.Get().(*Context) // 注入 http.ResponseWriter 和 *http.Request 到 *gin.Context 中 c.writermem.reset(w) c.Request = req // 每一次网络请求都会调用 c.reset() 对 *gin.Context 进行重置 c.reset() // 处理网络请求,进行响应(也是在这里做最终的路由匹配) engine.handleHTTPRequest(c) // 在对象池进行缓存,减少创建开销 engine.pool.Put(c) } // 核心处理函数 func (engine *Engine) handleHTTPRequest(c *Context) { // 从注入的依赖中取出请求方法及请求路径 httpMethod := c.Request.Method rPath := c.Request.URL.Path //... t := engine.trees for i, tl := 0, len(t); i < tl; i++ { // 判断请求方法是否能匹配到节点 if t[i].method != httpMethod { continue } root := t[i].root // 匹配路由,到这里就比较简单了,这里不做展开 // value 中包含了 handlers 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() 就是遍历 handlers,按顺序依次执行 // 也就完成了所有中间件及最终响应函数的执行,该函数在下面有展开 c.Next() c.writermem.WriteHeaderNow() return } //... } // 无匹配项,404 错误处理 serveError(c, http.StatusNotFound, default404Body) } func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } } // c.Next() 最终执行了 func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong"})} // c.JSON func (c *Context) JSON(code int, obj interface{}) { // 调用 render c.Render(code, render.JSON{Data: obj}) } func (c *Context) Render(code int, r render.Render) { c.Status(code) // ... // 最终调用 render.JSON.Render 方法中的 WriteJSON 方法响应结果 if err := r.Render(c.Writer); err != nil { panic(err) } } func WriteJSON(w http.ResponseWriter, obj interface{}) error { // 写入对应的 header 头部 writeContentType(w, jsonContentType) // 在 io.Writer 中写入对应的 JSON 内容 // 这里的 io.Writer 对应的就是 http.ResponseWriter encoder := json.NewEncoder(w) err := encoder.Encode(&obj) return err }
我们最后来梳理一遍, r.Run(":9999")
启动了一个 http
服务,监听了指定端口,然后将端口的所有请求交给 Engine
处理。
Engine
之所以有处理请求的能力,是因为实现了 http.Handler
接口,包含 ServeHTTP
方法,所有的请求就会交由 Engine
的 ServeHTTP
处理。
Engine
的 ServeHTTP
方法包装了一个 *gin.Context
对象,将这个对象传入每个 gin.HandlerFunc
中,然后调用所有的 handlers
,完成对中间件及最终响应函数的调用。
至此, gin
的主流程已经梳理完毕,接下来的文章是对 gin
的一些 API
的深入梳理,欢迎关注。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK