我相信开发过 WEB 项目, 那一定都接触过中间件,不管你用 beego 还是 gin。
你是否有思考过,中间件是怎么实现的么?
这篇文章就带你一起去了解中间件的实现原理。
咯,给你切一个我家的洋葱。
中间件和洋葱有啥关系?
你请仔细看他的横切面,是不是特别有意思,一个圈套一圈的。
这其实就是我们中间件的实现思路。
最里面的芯就是我们的路由方法,然后外面的一圈一圈的就是中间件。
当用户请求过来的时候,就从最外面,一层一层的往里面剥洋葱。
是不是觉得非常有意思,看来以后做程序员要多下厨了,说不定接下来你就会发现还有茄子模型,黄花模型,哈哈,开玩笑。
我们首先来定义一个结构体:
type Context struct {
handlers []func(c *Context)
index int8
}
结构体名字你随意,我就取 Context 了。
里面有两个关键的元素 handlers 和 index;handlers用来存储我们一层一层的洋葱;index 用来存储我们当前剥到第几层了。
so,我们的路由控制器方法去哪里了?
其实我们的路由控制器方法也是属于洋葱的一层,只是我们到他那一层就不剥了。
所以路由控制器方法,也是中间件方法。
用过 Gin 的朋友,就一定不会对 Use 方法陌生,他是添加中间件的方法。
于是我们也来模拟下这个方法:
func (this *Context) Use(f func(c *Context)) {
this.handlers = append(this.handlers, f)
}
就是往我们的 handlers 数组里面添加方法。
在中间件里面,经常会用到 next 方法,干啥用的?
就是往下剥一层洋葱,所以,就这样去实现:
func (this *Context) Next() {
this.index++
this.handlers[this.index](this)
}
我们再来模拟一个添加路由控制器的方法:
func (this *Context) GET(path string, f func(c *Context)) {
this.handlers = append(this.handlers, f)
}
我这里直接忽略了 path 这个变量,真实项目中,这个变量肯定是有用的。
最后我们开始剥洋葱:
func (this *Context) Run() { //剥开第一层
this.handlers[0](this)
}
我们的模型建立好了,现在开始享用:
func main() {
c := &Context{}
c.Use(func(c *Context) {
fmt.Println("mid1 start")
c.Next()
fmt.Println("mid1 end")
})
c.Use(func(c *Context) {
fmt.Println("mid2 start")
c.Next()
fmt.Println("mid2 end")
})
c.GET("/", func(c *Context) {
fmt.Println("GET handler func")
})
c.Run()
}
这段代码是不是和我们的 Gin 非常相似。
执行之后就能得到结果:
$ go run main.go
mid1 start
mid2 start
GET handler func
mid2 end
mid1 end
最后再画一个更加形象的图吧!