发布于:,更新于:

06.Mygin实现中间件Middleware

本篇是mygin的第六篇,参照gin框架,感兴趣的可以从 Mygin第一篇 开始看,Mygin从零开始完全手写,在实现的同时,带你一窥gin框架的核心原理实现。

目的

  • 实现中间件Middleware
    在上一篇 Mygin实现分组路由Group 中,实现了路由分组,且同一分组的执行,会先执行Group,有一点点中间件的雏形了。但是中间件不完全还应该提供中断功能,比如一个Group组中添加了auth鉴权中间件,只有auth认证通过才可以通过,因此需要对上篇中的内容进行一些修改。
    在实现之前,先分析gin中是怎样去实现的这一功能的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    func (c *Context) Next() {
    c.index++
    //遍历handlers
    for c.index < int8(len(c.handlers)) {
    //真正调用执行handler方法
    c.handlers[c.index](c)
    c.index++
    }
    }
    这个时候就有疑问了,从上述方法中看不到中间件执行失败的中断方法,那又是怎么实现中断。
    在揭晓答案之前,先看看int8(len(c.handlers) 为什么要写个int8,原因在于gin中规定的handlers最多63个,相信实际的应用请求中,没有超过63个那么多变态的执行链。在gin中如果某一中间件执行失败,就把c.index赋值为63,上述for循环就不满足条件,因此就跳出for循环,不再继续执行后面的代码。gin中对应的代码也很简单。
    1
    2
    3
    4
    5
    const abortIndex int8 = math.MaxInt8 >> 1
    //中间件执行失败,中断方法
    func (c *Context) Abort() {
    c.index = abortIndex
    }
    因此只需在mygin/content.go中新加Next方法和Abort方法

上下文

content.go中的代码不多,索性加上注释全部贴出来。

  • mygin/content.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
    package mygin

    import (
    "encoding/json"
    "math"
    "net/http"
    )

    // 定义 表示最大和上下文应中止时的索引值
    const abortIndex int8 = math.MaxInt8 >> 1

    // Context 封装了一个HTTP请求的上下文
    type Context struct {
    Request *http.Request
    Writer http.ResponseWriter
    Params Params
    index int8
    }

    // Next 执行链中的剩余处理程序。
    func (c *Context) Next(handlers HandlersChain) {
    //遍历handlers
    for c.index < int8(len(handlers)) {
    //真正调用执行handler方法
    handlers[c.index](c)
    c.index++
    }
    }

    // Abort 中断链中剩余处理程序的执行。
    func (c *Context) Abort() {
    c.index = abortIndex
    }

    // IsAborted 如果当前上下文被中止,则返回true。
    func (c *Context) IsAborted() bool {
    return c.index >= abortIndex
    }

    // writeContentType 如果尚未设置,则设置Content-Type标头。
    func writeContentType(w http.ResponseWriter, value []string) {
    header := w.Header()
    if val := header["Content-Type"]; len(val) == 0 {
    header["Content-Type"] = value
    }

    }

    // Status 设置HTTP响应状态码。
    func (c *Context) Status(code int) {
    c.Writer.WriteHeader(code)
    }

    // JSON 将值序列化为JSON并将其写入响应。
    func (c *Context) JSON(v interface{}) error {
    writeContentType(c.Writer, []string{"application/json; charset=utf-8"})
    encoder := json.NewEncoder(c.Writer)
    err := encoder.Encode(v)
    if err != nil {
    c.Status(http.StatusInternalServerError)
    }
    c.Status(http.StatusOK)
    return err
    }

    // Html 将字符串以HTML形式写入响应。
    func (c *Context) Html(v string) error {
    writeContentType(c.Writer, []string{"text/html; charset=utf-8"})
    c.Status(http.StatusOK)
    _, err := c.Writer.Write([]byte(v))
    return err
    }

    // String 将字符串写入响应
    func (c *Context) String(v string) error {
    writeContentType(c.Writer, []string{"text/plain; charset=utf-8"})
    c.Status(http.StatusOK)
    _, err := c.Writer.Write([]byte(v))
    return err
    }

    接下来就是调用handles的修改了,原来的解决方法是直接循环调用,对应的代码如下:
    1
    2
    3
    4
    5
    6
    7
    for _, handler := range handlers {
    handler(&Context{
    Request: r,
    Writer: w,
    Params: params,
    })
    }

引擎

  • mygin/engine.go
    现在找到engine.go文件中将上面的代码替换为:
    1
    2
    3
    4
    5
    6
    7
    8
    //实例化一个下上文
    c := &Context{
    Request: r,
    Writer: w,
    Params: params,
    }
    // 执行处理函数链
    c.Next(handlers)

测试代码

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
package main

import (
"gophp/mygin"
"path"
)

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

//测试Abort
group := r.Group("/api", func(context *mygin.Context) {
//todo....
context.String("api Group 中间件失败了....\n")
context.Abort()
})
//这个回调不会执行
group.GET("/hello/:name", func(context *mygin.Context) {
name := context.Params.ByName("name")
context.String(path.Join("hello ", name, "!"))
})

//测试没有发生Abort
group2 := r.Group("/api2", func(context *mygin.Context) {
//todo....
context.String("api Group 中间件成功了....\n")
})

//这个回调会执行
group2.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
 curl http://127.0.0.1:8088/api/hello/scott
api Group 中间件失败了....
~ curl http://127.0.0.1:8088/api2/hello2/scott
api Group 中间件成功了....
hello2 /scott/!

看到上诉输出,即为成功。赶快去试试吧!




本站由 [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/) 许可协议,转载请注明出处。