中间件原理
中间件(middleware),其原理就是对一个方法进行包裹装饰,然后返回同类型的方法,在Python中又名装饰器,甚至成为了Python的语法糖。
应用场景大多是需要对某一类函数进行通用的前置或者后置处理。
最常见的就是在web开发中,执行相应请求的handler函数前,需要对token合法性进行校验,在handler执行完后需要对处理结果产生的err进行记录和上报,这些都是通用的逻辑,并不需要在每个handler中都编写一遍。
实现一个最简单也最经典的中间件例子,计算函数的执行时间、记录日志。
func Hi(writer http.ResponseWriter, request *http.Request){
time.Sleep(time.Second*1)
rsp:=map[string]interface{}{"errcode":0,"msg":"hi"}
rspByte,_:=json.Marshal(rsp)
writer.Write(rspByte)
}
//中间件
func TimeUse(next http.HandlerFunc)http.HandlerFunc{
return func(writer http.ResponseWriter, request *http.Request) {
start:=time.Now()
next(writer,request)
fmt.Printf("cost %f second",time.Since(start).Seconds())
}
}
func Log(next http.HandlerFunc)http.HandlerFunc{
return func(writer http.ResponseWriter, request *http.Request) {
next(writer,request)
log.Println("run success")
}
}
func main() {
mx:=http.NewServeMux()
mx.HandleFunc("/hi", Log(TimeUse(Hi)))
http.ListenAndServe(":8080",mx)
}
我们在需要使用中间件的地方嵌套包裹,就实现了中间件的效果。但是会发现,每当新增一个中间件都需要这样包裹,最后造成的代码是这样的。
当中间件数量多起来之后,这样的写法着实会令人头皮发麻,当然要优化一下。
初始化需要执行的中间件数组,使用for循环进行嵌套包裹,这样的好处就是需要改动一组使用相同中间件的函数时,只需要更改中间链数组就可以了,这也是比较通用的写法。
type middle func (next http.HandlerFunc)http.HandlerFunc
func addMiddle(h http.HandlerFunc,m ...middle)http.HandlerFunc{
for i:=len(m)-1;i>=0;i--{
h=m[i](h)
}
return h
}
func main() {
mx:=http.NewServeMux()
m1:=[]middle{TimeUse,Log}
mx.HandleFunc("/hi", addMiddle(Hi,m1...))
mx.HandleFunc("/hi2", addMiddle(Hi1,m1...))
mx.HandleFunc("/hi3", addMiddle(Hi2,m1...))
mx.HandleFunc("/hi4", addMiddle(Hi3,m1...))
http.ListenAndServe(":8080",mx)
}
采用倒序包裹是为了对应添加中间件顺序,越往后添加的中间件越贴近需要执行的逻辑。
这个过程可以想象成以下的模型,其实就是典型的递归调用。
gin的中间件
gin初始化一个handler,并添加中间件。
func test03() {
r := gin.Default()
r.Handle("GET", "/hello", func(context *gin.Context) {
fmt.Println("one")
context.Next()
fmt.Println("one-back")
}, func(context *gin.Context) {
fmt.Println("myHandler")
})
r.Run(":8080")
}
func main() {
test03()
}
查看handle源码,添加的中间件最终是与处理逻辑handler一起组装成了HandlersChain,毕竟他们本质上一样的,都是HandlerFunc,只不过业务逻辑是最后一个执行的handlerFunc。
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
// 计算绝对路径
absolutePath := group.calculateAbsolutePath(relativePath)
// 将handler与原有的合并,组成新的HandlersChain
handlers = group.combineHandlers(handlers)
// 注册到全局路由树中
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
//合并处理链
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
在gin处理http请求的SeverHttp()中,我们可以看到,根据本次请求的路径从路由树中获得本次对应的HandlersChain,然后赋值给ctx.Handlers,接着执行ctx.Next()。
// Find route in tree
value := root.getValue(rPath, c.Params, unescape)
if value.handlers != nil {
//赋值给context
c.handlers = value.handlers
c.Params = value.params
c.fullPath = value.fullPath
//开始执行context的handlerChain
c.Next()
c.writermem.WriteHeaderNow()
return
}
为了方便分析ctx的执行原理,这里模仿gin的context结构模仿定义了一个简化的GinContext,重点关注next()函数,index需要初始化为-1
type Handler func(gc *GContext)
type HanclerChain []Handler
type GContext struct {
Handlers HanclerChain
index int
}
func (g *GContext) Next() {
g.index++
for g.index < len(g.Handlers) {
g.Handlers[g.index](g)
g.index++
}
}
func (g *GContext) Handle(hs ...Handler) {
g.Handlers = append(g.Handlers, hs...)
}
func (g *GContext) Start() {
g.Next()
}
func main() {
gc := &GContext{
index: -1,
Handlers: HanclerChain{},
}
gc.Handle(func(gc *GContext) {
fmt.Println("one")
gc.Next()
fmt.Println("one-back")
}, func(gc *GContext) {
fmt.Println("two")
gc.Next()
fmt.Println("two-back")
}, func(gc *GContext) {
fmt.Println("three")
gc.Next()
fmt.Println("three-back")
}, func(gc *GContext) {
fmt.Println("four")
//gc.Next()
fmt.Println("four-back")
})
gc.Start()
}
运行结果是
我们在编写gin的中间件时,如果需要后置处理,是需要执行context.Next()的,很显然,这是一个递归调用,只是通过串联context,使中间件可以主动把握递归调用下一层的时机,甚至中止处理链的继续执行,如果没有调用next(),则在本次handler执行结束后直接执行下一个。中间件的一般流程是,先调用前置动作,使函数往下执行,往上返回,然后执行后置动作。
下面是GinContext的两个执行模型。分别对应中间件中使用Next()和不使用Next()。
通过对gin源码的分析,可以看出gin.Context的设计还是比较巧妙的,context在递归调用过程中很好的发挥了上下文的作用,穿针引线,携带着全局的请求信息,暴露向下层调用的api。