为了更好的实现GoFast,我看了很多Golang的Web框架,包括比较流行的:Gin、Echo、iris、go-zero等。他们关于中间件的实现有很多相似的地方,但也有特色的地方,这里我们就重点分析一下他们的实现,顺便说说我的看法,最后介绍一下GoFast的实现。

Gin构造中间件数组

Gin框架是对每个路由节点单独构建一个中间件切片,请求来了之后按照顺序依次执行,中间有异常就中断执行链。

 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
router.POST("/loginJSON", func(c *gin.Context) {...}

func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodPost, relativePath, handlers)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := group.calculateAbsolutePath(relativePath)
    // 将路由处理函数合并到分组中间件切片中。
    // 这里看出,路由一旦加好,如果在改变group的中间件,对已加好的路由是无效的。
	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
}
     
// 当前路由加入路由树
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
	assert1(path[0] == '/', "path must begin with '/'")
	assert1(method != "", "HTTP method can not be empty")
	assert1(len(handlers) > 0, "there must be at least one handler")
    // ...
	root.addRoute(path, handlers)
    // ...
}

// 路由节点中的 handlers 记录的就是中间件函数,执行的时候依次执行。
type node struct {
	path      string
	indices   string
	wildChild bool
	nType     nodeType
	priority  uint32
	children  []*node
	handlers  []HandlerFunc
	fullPath  string
}

// 依次执行 handlers
func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

Iris构造中间件数组

 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
func (api *APIBuilder) Use(handlers ...context.Handler) {
	api.middleware = append(api.middleware, handlers...)
}

func (api *APIBuilder) UseGlobal(handlers ...context.Handler) {
	for _, r := range api.routes.routes {
		r.Use(handlers...) // prepend the handlers to the existing routes
	}
	// set as begin handlers for the next routes as well.
	api.beginGlobalHandlers = append(api.beginGlobalHandlers, handlers...)
}

// 每个路由可以有自己单独的 handlers 
type Route struct {
    // ...
	beginHandlers context.Handlers
	Handlers        context.Handlers `json:"-"`
	doneHandlers context.Handlers
    // ...
}

// Build每个Route节点的中间件函数,begin -> handler -> done
func (r *Route) BuildHandlers() {
	if len(r.beginHandlers) > 0 {
		r.Handlers = append(r.beginHandlers, r.Handlers...)
		r.beginHandlers = r.beginHandlers[0:0]
	}

	if len(r.doneHandlers) > 0 {
		r.Handlers = append(r.Handlers, r.doneHandlers...)
		r.doneHandlers = r.doneHandlers[0:0]
	}
}

// 启动server时候需要处理的动作。
func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error {
    // 先根据所有的中间件函数,Build路由树以及合并中间件
	if err := app.Build(); err != nil {
		app.logger.Error(err)
		return err
	}

	app.Configure(withOrWithout...)
	app.tryStartTunneling()

	err := serve(app)
	if err != nil {
		app.Logger().Error(err)
	}
    
	return err
}

// Iris 构造了 before -> handler -> done 的事件模型
func main() {
	app := iris.New()

	app.Use(before0)
	app.UseGlobal(before3)
	app.Done()
	app.DoneGlobal()

	app.Get("/", before, mainHandler, after).Done()
	app.Get("/cd", indexHandler)

	app.Use(before1)
	app.UseGlobal(before2)

	app.Run(iris.Addr(":8083"))
}

最后匹配到路由,然后执行中间件的方式和Gin是一样的,看下面匹配路由和执行中间件的代码就知道了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 匹配路由节点
n := t.search(path, ctx.Params())
if n != nil {
	ctx.SetCurrentRouteName(n.RouteName)
	ctx.Do(n.Handlers)
	return
}

// 和Gin一样,按索引顺序依次执行
func DefaultNext(ctx Context) {
	if ctx.IsStopped() {
		return
	}
	if n, handlers := ctx.HandlerIndex(-1)+1, ctx.Handlers(); n < len(handlers) {
		ctx.HandlerIndex(n)
		handlers[n](ctx)
	}
}

iris框架还有一种MVC模式,此时controller的所有方法会按照规则自动生成相应的路由,并注册到路由树。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// main 函数中加入配置
mvc.New(app).Handle(new(ExampleController))

// 定义Controller ++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ExampleController serves the "/", "/ping" and "/hello".
type ExampleController struct{}

// GetPing serves
// Method:   GET
// Resource: http://localhost:8080/ping
func (c *ExampleController) GetPing() string {
	return "pong"
}

// 通过反射找到所有方法,自动转变成路由并注册 +++++++++++++++++++++++++++++++++
// register all available, exported methods to handlers if possible.
func (c *ControllerActivator) parseMethods() {
	n := c.Type.NumMethod()
	for i := 0; i < n; i++ {
		m := c.Type.Method(i)
		c.parseMethod(m)
	}
}

Echo请求时构造链式中间件

echo的有两个全局中间件premiddleware 和 middleware 。比如Gin的单一数组,可配置性更强一些。

 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
e := echo.New()
e.POST("/bind", func(c echo.Context) error {
    // ...
})

// 添加路由处理函数
func (e *Echo) add(host, method, path string, handler HandlerFunc, 
                   middleware ...MiddlewareFunc) *Route {
	name := handlerName(handler)
	router := e.findRouter(host)
	router.Add(method, path, func(c Context) error {
        // 路由匹配之后动态构造中间件数组
		h := applyMiddleware(handler, middleware...)
		return h(c)
	})
	r := &Route{
		Method: method,
		Path:   path,
		Name:   name,
	}
	e.router.routes[method+path] = r
	return r
}

// 当请求来的时候,走下面动态构造请求链,并链式执行。
// 查找当前路由节点,匹配就取出handler,再结合全局的middleware构造最终的请求链。
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Acquire context
	c := e.pool.Get().(*context)
	c.Reset(r, w)

	h := NotFoundHandler

	if e.premiddleware == nil {
		e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
		h = c.Handler()
		h = applyMiddleware(h, e.middleware...)
	} else {
		h = func(c Context) error {
			e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
			h := c.Handler()
			h = applyMiddleware(h, e.middleware...)
			return h(c)
		}
		h = applyMiddleware(h, e.premiddleware...)
	}

	// Execute chain
	if err := h(c); err != nil {
		e.HTTPErrorHandler(err, c)
	}

	// Release context
	e.pool.Put(c)
}

// 这是构造链式中间件的关键函数
func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc {
	for i := len(middleware) - 1; i >= 0; i-- {
		h = middleware[i](h)
	}
	return h
}

go-zero构造链式中间件

 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
// 路由的通用写法
func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
	engine.AddRoutes(
		[]rest.Route{
            {
				Method:  http.MethodGet,
				Path:    "/from/:name",
				Handler: GreetHandler(serverCtx),
			},
		},
	)
}

// server.Start() 的时候会从底层构造每个路由的中间件调用链条
func (s *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,
	route Route, verifier func(chain alice.Chain) alice.Chain) error {
	chain := alice.New(
		handler.TracingHandler,
		s.getLogHandler(),
		handler.PrometheusHandler(route.Path),
		handler.MaxConns(s.conf.MaxConns),
		handler.BreakerHandler(route.Method, route.Path, metrics),
		handler.SheddingHandler(s.getShedder(fr.priority), metrics),
		handler.TimeoutHandler(time.Duration(s.conf.Timeout)*time.Millisecond),
		handler.RecoverHandler,
		handler.MetricHandler(metrics),
		handler.MaxBytesHandler(s.conf.MaxBytes),
		handler.GunzipHandler,
	)
	chain = s.appendAuthHandler(fr, chain, verifier)

	for _, middleware := range s.middlewares {
		chain = chain.Append(convertMiddleware(middleware))
	}
	handle := chain.ThenFunc(route.Handler)

	return router.Handle(route.Method, route.Path, handle)
}

// 这是其中一个中间件的例子,传入和返回的是同一种类型的函数。
func TimeoutHandler(duration time.Duration) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		if duration > 0 {
			return http.TimeoutHandler(next, duration, reason)
		}

		return next
	}
}

GoFast的中间件设计

前面几个框架中间件的实现都不错,但有些地方我并不是太喜欢,为什么呢?

  1. 业务代码中需要使用ctx.Next(),比如Iris这样挺繁琐的。
  2. 在使用app.Use()函数的时候,这与路由的app.GET()的先后顺序相关。也就是你需要注意顺序。
  3. echo居然在运行时路由匹配之后动态构造中间件数组。
  4. echo和go-zero的链式调用并不太好理解,自定义中间件稍有障碍。

于是在GoFast中我实现了类似Gin和Iris(在实现GoFast的时候我并没有看过Iris框架)的中间件数据结构,但是比他们都灵活的多。更多具体内容参考我对GoFast的专题介绍:

GoFast设计概览:/2021/01/01120012-001-intr.html

参考:

https://blog.csdn.net/weixin_29292535/article/details/112126594

(完)