Versions Compared

Key

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

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

中间件定义

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

我们拿一个跨域请求的中间件定义来示例说明一下:

Code Block
languagego
func MiddlewareCORS(r *ghttp.Request) {
	r.Response.CORSDefault()
	r.Middleware.Next()
}

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

中间件类型

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

前置中间件

其定义类似于:

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

后置中间件

其定义类似于:

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

中间件注册

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

全局中间件

Code Block
languagego
func (s *Server) Use(handlers ...HandlerFunc)

全局中间件是可以独立使用的请求拦截方法,通过路由规则的方式进行注册,绑定到Server上,由于中间件需要执行请求拦截操作,因此往往是使用"模糊匹配"或者"命名匹配"规则。 

Tip

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

分组路由中间件

Code Block
languagego
func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup

分组路由中注册的中间件绑定到当前分组路由中的所有的服务请求上,当服务请求被执行前会调用到其绑定的中间件方法。 分组路由仅有一个Middleware的中间件注册方法。分组路由中间件与全局中间件不同之处在于,分组路由中间件无法独立使用,必须在分组路由注册中使用,并且绑定到当前分组路由中所有的路由上作为路由方法的一部分。

执行优先级

全局中间件

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

  1. 首先,由于全局中间件是基于模糊路由匹配,因此当同一个路由匹配到多个中间件时,会按照路由的深度优先规则执行,具体请查看路由章节;
  2. 其次,同一个路由规则下,会按照中间件的注册先后顺序执行,中间件的注册方法也支持同时按照先后顺序注册多个中间件;
  3. 最后,为避免优先级混淆和后续管理,建议将所有中间件放到同一个地方进行先后顺序注册来控制执行优先级;
Tip

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

分组路由中间件

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

Code Block
languagego
linenumberstrue
collapsetrue
package main

import (
	"context"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/glog"
)

type HelloReq struct {
	g.Meta `path:"/hello" method:"get"`
}
type HelloRes struct {
}

type Hello struct{}

func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
	return
}

func RequestHandle1(r *ghttp.Request) {
	glog.Debug(r.GetCtx(), "1")

	r.Middleware.Next()
}

func RequestHandle2(r *ghttp.Request) {
	glog.Debug(r.GetCtx(), "2")

	r.Middleware.Next()
}

func RequestHandle3(r *ghttp.Request) {
	glog.Debug(r.GetCtx(), "3")

	r.Middleware.Next()
}

func RequestHandle4(r *ghttp.Request) {
	glog.Debug(r.GetCtx(), "4")

	r.Middleware.Next()
}

func RequestHandle5(r *ghttp.Request) {
	r.Middleware.Next()

	glog.Debug(r.GetCtx(), "5")
}

func RequestHandle6(r *ghttp.Request) {
	r.Middleware.Next()

	glog.Debug(r.GetCtx(), "6")
}

func RequestHandle7(r *ghttp.Request) {
	r.Middleware.Next()

	glog.Debug(r.GetCtx(), "7")
}

func RequestHandle8(r *ghttp.Request) {
	r.Middleware.Next()

	glog.Debug(r.GetCtx(), "8")
}

func main() {
	s := g.Server()
	s.Use(ghttp.MiddlewareHandlerResponse)
	s.Group("/", func(g *ghttp.RouterGroup) {
		// 前置中间件
		g.Middleware(RequestHandle1)
		g.Middleware(RequestHandle2)

		// 后置中间件
		g.Middleware(RequestHandle5)
		g.Middleware(RequestHandle6)

		g.Group("/sub", func(g *ghttp.RouterGroup) {
			// 前置中间件
			g.Middleware(RequestHandle3)
			g.Middleware(RequestHandle4)

			// 后置中间件
			g.Middleware(RequestHandle7)
			g.Middleware(RequestHandle8)

			g.Bind(new(Hello))
		})
	})
	s.Run()
}

执行结果如下:

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

重要提示

如果在有分层的分组路由中使用中间件,同一个中间件只会执行一次,例如:



Panel
titleContent Menu

Table of Contents