Gin(三)给 Engine 看看相

内容纲要

前情提要

Gin(二)深度解析 HelloWorld,尝尝核心代码的滋味

经过前篇文章的分析,我们能知道,Gin 运行的大龙骨大致为:

  1. New 一个 Engine 实例
  2. 把 Engine 实例作为 http.Server 的 Handler 参数传进去
  3. http.Server 启动服务并监听请求
  4. http.Server 接收到请求后,通过参数 Handler 的 ServeHTTP 接口,把请求包传回给 Engine

本篇目的

  • 分析 Engine 的数据处理流程

Engine 的数据入口

我们已经知道 Engine 的 ServerHTTP 函数能从 http.Server 得到请求数据包,接下来我们来(跟踪 Engine 的 ServerHTTP)看看 Engine 又对请求包做了什么。

Engine 的 ServerHTTP 函数

// ServeHTTP conforms to the http.Handler interface.
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)
}
  • 函数声明
    • 参数
      • http.ResponseWriter
        • 来自 go 自带的 http 包
        • 是一个 interface(有下面 3 个接口)
          • Header() Header
            • 它的作用是返回一个 Header(其实是:map[string][]string) 类型的变量(当然还有其它操作,我们暂时不深究)
            • 是请求响应头的键值对数据
          • Write([]byte) (int, error)
            • 将数据作为请求响应的一部分写入(当然还有其它操作,我们暂时不深究)
          • WriteHeader(statusCode int)
            • 主要是设置响应状态码(当然还有其它操作,我们暂时不深究)
        • 经过代码跟踪,能知道这个参数在运行态的时候,其实是一个实现了 http.ResponseWriter 接口的结构体 http.response(一个变量很多的包,暂时不深究)
      • http.Request
        • 这个参数运行时,实体指向的是前一个参数 http.ResponseWriter 实际指向的 http.response 的一个变量
        • 该变量包含请求 Method、URL、Body 等内容
  • 第 3 行
    • pool
      • Engine 结构体的变量
      • 是 synx.Pool 结构体类型(临时对象池)
        • 用来缓存已经申请了的、目前未使用的、接下来可能会使用的内存,目的是缓解 GC 压力
        • 在多 goroutine 中是安全的(简单理解就是:线程安全)
        • 有一个默认 New 函数用于创建缓存对象(可以通过修改 New 指向的函数来自定义创建缓存对象的类型)
      • Engine 为 pool 的 New 设置了生成 Context 的函数
    • Context(请求上下文信息:是整个请求数据处理的核心所在)
      • 包含请求对象、响应对象、URL 参数等信息
      • 可以通过函数方法获取 Cookie、请求包体信息等
  • 第 4~6 行
    • 初始化 Context 信息
      • 把参数 http.ResponseWriter 和 http.Request 绑定到 Context 中
  • 第 8 行
    • Engine 通过将 Context 传入这个函数,并根据 Context 中的 URL 路径、参数、Method 等信息,在 Engine 的 trees 变量中查找请求路由(请求处理函数)
      • 查找到匹配路由则调用路由处理函数(gin.HandlerFunc)
      • 匹配不到根据不同的行为返回 MethodNotAllowed 或 PageNotFound 等错误(前文中的 Demo 中并没有定义任何请求路由,所以会执行者部分代码进行请求处理)
  • 第 10 行
    • 请求处理结束,将 Context 资源放回 pool 里

ServerHTTP 小结

经过分析 ServerHTTP 行数的行为,我们大致的了解了数据从 http.Server 到 gin.Engine,gin.Engine 把请求、响应封装到 Context,再由 Engine 的 handleHTTPRequest 经过从 trees 里查找请求处理 HandlerFunc 来真正处理业务数据的过程。

engine.handleHTTPRequest 如何处理请求的技术细节

通过前面的分析,我们知道 engine.handleHTTPRequest 是真正处理请求实体的方法。接下来,我们再深入 engine.handleHTTPRequest 看下他到底是怎么工作的。

首先我们大致过一下 engine.handleHTTPRequest 的代码:

func (engine *Engine) handleHTTPRequest(c *Context) {
    httpMethod := c.Request.Method
    rPath := c.Request.URL.Path
    unescape := false
    if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
        rPath = c.Request.URL.RawPath
        unescape = engine.UnescapePathValues
    }

    if engine.RemoveExtraSlash {
        rPath = cleanPath(rPath)
    }

    // Find root of the tree for the given HTTP method
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {
        if t[i].method != httpMethod {
            continue
        }
        root := t[i].root
        // 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
        }
        if httpMethod != "CONNECT" && rPath != "/" {
            if value.tsr && engine.RedirectTrailingSlash {
                redirectTrailingSlash(c)
                return
            }
            if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
                return
            }
        }
        break
    }

    if engine.HandleMethodNotAllowed {
        for _, tree := range engine.trees {
            if tree.method == httpMethod {
                continue
            }
            if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
                c.handlers = engine.allNoMethod
                serveError(c, http.StatusMethodNotAllowed, default405Body)
                return
            }
        }
    }
    c.handlers = engine.allNoRoute
    serveError(c, http.StatusNotFound, default404Body)
}

代码初步解读:

  • 首先我们可以看到,整个函数逻辑大致被几个 if 和 for 分为 4 块
    • 第 5 行附近的 if 代码块
      • 这块的核心在 UseRawPath,这个变量在我们之前在看 Engine 结构体代码的时候出现过,大致作用就是用来限定我们使用的是 url.Path 还是 url.RawPath(具体区别我们先按下不表,暂时知道它是这个作用就行)
    • 第 10 行开始的 if 代码块
      • 这个也在 Engine 结构体里提过,是处理 URL 末尾斜杆问题的(暂时不深究)
    • 第 15 行开始的 for 代码块(重点来了
      • 这块就是我们要找的路由查询以及处理函数调用的代码(源码里还特别加了注释,说明确实是比较重要的一块吧)
      • 看这段代码逻辑前,我们先看下代码里出现的两个重要结构体、变量
        • gin.node(是一个结构体,代码就不贴了,同学们可以自己去源码里看)
          • 初步看着大致可以知道这是个管理 path 的结构体(树)
        • trees
          • 我们之前看过 Engine 的代码,知道 trees 是一个 gin.methodTrees 类型
            • gin.methodTrees 是 gin.methodTree 数组
              • gin.methodTree 是一个结构体,里面包含两个变量:method 字符串和 gin.node 结构体
      • 这块代码主要是循环 trees ,从其中找出 root node(详见 node 设计)
    • 第 43 行开始的 if 代码块

通过初步分析,我们发现几个很重要的点:

  • trees
  • node

篇幅太长不好消化,这几个点留到下一篇文章来分析吧。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注