Gin(五)给 Engine …不皮了,来看 RouterGroup

内容纲要

前情提要

Gin(四)给 Engine 摸摸骨

前篇文章我们分析了 trees 类型的结构体和 node 结构体的构成和方法。从 node 的函数 方法里我们看到了一个关键的影子:RouterGroup。

RouterGroup

首先看一波源码 ↓

源码

这段代码适合倒着看😂……

// IRouter defines all router handle interface includes single and group router.
type IRouter interface {
    IRoutes
    Group(string, ...HandlerFunc) *RouterGroup
}

// IRoutes defines all router handle interface.
type IRoutes interface {
    Use(...HandlerFunc) IRoutes

    Handle(string, string, ...HandlerFunc) IRoutes
    Any(string, ...HandlerFunc) IRoutes
    GET(string, ...HandlerFunc) IRoutes
    POST(string, ...HandlerFunc) IRoutes
    DELETE(string, ...HandlerFunc) IRoutes
    PATCH(string, ...HandlerFunc) IRoutes
    PUT(string, ...HandlerFunc) IRoutes
    OPTIONS(string, ...HandlerFunc) IRoutes
    HEAD(string, ...HandlerFunc) IRoutes

    StaticFile(string, string) IRoutes
    Static(string, string) IRoutes
    StaticFS(string, http.FileSystem) IRoutes
}

// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
    Handlers HandlersChain
    basePath string
    engine   *Engine
    root     bool
}

var _ IRouter = &RouterGroup{}

源码解读(说好倒着看就倒着看):

  • 第 35 行(var _ IRouter = &RouterGroup{}
    • 从段代码我们能非常直观的看到 RouterGroup 是一个实现了 IRouter 接口的结构体
      • 结论就是 RouterGroup 拥有 IRouter 里定义的所有函数方法
  • 第 28~32 行(type RouterGroup struct {...}
    • RouterGroup 的结构体定义(4 个变量)
      • Handlers 老熟人:应该是存储了某组函数方法
        • 从后面的源码中可以知道是存储了中间件(middleware)相关的函数方法
      • basePath:从变量名字结合结构体名字,可以推断出是这个路由组的基础路径😂
      • engine:Engine 跟 RouterGroup 真是你中有我,我中有你(待会就扒开你们的秘密)
      • root:一个 bool 型变量,很明显是标记这个 Group 是不是 root Group 的
  • 第 8~24 行(type IRoutes interface {...})(结合 RouterGroup 的接口实现看看)
    • 一组非常眼熟的东西(GET、POST、PUT……),有机智的小伙伴们已经能够联想到官方 HelloWorld Demo 里的 GET、POST……了,不多介绍(如果看到这些函数完全没想法的话,可能 web 基础还不是很牢靠,建议先不看这系列文章,先学会用,再深入了解原理,这样会比较好)
      • GET(实现都差不多,随便挑一个看)
        • 实现代码只有一句(return group.handle(http.MethodGet, relativePath, handlers)
          • 出现了实现核心 group.handle( 注意是小写的,不是接口里那个大写的 Handle)
            • handle 的实现里出现了一句有意思的代码group.engine.addRoute(httpMethod, absolutePath, handlers)
              • 再跟过去看看,就看到了root := engine.trees.get(method)root.addRoute(path, handlers)
                • 一目了然,RouterGroup 的 GET 就是把路径、请求处理函数方法加入到 Engine 的 trees 的 node 里
    • 几个不太眼熟的东西()
      • Use:中间件相关的函数,把中间件处理函数加到 RouterGroup 的 Handlers 里
      • Handle:从注释和代码里可以知道是支持批量添加请求路由的方法
      • Any:All In One 模式(调一个 Any 就等于一个路径加了所有(9 个)的请求方法的支持)
      • StaticFile:给单个静态文件夹路由用的(注释里还加了使用 example:router.StaticFile("favicon.ico", "./resources/favicon.ico")
      • Static:调用了下面的 StaticFS(使用了 gin 自带的 FileSystem 的 Dir,即 http.FileServer),给静态文件夹加路由用的(example:router.Static("/static", "/var/www")
      • StaticFS:功能同上,支持自定义文件系统(需要实现 http.FileSystem 接口)
  • 第 2~5 行
    • 在 IRouters 的基础上增加了一个 Group 接口(完全是为了 RouterGroup 写的)
      • Group:创建一个新的路由组

Engine 跟 RouterGroup 的关系

由 IRouter 可以看出,RouterGroup 的主要功能有 3 个

  • 对 Engine 的 trees(路由树) 进行管理
  • 对中间件进行管
  • 对路由分组进行管理

由 RouterGroup 的实现代码可看出,它单独存在基本没有什么意义,它是一个专属于 Engine 的抽象抽象层,主要用于管理 Engine 的路由。

  • 由于 trees 的实体在 Engine 里,RouterGroup 要操作 trees,需要通过 Engine,所以 RouterGroup 里始终会有一个指向 Engine 实例的指针变量
  • 而 RouterGroup 的 Group 的接口和 root 变量的设计,则赋予了它为路由分组的能力

工作流程

要看工作流程最快的方法就是写一个 Demo,然后 Debug 它。

先来看一个最基础的用法:

package main

import "github.com/gin-gonic/gin"

func main()  {
    engine := gin.Default()

    r.GET("/hello/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "world",
        })
    })

    engine.Run()  // 监听并在 0.0.0.0:8080 上启动服务
}

经过 Debug 跟踪,我们知道一个路由注册流程是(其实前面看 GET 代码的时候已经看的差不多了):

  • Engine 实例(已知 Engine 包含 RouterGroup)调用 GET,传递请求路径、响应函数方法
    • GET 调用 group.handle,
      • 并通过自身(Engine 里的 RouterGroup)的 Engine 指针,调用了 Engine 的 addRouter
        • 把请求路径、响应方法加入到 trees 的相应的 root 里

请求响应流程:

  • 客户端请求服务器
    • HTTP 服务引擎 http.Server 接收到请求,并初步处理成 http.ResponseWriter 和 *http.Request 并传递给注册过的上层请求处理 Handler(实现 ServerHTTP 接口,并注册到 http.ListenAndServe 的 Handler)(即 Gin 的 Engine)
      • Engine 把请求数据放入 Context pool 中,并传递给 Engine 的 handleHTTPRequest 进行处理
        • handleHTTPRequest 从 trees 中查找对应的 node,并回调注册过的请求处理 Handler

总结

至此,Engine 的整体设计、调度流程已经基本理清了。整体实现精简而不失巧妙,不愧为目前最流行的 Web 框架之一。

流程、架构图

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注