本篇是mygin的第六篇,参照gin框架,感兴趣的可以从 Mygin第一篇 开始看,Mygin从零开始完全手写,在实现的同时,带你一窥gin框架的核心原理实现。
目的
- 实现中间件Middleware
在上一篇 Mygin实现分组路由Group 中,实现了路由分组,且同一分组的执行,会先执行Group,有一点点中间件的雏形了。但是中间件不完全还应该提供中断功能,比如一个Group组中添加了auth鉴权中间件,只有auth认证通过才可以通过,因此需要对上篇中的内容进行一些修改。
在实现之前,先分析gin中是怎样去实现的这一功能的
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中对应的代码也很简单。
const abortIndex int8 = math.MaxInt8 >> 1
//中间件执行失败,中断方法
func (c *Context) Abort() {
c.index = abortIndex
}
因此只需在mygin/content.go中新加Next方法和Abort方法
上下文
content.go中的代码不多,索性加上注释全部贴出来。
- mygin/content.go
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的修改了,原来的解决方法是直接循环调用,对应的代码如下:
for _, handler := range handlers {
handler(&Context{
Request: r,
Writer: w,
Params: params,
})
}
引擎
- mygin/engine.go
现在找到engine.go文件中将上面的代码替换为:
//实例化一个下上文
c := &Context{
Request: r,
Writer: w,
Params: params,
}
// 执行处理函数链
c.Next(handlers)
测试代码
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测试
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/!
看到上诉输出,即为成功。