使用echo框架可以方便的定义自己的中间件,这里研究下echo中间件的实现以及是如何实现链式调用的。
如果有表意不清或者错误的地方,请联系我纠正。
比如我们有下面的中间件:

该中间件计算了请求处理时间,CalHandleTime仅仅返回的是一个echo.HandlerFunc方法,此时HandlerFunc方法中的代码并没有被执行。
HandlerFunc的定义如下:

HandlerFunc func(Context) error

中间件方法(MiddlewareFunc)的定义就是传入一个HandlerFunc,然后返回一个HandlerFunc:

MiddlewareFunc func(HandlerFunc) HandlerFunc

可以看下e.Use()的实现,就是把我们的中间件方法添加到中间件数组中,供后续调用:

func (e *Echo) Use(middleware ...MiddlewareFunc) {e.middleware = append(e.middleware, middleware...)
}

我们定义自己的路由,然后使用这个中间件:

func main() {e := echo.New()e.GET("/hello", func(ctx echo.Context) error {return ctx.String(http.StatusOK, "hello world~!")}, CalHandleTime)
}

我们定义了一个Get方法的路由,Get方法第二个参数是具体的处理方法,我们简单返回hello world,这个处理方法也是一个echo.HandlerFunc,可以发现是跟中间件返回的方法类型是一致的。Get方法后面的参数都是中间件参数,执行顺序跟传入顺序一样。
我们可以看下Get方法的实现:

func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {name := handlerName(handler)e.router.Add(method, path, func(c Context) error {h := handler// 中间件调用链,这里串联起来了,合并成了一个方法。for i := len(middleware) - 1; i >= 0; i-- {h = middleware[i](h)}return h(c)})r := &Route{Method:  method,Path:    path,Handler: name,}e.router.routes[method+path] = r
}

我们主要看中间件部分的实现:

e.router.Add(method, path, func(c Context) error {h := handler// 中间件调用链,这里串联起来了,合并成了一个方法。for i := len(middleware) - 1; i >= 0; i-- {h = middleware[i](h)}return h(c)})
  • handler是我们传入的处理业务的方法,就是返回hello world的那段代码,是echo.HandlerFunc类型,middleware是我们传入的中间件方法,可能有多个,所以这里遍历所有的,然后关联起来,并且要保证顺序。
  • middleware类型是echo.MiddlewareFunc,可以看之前的定义,echo.MiddlewareFunc入参出参也都是echo.HandleFunc,跟我们处理业务逻辑的方法是一个类型,所以可以将业务处理方法和多个中间件方法返回的echo.HandlerFunc合并成一个方法。
  • 可以看到中间件方法入参是(next echo.HandlerFunc),也就是下一个echo.HandlerFunc,然后返回一个新的echo.HandlerFunc方法,新的方法封装了自己的逻辑,然后再调用next echo.HandlerFunc,所以最后调用的中间件方法,里面返回的echo.HandlerFunc会先执行。
  • 可以看到遍历中间件时采用了逆序的遍历,也是因为这个原因,这样可以保证第一个定义的中间件方法中的echo.HandlerFunc会先执行,然后按照中间件定义的顺序依次执行,最后执行我们的业务处理方法。这样就合并成了一个方法,当请求到来时,会执行这个方法。