go的http框架都有中间件系统,可以编写中间件对请求响应流进行处理。中间件可以说非常强大,但其原理却很简单。
之前的一篇文章介绍了go的http server构架,基本上都是http.handler串连到一起组成了一个http server。中间件本质也是一个http.handler,只是多了个获取其他http.handler的能力。
1. 搭建测试环境
//1. 编写业务处理器
type MyHandler struct{}
func (this MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("my handler \n"))
}
//2. 编写服务并启动
route := http.NewServeMux()
handler:=MyHandler{}
route.Handle("/api/my",handler)
http.ListenAndServe(":9100", route)
2. 编写第一个中间件
中间件本质也是一个http.handler对象,只是中间件多了一个获取其他http.handler的能力,中间件简而言之就是一个对其他http.handler进行处理的http.handler对象。
type Middleware1 struct {
next http.Handler
}
func (this *Middleware1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println("this is mid 1 begin")
this.next.ServeHTTP(w, r)
log.Println("this is mid 1 close")
}
从以上中间件代码可以看出next http.Handler会包裹在本中间件中调用,所以可以在调用next前后进行处理。本例只是简单打印了些说明文字。真正中间件会利用w r参数对请求响应流做些处理。
3. 使用中间件
route := http.NewServeMux()
//路由处理器与普通处理器进行绑定
handler:=MyHandler{}
route.Handle("/api/mid1",&Middleware1{handler})
//基础处理
http.ListenAndServe(":9100", route)
启动程序并在浏览器里输入 http://localhost:9100 进行测试,可以看到终端里打印了相关文字。但是这种中间件写法有点臃肿,还可以直接用函数写中间件。
4. 函数式中间件
func Middleware2(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("this is mid 2 begin")
next.ServeHTTP(w, r)
log.Println("this is mid 2 close")
})
}
该中间件比较简洁,利用了http.HandlerFunc函数直接生成一个http.Handler对象进行返回,此处为闭包环境所以可以在return前做一些永久初始化之类的操作。
5. 函数式中间件使用
//路由处理器
route := http.NewServeMux()
handler := MyHandler{}
route.Handle("/api/mid1", &Middleware1{handler}) //结构体式中间件
route.Handle("/api/mid2", Middleware2(handler)) //函数式中间件
http.ListenAndServe(":9100", route)
}
此处可以看出函数式中间件比结构体中间件编写和使用都比较简洁,下边的所有例子都会使用函数式中间件。
6. 多个中间件串联使用
首先编写两个中间件,功能也仅仅只是打印前后。
//中间件2 第二种函数式写法
func Middleware2(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("this is mid 2 begin")
next.ServeHTTP(w, r)
log.Println("this is mid 2 close")
})
}
//中间件2 第二种函数式写法
func Middleware3(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("this is mid 3 begin")
next.ServeHTTP(w, r)
log.Println("this is mid 3 close")
})
}
编写服务并同时使用这两个中间件
func main() {
//路由处理器
route := http.NewServeMux()
handler := MyHandler{}
route.Handle("/api/mid23", Middleware2(Middleware3(handler))) //同时使用两个中该年间
http.ListenAndServe(":9100", route)
}
测试一下可以看到打印内容如下:
2022/05/02 12:47:29 this is mid 2 begin
2022/05/02 12:47:29 this is mid 3 begin
2022/05/02 12:47:29 this is mid 3 close
2022/05/02 12:47:29 this is mid 2 close
注意打印顺序为 2begin->3begin->3close->2close,在3begin->3close中间调用了业务处理器。从这能看出来中间件就像一个堆栈,先进后出。请求流通过一层层中间件处理,最后被业务处理器处理完后在生成响应流,响应流在按照“原路”一层层进行返回。
7. 中间件管理器 Alice
以上只是串联了两个中间件,使用起来已经不方便了 Middleware2(Middleware3(handler)),如果中间件比较多,一些框架使用七八个中间件是很普遍的。那样的话会嵌套很长,比如如下代码:
mid1(mid2(mid3(mid4(mid5(mid6...)))))
这种中间件使用方式绝对不是正确的方式,所以可以使用 "github.com/justinas/alice" 对中间件进行统一处理。 alice的代码和原理非常简单,就是把函数式中间件注册到它内部一个切片里,alice去做嵌套调用的活。
下面简单介绍下alice使用:
- 创建Chan对象
chan := alice.New(mid1,mid2,mid3)
其中mid1这些参数都是函数式中间件,New以后会返回一个Chan对象,这里可以不用mid这些中间件,用其Append方法可以随时添加。
- 添加中间件 如果中间不是在New的时候添加也可以通过 Append函数进行添加
chan.Append(mid4,mid5,mid6)
- 添加业务处理器并获取链式处理器 如果处理器是结构体通过下列方法获取最终链式处理器
chan.Then(handler)
如果处理器是函数式处理器通过下列方法使用。
chan.ThenFun(handlerFun)
alice使用起来很简单,方法也就这四个。下边把之前的例子用alice从新实现。
//路由处理器
route := http.NewServeMux()
handler := MyHandler{}
mid := alice.New(
Middleware2,
Middleware3, //有多少中间件都可以放在这里
)
route.Handle("/api/mid", mid.Then(handler))
http.ListenAndServe(":9100", route)
怎么样?是不是很简单了,当中间件多起来后,效果会更加明显的