关键结构
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)) } }
参考&致谢
文章来源:智云一二三科技
文章标题:Gin浅析
文章地址:https://www.zhihuclub.com/7005.shtml
评论已关闭
saf.yahr.zhihuclub.com.nes.dw