一. 获取请求后的路由匹配
- gin中Engine实现了ServeHTTP()函数,是请求的入口函数, 这里可以去了解一下net/http的原理,查看ServeHTTP()源码,内部会调用handleHTTPRequest()
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)
}
- 查看handleHTTPRequest()源码(此处只摘取了其中关键部分),在该方法中会调用node下的getValue(rPath, c.Params, unescape)
func (engine *Engine) handleHTTPRequest(c *Context) {
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
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
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
- getValue()该函数根据给定的路径(键)返回nodeValue值,nodeValue中保存注册的处理函数和匹配到的路径参数数据
type nodeValue struct {
handlers HandlersChain
params Params
tsr bool
fullPath string
}
func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) {
value.params = po
walk:
for {
prefix := n.path
if path == prefix {
if value.handlers = n.handlers; value.handlers != nil {
value.fullPath = n.fullPath
return
}
if path == "/" && n.wildChild && n.nType != root {
value.tsr = true
return
}
indices := n.indices
for i, max := 0, len(indices); i < max; i++ {
if indices[i] == '/' {
n = n.children[i]
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
(n.nType == catchAll && n.children[0].handlers != nil)
return
}
}
return
}
if len(path) > len(prefix) && path[:len(prefix)] == prefix {
path = path[len(prefix):]
if !n.wildChild {
c := path[0]
indices := n.indices
for i, max := 0, len(indices); i < max; i++ {
if c == indices[i] {
n = n.children[i]
continue walk
}
}
value.tsr = path == "/" && n.handlers != nil
return
}
n = n.children[0]
switch n.nType {
case param:
end := 0
for end < len(path) && path[end] != '/' {
end++
}
if cap(value.params) < int(n.maxParams) {
value.params = make(Params, 0, n.maxParams)
}
i := len(value.params)
value.params = value.params[:i+1]
value.params[i].Key = n.path[1:]
val := path[:end]
if unescape {
var err error
if value.params[i].Value, err = url.QueryUnescape(val); err != nil {
value.params[i].Value = val
}
} else {
value.params[i].Value = val
}
if end < len(path) {
if len(n.children) > 0 {
path = path[end:]
n = n.children[0]
continue walk
}
value.tsr = len(path) == end+1
return
}
if value.handlers = n.handlers; value.handlers != nil {
value.fullPath = n.fullPath
return
}
if len(n.children) == 1 {
n = n.children[0]
value.tsr = n.path == "/" && n.handlers != nil
}
return
case catchAll:
if cap(value.params) < int(n.maxParams) {
value.params = make(Params, 0, n.maxParams)
}
i := len(value.params)
value.params = value.params[:i+1]
value.params[i].Key = n.path[2:]
if unescape {
var err error
if value.params[i].Value, err = url.QueryUnescape(path); err != nil {
value.params[i].Value = path
}
} else {
value.params[i].Value = path
}
value.handlers = n.handlers
value.fullPath = n.fullPath
return
default:
panic("invalid node type")
}
}
value.tsr = (path == "/") ||
(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
path == prefix[:len(prefix)-1] && n.handlers != nil)
return
}
}
二. 总结
- 这里还是要说一下gin与net/http直接,在编写gin服务时,先初始化容器,创建Engine实例,然后调用Run()启动服务,在run方法中会执行net/http下的ListenAndServe(),请求数据就在net/http开始流转,所以 gin 建立 socket 的过程,accept 客户端请求的过程与 net/http 没有差别
- gin的路由注册,路由关系维护是基于httprouter实现的(有的博客说是直接调用httprouter底层,但是我再gin中没有找到,不知道是不是我看的不够仔细),这里就先说成gin路由注册是参考httprouter实现的
- 在编写gin服务时要先拿到Engine实例,了解一下Engine 的继承关系与内部结构
- Engine隐式的继承了RouterGroup,那Engine实际就可以看成一个RouterGroup,而RouterGroup实现了IRouter,IRoutes接口拥有GET(),POST()等路由注册方法
- Engine还实现了Handler也就是实现了ServeHTTP()函数,是请求的入口函数
- Engine中还存在一个trees属性也就是前缀树,
- Engine上还绑定了一个addRoute()方法,在通过RouterGroup的GET(),POST()等方法进行路由注册时,内部会调用到该方法,最终会将接口路径与接口处理器函数封装为methodTree保存到Engine的trees中
- 在执行ListenAndServe()启动http服务在指定端口监听接收请求时,内部会会调用
- " net.Listen(“tcp”, addr)": 多路复用相关初始化,初始化socket,端口连接绑定,开启监听
- “srv.Serve(ln)”: 开启循环调用Accept()基于多路复用初始化FD,pollDesc,封装epollevent,调用netpollopen(),将可读,可写,对端断开,边缘触发 的监听事件注册到epollevent中
- 当接收到连接请求Accept()方法返回会拿到一个net.Conn连接,可以理解为拿到了一个基于TCP的HTTP连接,并将conn连接的状态标志为StateNew,Conn是一个接口,内部提供了:Read读取连接数据,Write发生数据,Close关闭连接等方法,比较重要,底层就是通过这几个方法处理请求读写的
- 然后执行"go c.serve(ctx)" 开启一个goroutine执行conn的serve()方法
- 查看conn上的serve(),该方法内重点执行了:
- 首先调用newBufioReader() 封装了一个bufio.Reader
- 开启了一个无限for循环,循环内
- 调用conn的readRequest(ctx)方法读取请求的内容,比如解析HTTP请求协议,读取请求头,请求参数,封装Request和response,在解析时会读取请求头的 Content-Length,不为 0会通过TCPConn.Read() 方法读取指定长度的数据并存入请求体中,如果 Content-Length 为 0 或者没有设置,则请求体为空
- 封装serverHandler调用serverHandler上的ServeHTTP(w, w.req)方法进行路由匹配,找到对应的处理函数,执行我们写的业务逻辑
- 调用response的finishRequest()方法进行最后处理工作,当底层 bufio.Writer 缓冲区的大小达到阈值或者Flush() 被显式调用时,就会将缓冲区内的数据写入到底层连接中,并触发 Conn 的 Write() 方法将数据发送到客户端,另外finishRequest()方法还会进行一些比如异常处理,资源回收,状态更新等操作
- 最后调用conn的setState()设置连接状态为StateIdle,方便后续重用连接
- 会封装serverHandler调用serverHandler上的ServeHTTP(w, w.req),其中ServeHTTP是一个接口,绝大多数Web框架都是通过实现该接口,从而替换掉Golang默认的路由,这里执行的就是gin中Engine实现的ServeHTTP()函数,查看该函数,重点关注调用了Engine上绑定的handleHTTPRequest()方法
- 解析请求,获取请求url,请求方法method, 获取到trees路由树,
- 然后基于当前的请求方法,比如GET,POST等,在trees中获取到root根节点(路由注册保存时会以请求方法作为root根节点存储到trees中)
- 获取到root根节点后,是一个node结构体变量,调用node上的getValue()获取子节点,在getValue()方法中会根据node的nType节点类型属性等执行不同的匹配规则判断是否满足条件,如果满足则获取,最终会获取到所有满足条件的node封装一个nodeValue结构体变量,nodeValue有一个handlers属性,存储了当前接口调用需要执行的处理器包括中间件
- 遍历handlers,也就是处理器包括中间件,按照顺序开始执行