go httpServer 中间件原理

566 阅读4分钟

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使用:

  1. 创建Chan对象

chan := alice.New(mid1,mid2,mid3)

其中mid1这些参数都是函数式中间件,New以后会返回一个Chan对象,这里可以不用mid这些中间件,用其Append方法可以随时添加。

  1. 添加中间件 如果中间不是在New的时候添加也可以通过 Append函数进行添加

chan.Append(mid4,mid5,mid6)

  1. 添加业务处理器并获取链式处理器 如果处理器是结构体通过下列方法获取最终链式处理器

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)

怎么样?是不是很简单了,当中间件多起来后,效果会更加明显的