内容纲要
前情提要
Gin(二)深度解析 HelloWorld,尝尝核心代码的滋味
经过前篇文章的分析,我们能知道,Gin 运行的大龙骨大致为:
- New 一个 Engine 实例
- 把 Engine 实例作为 http.Server 的 Handler 参数传进去
- http.Server 启动服务并监听请求
- 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)
- 主要是设置响应状态码(当然还有其它操作,我们暂时不深究)
- Header() Header
- 经过代码跟踪,能知道这个参数在运行态的时候,其实是一个实现了 http.ResponseWriter 接口的结构体 http.response(一个变量很多的包,暂时不深究)
- http.Request
- 这个参数运行时,实体指向的是前一个参数 http.ResponseWriter 实际指向的 http.response 的一个变量
- 该变量包含请求 Method、URL、Body 等内容
- http.ResponseWriter
- 参数
- 第 3 行
- pool
- Engine 结构体的变量
- 是 synx.Pool 结构体类型(临时对象池)
- 用来缓存已经申请了的、目前未使用的、接下来可能会使用的内存,目的是缓解 GC 压力
- 在多 goroutine 中是安全的(简单理解就是:线程安全)
- 有一个默认 New 函数用于创建缓存对象(可以通过修改 New 指向的函数来自定义创建缓存对象的类型)
- Engine 为 pool 的 New 设置了生成 Context 的函数
- Context(请求上下文信息:是整个请求数据处理的核心所在)
- 包含请求对象、响应对象、URL 参数等信息
- 可以通过函数方法获取 Cookie、请求包体信息等
- pool
- 第 4~6 行
- 初始化 Context 信息
- 把参数 http.ResponseWriter 和 http.Request 绑定到 Context 中
- 初始化 Context 信息
- 第 8 行
- Engine 通过将 Context 传入这个函数,并根据 Context 中的 URL 路径、参数、Method 等信息,在 Engine 的 trees 变量中查找请求路由(请求处理函数)
- 查找到匹配路由则调用路由处理函数(gin.HandlerFunc)
- 匹配不到根据不同的行为返回 MethodNotAllowed 或 PageNotFound 等错误(前文中的 Demo 中并没有定义任何请求路由,所以会执行者部分代码进行请求处理)
- Engine 通过将 Context 传入这个函数,并根据 Context 中的 URL 路径、参数、Method 等信息,在 Engine 的 trees 变量中查找请求路由(请求处理函数)
- 第 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 结构体
- gin.methodTrees 是 gin.methodTree 数组
- 我们之前看过 Engine 的代码,知道 trees 是一个 gin.methodTrees 类型
- gin.node(是一个结构体,代码就不贴了,同学们可以自己去源码里看)
- 这块代码主要是循环 trees ,从其中找出 root node(详见 node 设计)
- 第 43 行开始的 if 代码块
- 第 5 行附近的 if 代码块
通过初步分析,我们发现几个很重要的点:
- trees
- node
篇幅太长不好消化,这几个点留到下一篇文章来分析吧。