发布于:,更新于:

05.Mygin实现分组路由Group

本篇是Mygin第五篇

目的

  • 实现路由分组

为什么要分组

分组控制(Group Control)是 Web 框架应该提供的基础功能之一,对同一模块功能的开发,应该有相同的前缀。或者对一部分第三方接口,统一需要加解密等功能。分组后很方便。例如:

  • 对于任务模块,统一前缀为/task
  • 除去/user/login接口,都需要鉴权
  • 以/openapi 开头的接口,需要对接第三方平台,需要三方平台鉴权

大多数分组都是统一前缀,正确的分组可以实现子分组,无限极往下分组,当然实际情况下也不会有太多层分组。每个分组有不同的中间件(middleware),分组与子分组就像洋葱一样,一层一层往内。要想往内,需要拨开最外层,也就是要执行外层的中间件才能往内。对于分组,也有后续扩展的好处,比如/task分组,现在要统一加上访问日志记录,有了分组后就可以在该分组上添加一个中间件就可以了。

分组嵌套

在实现分组嵌套之前,先看看gin中的分组嵌套是怎么实现的。gin中的分组中含有Engin的指针,其实很好理解,因为分组要有访问Router的能力。Engin中继承了RouterGroup,实例化Engin后就可以使用Group的功能。有了Engin的指针,整个框架的所有资源都是由Engine去统一调度的,因此通过Engine可以间接地拥有整个框架的功能。你中有我我中有你。

  • gin/routergroup.go
    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    package mygin

    import (
    "net/http"
    "path"
    "regexp"
    )

    // IRoutes 定义了路由组的接口
    type IRoutes interface {
    BasePath() string
    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
    Match([]string, string, ...HandlerFunc) IRoutes
    }

    // anyMethods 包含所有 HTTP 方法的字符串表示
    var anyMethods = []string{
    http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
    http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
    http.MethodTrace,
    }

    // RouterGroup 定义了路由组的结构体
    type RouterGroup struct {
    Handlers HandlersChain // 路由组的中间件处理函数链
    basePath string // 路由组的基础路径
    engine *Engine // 路由组所属的引擎
    root bool // 是否是根路由组
    }

    // Group 创建一个新的路由组
    func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
    return &RouterGroup{
    Handlers: append(group.Handlers, handlers...),
    basePath: path.Join(group.basePath, relativePath),
    engine: group.engine,
    }
    }

    // BasePath 返回路由组的基础路径
    func (group *RouterGroup) BasePath() string {
    return group.basePath
    }

    // handle 处理路由,将路由信息添加到引擎中
    func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := path.Join(group.basePath, relativePath)
    //处理函数,Group组定义的函数先执行,自定义的函数后执行
    handlers = append(group.Handlers, handlers...)
    group.engine.addRoute(httpMethod, absolutePath, handlers)

    if group.root {
    return group.engine
    }
    return group
    }

    // Handle 校验 HTTP 方法的有效性,并处理路由
    func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
    // 检查 HTTP 方法的有效性
    if match := regexp.MustCompile("^[A-Z]+$").MatchString(httpMethod); !match {
    panic("http method " + httpMethod + " is not valid")
    }
    // 处理路由
    return group.handle(httpMethod, relativePath, handlers)
    }

    // GET 注册 GET 方法的路由
    func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodGet, relativePath, handlers)
    }

    // POST 注册 POST 方法的路由
    func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPost, relativePath, handlers)
    }

    // DELETE 注册 DELETE 方法的路由
    func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodDelete, relativePath, handlers)
    }

    // PATCH 注册 PATCH 方法的路由
    func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPatch, relativePath, handlers)
    }

    // PUT 注册 PUT 方法的路由
    func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPut, relativePath, handlers)
    }

    // OPTIONS 注册 OPTIONS 方法的路由
    func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodOptions, relativePath, handlers)
    }

    // HEAD 注册 HEAD 方法的路由
    func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodHead, relativePath, handlers)
    }

    // Match 注册多个方法的路由
    func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes {
    for _, method := range methods {
    group.handle(method, relativePath, handlers)
    }

    if group.root {
    return group.engine
    }
    return group
    }

    // Any 注册所有方法的路由
    func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
    for _, method := range anyMethods {
    group.handle(method, relativePath, handlers)
    }

    if group.root {
    return group.engine
    }
    return group
    }

    可以看到group.engine.addRoute方法,实际调用的仍然是mygin/router.go中的addRoute方法。当group.root为true,也就是为根时,相当于没有调用Group方法,返回group.engine,也就是相当于直接调用的engine,只有当调用Group方法后才会返回group。
    接下来看engine的修改
  • mygin/engine.go
    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    package mygin

    import (
    "net/http"
    )

    // HandlerFunc 定义处理函数类型
    type HandlerFunc func(*Context)

    // HandlersChain 定义处理函数链类型
    type HandlersChain []HandlerFunc

    // Engine 定义引擎结构,包含路由器
    type Engine struct {
    Router
    RouterGroup
    }

    // ServeHTTP 实现http.Handler接口的方法,用于处理HTTP请求
    func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 获取对应HTTP方法的路由树的根节点
    root := e.trees.get(r.Method)
    // 解析请求路径
    parts := root.parseFullPath(r.URL.Path)

    // 查找符合条件的节点
    searchNode := make([]*node, 0)
    root.search(parts, &searchNode)

    // 没有匹配到路由
    if len(searchNode) == 0 {
    w.Write([]byte("404 Not found!\n"))
    return
    }

    // 参数赋值
    params := make([]Param, 0)
    searchPath := root.parseFullPath(searchNode[0].fullPath)
    for i, sp := range searchPath {
    if sp[0] == ':' {
    params = append(params, Param{
    Key: sp[1:],
    Value: parts[i],
    })
    }
    }

    // 获取处理函数链
    handlers := searchNode[0].handlers
    if handlers == nil {
    w.Write([]byte("404 Not found!\n"))
    return
    }

    // 执行处理函数链
    for _, handler := range handlers {
    handler(&Context{
    Request: r,
    Writer: w,
    Params: params,
    })
    }
    }

    // Default 返回一个默认的引擎实例
    func Default() *Engine {
    engine := &Engine{
    Router: Router{
    trees: make(methodTrees, 0, 9),
    },
    RouterGroup: RouterGroup{
    Handlers: nil,
    basePath: "/",
    root: true,
    },
    }

    // Group 保存 engine 的指针
    engine.RouterGroup.engine = engine

    return engine
    }

    // Run 启动HTTP服务器的方法
    func (e *Engine) Run(addr string) error {
    return http.ListenAndServe(addr, e)
    }
    可以仔细观察下addRoute函数,调用了group.engine.router.addRoute来实现了路由的映射。由于Engine从某种意义上继承了RouterGroup的所有属性和方法,因为 (*Engine).engine 是指向自己的。这样实现,我们既可以像原来一样添加路由,也可以通过分组添加路由。

最后来测试一下

  • main.go
    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
    package main

    import (
    "gophp/mygin"
    "path"
    )

    func main() {
    // 创建一个默认的 mygin 实例
    r := mygin.Default()

    group := r.Group("/api", func(context *mygin.Context) {
    context.String("api Group ....\n")
    })

    group.GET("/hello/:name", func(context *mygin.Context) {
    name := context.Params.ByName("name")
    context.String(path.Join("hello ", name, "!"))
    })

    group.GET("/hello2/:name", func(context *mygin.Context) {
    name := context.Params.ByName("name")
    context.String(path.Join("hello2 ", name, "!\n"))
    })

    // 启动服务器并监听端口
    r.Run(":8088")
    }

curl请求测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 curl -i http://localhost:8088/api/hello/scott
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 24 Jan 2024 05:16:45 GMT
Content-Length: 29

api Group ....
hello /scott/!
~ curl -i http://localhost:8088/api/hello2/scott
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 24 Jan 2024 05:16:54 GMT
Content-Length: 31

api Group ....
hello2 /scott/!

可以看到两个路由都安装预定的返回了,即先返回Group定义的,再返回路由定义的。




本站由 [PengBin](/) 使用 [Stellar 1.26.3](https://github.com/xaoxuu/hexo-theme-stellar/tree/1.26.3) 主题创建。 本博客所有文章除特别声明外,均采用 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 许可协议,转载请注明出处。