用Golang实现并理解Web中间件
在编写web应用中,我们常常会遇到这样的需求,比如,我们需要上报每个API的运行时间到运维监控系统。这时候你可以像下述代码一样将统计的逻辑写到每个路由函数中。
func someApi(w http.ResponseWriter, r *http.Request) { start := time.Now() // your logic metrics.Upload(time.Since(start)) }然而,这显然有悖DRY原则,我们需要将这些非业务逻辑剥离出来以实现解耦。这时候,中间件就能派上用场了,为了简单起见,我们这里将采用标准库net/http来实现。
准备工作 func hello(w http.ResponseWriter, r *http.Request) { log.Println("execute hello func") w.Write([]byte("hello, world")) } func main() { http.Handle("/", http.HandlerFunc(hello)) http.ListenAndServe(":3000", nil) }这里,我们创建了一个hello函数并将其转换成一个Handler用以处理HTTP请求。
中间件的实现 func middlewareOne(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println("middleware one start") // before logic next.ServeHTTP(w, r) log.Println("middleware one end") // after logic }) }这里,我们实现了一个中间件函数middlewareOne,它接收一个next的参数其类型为http.Handler并返回一个新的http.Handler。而next.ServeHTTP(w, r)会回调next函数自身,,即next(w, r)。看到这里,你可能会有点懵,我们需要先复习一下Handler,HandlerFunc,ServeHTTP三者的关系。
下面是三者的定义:
// A Handler responds to an HTTP request. type Handler interface { ServeHTTP(ResponseWriter, *Request) } type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }Handler是一个接口,用以处理HTTP请求,换句话说,一个函数若想成为一个路由函数必须得是一个Handler接口。
HandlerFunc是一个签名为func(ResponseWriter, *Request)的函数类型,其实现了ServerHTTP方法且签名相同,故HandleFunc是一个Handler,其ServerHTTP方法调用HandlerFunc自身。
三者关系如下图所示:
好了,接下来我们将中间件函数应用到我们的Handler上。
func main() { http.Handle("/", middlewareOne(http.HandlerFunc(hello))) http.ListenAndServe(":3000", nil) }运行程序,然后访问:3000/,控制台将输出如下结果。
2019/12/middleware one start 2019/12/execute hello func 2019/12/middleware one end当然,如果你想应用多个中间件,你只需要再套上一层,例如下述代码:
func middlewareTwo(next http.Handler) http.Handler { return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { log.Println("middleware two start") next.ServeHTTP(w, r) log.Println("middleware two end") }) } func main() { http.Handle("/", middlewareTwo(middlewareOne(http.HandlerFunc(hello)))) http.ListenAndServe(":3000", nil) }我画出了该函数链的执行流程,如下图所示:
可以看到,如果我们把路由函数hello看做为汉堡里的肉饼,中间件函数看做成面包。那么,middlewareOne包住了肉饼hello,而middlewareTwo又包住了middlewareTwo。
总结中间件函数与Python中的装饰器函数十分类似,都是在原函数逻辑不变的条件下进行扩展。
对于Web框架而言,类似于Flask里面的before_request和after_request钩子函数,next.ServeHTTP之前逻辑的等同于before_request里的逻辑、之后的等同于于after_request里的逻辑。
在业务开发中,我们应尽量将非业务逻辑抽象到中间件中,实现代码的松耦合与易扩展。
参考https://github.com/chai2010/advanced-go-programming-book/blob/master/ch5-web/ch5-03-middleware.md
https://www.alexedwards.net/blog/making-and-using-middleware
用Golang实现并理解Web中间件
温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/web/39747.html
- 上一篇:Web 服务器配置
- 下一篇:第十 构建Web内容的技术