29

Gin 源码阅读(一)

 4 years ago
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 结构体实例,并添加了两个默认中间件 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 的深入梳理,欢迎关注。

原文地址,欢迎收录


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK