Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

中间件设计

GF提供了优雅的中间件请求控制方式,该方式也是主流的WebServer提供的请求流程控制方式,基于中间件设计可以为WebServer提供更灵活强大的插件机制。经典的中间件洋葱模型:

经典的中间件洋葱模型

中间件定义

中间件的定义和普通HTTP执行方法HandlerFunc一样,但是可以在Request参数中使用Middleware属性对象来控制请求流程。

...

可以看到在该中间件中执行完成跨域请求处理的逻辑后,使用r.Middleware.Next()方法进一步执行下一个流程;如果这个时候直接退出不调用r.Middleware.Next()方法的话,将会退出后续的执行流程(例如可以用于请求的鉴权处理)。

中间件类型

中间件的类型分为两种:前置中间件和后置中间件。前置即在路由服务函数调用之前调用,后置即在其后调用。

前置中间件

其定义类似于:

func Middleware(r *ghttp.Request) {
	// 中间件处理逻辑
	r.Middleware.Next()
}

后置中间件

其定义类似于:

func Middleware(r *ghttp.Request) {
	r.Middleware.Next()
	// 中间件处理逻辑
}

中间件注册

中间件的注册有多种方式,参考接口文档: https://godoc.org/github.com/gogf/gf/net/ghttp

全局中间件

// 通过Server对象绑定
func (s *Server) BindMiddleware(pattern string, handlers ...HandlerFunc)
func (s *Server) BindMiddlewareDefault(handlers ...HandlerFunc)

// BindMiddlewareDefault 别名
func (s *Server) Use(handlers ...HandlerFunc)

// 通过Domain对象绑定
func (d *Domain) BindMiddleware(pattern string, handlers ...HandlerFunc)
func (d *Domain) BindMiddlewareDefault(handlers ...HandlerFunc)

// BindMiddlewareDefault 别名
func (d *Domain) Use(handlers ...HandlerFunc)

...

全局中间件仅对动态请求拦截有效,无法拦截静态文件请求。

分组路由中间件

func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup

通过分组路由使用中间件特性是比较常用的方式。分组路由中注册的中间件绑定到当前分组路由中的所有的服务请求上,当服务请求被执行前会调用到其绑定的中间件方法。 分组路由仅有一个Middleware的中间件注册方法。

中间件执行优先级

全局中间件

由于全局中间件也是通过路由规则执行,那么也会存在执行优先级:

...

这里的建议来参考于gRPC的拦截器设计,没有过多的路由控制,仅在一个地方同一个方法统一注册。往往越简单,越容易理解,也便于长期维护。

分组路由中间件

分组路由中间件是绑定到分组路由上的服务方法,不存在路由规则匹配,因此只会按照注册的先后顺序执行。参考后续示例。

使用示例1,允许跨域请求

第一个例子,也是比较常见的功能需求。

我们需要在所有API请求之前增加允许跨域请求的返回Header信息,该功能可以通过中间件实现:

...

这里我们使用group.Middleware(MiddlewareCORS)将跨域中间件通过分组路由的形式注册绑定到了/api.v2路由下所有的服务函数中。随后我们可以通过请求 http://127.0.0.1:8199/api.v2/user/list 来查看允许跨域请求的Header信息是否有返回。

使用示例2,请求鉴权处理

我们在跨域请求中间件的基础之上加上鉴权中间件。

为了简化示例,在该示例中,当请求带有token参数,并且参数值为123456时可以通过鉴权,并且允许跨域请求,执行请求方法;否则返回403 Forbidden状态码。

...

可以看到,我们的服务方法绑定了两个中间件,跨域中间件和而鉴权中间件。 请求时将会按照中间件注册的先后顺序,先执行MiddlewareCORS全局中间件,再执行MiddlewareAuth分组中间件。 随后我们可以通过请求 http://127.0.0.1:8199/api.v2/user/listhttp://127.0.0.1:8199/api.v2/user/list?token=123456 对比来查看效果。

使用示例3,鉴权例外处理

使用分组路由中间件可以很方便地添加鉴权例外,因为只有当前分组路由下注册的服务方法才会绑定并执行鉴权中间件,否则并不会执行到鉴权中间件。

...

可以看到,只有/admin/dashboard路由的服务方法绑定了鉴权中间件main.MiddlewareAuth,而/admin/login路由的服务方法并没有添加鉴权处理。 随后我们访问以下URL查看效果: 1. http://127.0.0.1:8199/admin/login 1. http://127.0.0.1:8199/admin/dashboard 1. http://127.0.0.1:8199/admin/dashboard?token=123456

使用示例4,统一的错误处理

基于中间件,我们可以在服务函数执行完成后做一些后置判断的工作,特别是统一数据格式返回、结果处理、错误判断等等。这种需求我们可以使用后置的中间件类型来实现。我们使用一个简单的例子,用来演示如何使用中间件对所有的接口请求做后置判断处理,作为一个抛砖引玉作用。

...

执行该示例后,访问 http://127.0.0.1:8199/api.v2/user/list?token=123456 查看效果。

使用示例5,自定义日志处理

我们来更进一步完善一下以上示例,我们将请求日志包括状态码输出到终端。这里我们必须得使用”全局中间件”了,这样可以拦截处理到所有的服务请求,甚至404请求。

...