关键结构
-
Context
贯穿整个HTTP业务当中,为相关业务提供上下文服务-
WriteResponse
是Gin自己定义HTTPResponseWriter
接口,除了包含http.ResponseWriter
外,额外增加了一些常用的接口。 -
handles
包含了使用者自定义的业务和一些中间件(Gin
默认会使用日志中间件和崩溃恢复中间件) -
Cache
包含着QueryCaChe
和FormCache
,一个是读url的param参数,一个是读form表单参数 -
Param
是使用者定义的url路径参数,例如 /user/:id -
*Engine
绑定Engine
-
-
Engine
是Gin
的引擎,把上下文和路由联合起来-
RouterGroup
实现了一系列给Engine
添加路由的方法,方便使用 -
sync.Pool
是复用池的概念,目的是减少GC带来的影响,用法是先创建再放入,不能理解为线程池或数据库连接池的概念,因为会随时回收,是编程人员不可控的。 -
trees
是Gin
实现快速路由匹配的利器,是基于Trie
的算法思想进行路由匹配的,:param
和*
的实现都是由它完成的,有兴趣的同学可以参考下我的上一篇文章字典树-Trie
-
常用方法
初始化
- 初始化Engine
中间件
- 将添加的中间件加入到Handles数组里面,形成调用链
创建路由
- 创建一个
Get
方法并放入trees
中,用于运行中的匹配
启动服务
- 调用
http
库中的监听函数
调用过程
-
Gin
自己实现http.Handler
接口,从trees
中去到注册进去的业务,进行链式调用
中间件
这里我们看到tree中路由对应的是HandlersChain,实际就是[]HandlerFunc,所以一个路由,实际上会对应多个handlers。
首先我们已经把request和responseWriter封装在context里面了,多个handler只要处理好这个context就可以了,所以是可以一个路由拥有多个handler的。
其次这里的handler是怎么来的呢?
每个路由的handler有几个来源,第一个来源是在engine.GET的时候调用增加的。第二个来源是RouterGroup.GET的时候增加的,其实这两种方式都是调用
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers)
}
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()
}
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
从两个copy的顺序可以看出,group的handler高于自定义的handler。这里自定义的handler可以是多个,比如:
router.GET("/before", MiddleWare(), func(c *gin.Context) {
request := c.MustGet("request").(string)
c.JSON(http.StatusOK, gin.H{
"middile_request": request,
})
})
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("before middleware")
c.Set("request", "clinet_request")
c.Next()
fmt.Println("before middleware")
}
}
这里的/before实际上是带了两个handler。
第三种方法是使用Use增加中间件的方式:
router.Use(MiddleWare())
这里的会把这个中间件(实际上也是一个handler)存放到routerRroup上。所以中间件是属于groupHandlers的。
在请求进来的时候是如何调用的呢?
答案还是在handleHTTPRequest中
func (engine *Engine) handleHTTPRequest(c *Context) {
...
handlers, params, tsr := root.getValue(path, c.Params, unescape)
if handlers != nil {
c.handlers = handlers
c.Params = params
c.Next()
c.writermem.WriteHeaderNow()
return
}
..
}
func (c *Context) Next() {
c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
每个请求进来,匹配好路由之后,会获取这个路由最终combine的handlers,把它放在全局的context中,然后通过调用context.Next()来进行递归调用这个handlers。
想一想,就算是链式调用,Gin
的Logger
记录时间是如何做到的?在执行完自己的业务是如何终止计时的呢?
其实GoLang
就有这种特性,想一想defer
是怎么做的。
是的,就是利用栈的特性:先进后出
func(c *lightGin.Context) {
// Start timer
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
c.Next()
// Log only when path is not being skipped
if _, ok := skip[path]; !ok {
param := LogFormatterParams{
Request: c.Request,
isTerm: isTerm,
Keys: c.Keys,
}
// Stop timer
param.TimeStamp = time.Now()
param.Latency = param.TimeStamp.Sub(start)
param.ClientIP = c.ClientIP()
param.Method = c.Request.Method
param.StatusCode = c.Writer.Status()
param.ErrorMessage = c.Errors.ByType(lightGin.ErrorTypePrivate).String()
param.BodySize = c.Writer.Size()
if raw != "" {
path = path + "?" + raw
}
param.Path = path
fmt.Fprint(out, formatter(param))
}
}
参考&致谢
有疑问加站长微信联系(非本文作者)