Gin 源码阅读(一)

作者: 昵称不用太拉风 | 来源:发表于2020-01-15 15:46 被阅读0次

    初始化 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 结构体实例,并添加了两个默认中间件 loggerrecovery 分别用于记录日志和捕获 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 对于路由定义的处理分为两步:

    1. 将全局中间件和单路由中间件及处理函数进行合并,得到一个 gin.HandlerFunc 数组;
    2. 将这个路由信息生成一个 node 节点,挂载在 Enginetrees 节点树中,不同的方法(如 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 方法,所有的请求就会交由 EngineServeHTTP 处理。

    EngineServeHTTP 方法包装了一个 *gin.Context 对象,将这个对象传入每个 gin.HandlerFunc 中,然后调用所有的 handlers,完成对中间件及最终响应函数的调用。

    至此,gin 的主流程已经梳理完毕,接下来的文章是对 gin 的一些 API 的深入梳理,欢迎关注。

    原文地址,欢迎收录

    相关文章

      网友评论

        本文标题:Gin 源码阅读(一)

        本文链接:https://www.haomeiwen.com/subject/etvtzctx.html