目录

【转载】gin介绍

介绍

  • gin 框架基于 httprouter 实现最重要的路由模块,采用类似字典树一样的数据结构来存储路由与handle方法的映射.也是框架高性能的原因,有兴趣的同学可以自行查阅
  • 本文提供 在线思维导图 搭配文章看事半功倍
  • Engine 容器对象,整个框架的基础
  • Engine.trees 负责存储路由和handle方法的映射,采用类似字典树的结构
  • Engine.RouterGroup,其中的Handlers存储着所有中间件
  • Context上下文对象,负责处理请求和回应,其中的handlers是存储处理请求时中间件和处理方法的

初始化容器

通过调用 gin.New() 方法来实例化Engine. 虽然参数很多,但我们只需要注意 RouterGroup ,treesengine.pool.New即可

  • engine.pool.New 负责创建Context对象,采用sync.Pool减少频繁context实例化带来的资源消耗,
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func New() *Engine {
  debugPrintWARNINGNew()
  engine := &Engine{
    //实例化RouterGroup,其中Handlers为中间件数组
    RouterGroup: RouterGroup{ 
      Handlers: nil,
      basePath: "/",
      root:     true,
    },
    FuncMap:                template.FuncMap{},
    RedirectTrailingSlash:  true,
    RedirectFixedPath:      false,
    HandleMethodNotAllowed: false,
    ForwardedByClientIP:    true,
    AppEngine:              defaultAppEngine,
    UseRawPath:             false,
    RemoveExtraSlash:       false,
    UnescapePathValues:     true,
    MaxMultipartMemory:     defaultMultipartMemory,
    //trees 是最重要的点!!!!负责存储路由和handle方法的映射,采用类似字典树的结构
    trees:                  make(methodTrees, 0, 9), 
    delims:                 render.Delims{Left: "{{", Right: "}}"},
    secureJsonPrefix:       "while(1);",
  }
  engine.RouterGroup.engine = engine
  //这里采用 sync/pool 实现context池,减少频繁context实例化带来的资源消耗
  engine.pool.New = func() interface{} {
    return engine.allocateContext()
  }
  return engine
}

注册中间件

gin的高性能主要依靠trees,每一个节点的内容你可以想象成一个key->value的字典树,key是路由,而value则是一个[]HandlerFunc,里面存储的就是按顺序执行的中间件和handle控制器方法,这里很重要,要考!

注册全局中间件

gin.use() 调用RouterGroup.Use()RouterGroup.Handlers写入记录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
  engine.RouterGroup.Use(middleware...) 
  engine.rebuild404Handlers() //注册404处理方法
  engine.rebuild405Handlers() //注册405处理方法
  return engine
}

//  其中`Handlers`字段就是一个数组,用来存储中间件
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
  group.Handlers = append(group.Handlers, middleware...)
  return group.returnObj()
}

注册路由组中间件

  • 通过 Group()方法返回一个 新生成的RouterGroup指针,用来分开每个路由组加载不一样的中间件
  • 注意这里的Handlers: group.combineHandlers(handlers),这行代码会复制一份全局中间件到新生成的RouterGroup.Handlers中,接下来路由注册的时候就可以一起写入树节点中
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
group := g.Group("/test_group")
group.Use(middleware.Test())
{
  //这里会最终路由和中间件以及handle方法一起写入树节点中
  group.GET("/test",handler.TestTool)
}

//返回一个RouterGroup指针
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
  return &RouterGroup{
    //复制一份全局中间件
    Handlers: group.combineHandlers(handlers),
    basePath: group.calculateAbsolutePath(relativePath),
    engine:   group.engine,
  }
}

注册路由以及中间件

不管哪种请求方式最终都会调用RouterGroup.handle,这个方法主要有两个作用

  • 处理路由的格式,将路由拼成 ‘/’ 字符开头的路由
  • 复制一份RouterGroup.Handlers,加上相应这次路由的handle方法,组成一个list放入树节点中
  • 最后调用trees.addRoute增加节点
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
g.GET("/test_tool", middleware.Test(),handler.TestTool)

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
  //根目录和路由结合起来,将路由拼成 '/' 字符开头的路由
  absolutePath := group.calculateAbsolutePath(relativePath) 
  //复制一份RouterGroup.Handlers,加上相应这次路由的handle方法,组成一个list放入树节点中
  handlers = group.combineHandlers(handlers) 
  group.engine.addRoute(httpMethod, absolutePath, handlers)
  return group.returnObj()
}

//调用 `trees`增加节点
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
  assert1(path[0] == '/', "path must begin with '/'")
  assert1(method != "", "HTTP method can not be empty")
  assert1(len(handlers) > 0, "there must be at least one handler")

  debugPrintRoute(method, path, handlers)
  root := engine.trees.get(method)
  if root == nil {
    root = new(node)
    root.fullPath = "/"
    engine.trees = append(engine.trees, methodTree{method: method, root: root})
  }
  root.addRoute(path, handlers)
}

启动

通过调用net/http来启动服务,由于engine实现了ServeHTTP方法,只需要直接传engine对象就可以完成初始化并启动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
g.Run()
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
}
func ListenAndServe(addr string, handler Handler) error {
  server := &Server{Addr: addr, Handler: handler}
  return server.ListenAndServe()
}
//来自 net/http 定义的接口,只要实现了这个接口就可以作为处理请求的函数
type Handler interface {
  ServeHTTP(ResponseWriter, *Request)
}
//实现了ServeHTTP方法
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(c *Context) 方法就好了
  • 通过请求方法和路由找到相对应的树节点,获取储存的[]HandlerFunc列表,通过调用c.Next()处理请求
  • 通过不停的移动下标递归,最后完成处理返回结果
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func (engine *Engine) handleHTTPRequest(c *Context) {
  ...
  // 
  t := engine.trees
  for i, tl := 0, len(t); i < tl; i++ {
    ...
    // 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
    }
    ...
  }
  ...
}

//这里挺巧妙的,通过不停的移动下标递归,最后完成处理返回结果
func (c *Context) Next() {
  c.index++
  for c.index < int8(len(c.handlers)) {
    c.handlers[c.index](c)
    c.index++
  }
}

参考文档

gin中文文档

gin项目地址

httprouter

全网最详细的gin源码解析