思路
带着问题去分析,从如下思路来分析gin的处理流程
定义路由
gin的中间件使用如下方法添加
// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
gin的group除了以上方法,还可以在初始化时传入
// 1、使用初始化时传入
group1:=g.Group("/g1", func(c *gin.Context) {
log.Println("before group1 middleware1")
c.Next()
log.Println("after group1 middleware1")
})
// 2、使用use添加
group1.Use(func(context *gin.Context) {
log.Println("before group1 middleware2")
c.Next()
log.Println("after group1 middleware3")
})
如下添加我们的业务处理逻辑,可以看到这里使用的也是 func(c *gin.Context) 签名,与中间件使用的签名一致。
g.Handle("GET", "/h1", func(c *gin.Context) {
c.JSON(http.StatusOK, Response{
Code: 10000,
Msg: "this is h1",
Data: struct {
}{},
})
})
通过层层查看group.Handle的源码,最终我们看到一个 combineHandler 方法
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
combineHandlers方法 ,将我们之前添加到 group.handlers 的中间件 handlerFunc 与我们最终在 Handle 里添加的包含业务逻辑的 handleFunc 组合在一起。
换个说法,就是通过 group.handle 添加的业务逻辑处理函数,都会与 group.handlers 最终组合成处理链,然后与路径relativePath一起,加入到路由中。每一个请求,最终执行的是处理链。
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
处理请求
了解了处理逻辑最终是怎样添加到gin的路由中的,那当前端发起api访问,gin收到一个request请求时,如何执行中间件和最终的业务处理逻辑的呢?
gin的启动
err:=g.Run(":8080")
if err != nil {
log.Fatal(err)
}
查看gin.Run的源码,其实就是对 http.ListenAndServe 的封装,熟悉net/http包的都知道,只要实现了 http.Handler 接口,即可处理网络请求。
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}
进一步查看gin的 serverHttp方法实现,可以看到最终是使用 engine.handleHTTPRequest(c) 对请求request进行处理。另外,在serverHttp 的方法里,将http.ResponseWriter,*http.Request封装到了gin自定义的Context中,供后续的流程使用。这就是 func(c *gin.Context) 签名中 Context 的来源。
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 这里获得的context对象会进行重置,清除上一请求的属性
// 从sync.pool中获得context对象
c := engine.pool.Get().(*Context)
// 重设http.ResponseWriter
c.writermem.reset(w)
// 重设*http.Request
c.Request = req
// context对象重置
c.reset()
// 进行本次请求的处理
engine.handleHTTPRequest(c)
// 将context对象放进sync.Pool复用
engine.pool.Put(c)
}
重点从22行代码开始,通过解析出Request的方法和路径,从路由中获得先前添加到路由中的处理链handlers,
然后赋值给 Context,接着执行c.Next(),开始递归执行中间件和自定义的业务处理逻辑。
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
}
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
handlers, params, tsr := root.getValue(rPath, c.Params, unescape)
if handlers != nil {
// 处理链赋值给context实例
c.handlers = handlers
c.Params = params
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && rPath != "/" {
if 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 handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
c.Next(),其实就是按照顺序执行handlerChain即注册到路由中的处理链。
总结
- gin的中间件和自定义的处理函数,都是func(c *gin.Context) 签名。
- 路由中最终保存的是带有中间件的处理链handlers。
- gin的Context包含了handlers处理链,http的ResponseWriter和Request,封装了获取参数以及返回接口的方法,贯穿整个处理流程。