中间件
参考文档:Go语言高级编程
中间件就是非业务的技术类组件,常常使用中间件剥离非业务逻辑。
如下是一个简单的web服务,挂载一个简单的路由。
package main
import (
"fmt"
"net/http"
)
func hello(wr http.ResponseWriter, r *http.Request) {
wr.Write([]byte("hello"))
}
func main() {
http.Handle("/", http.HandlerFunc(hello))
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("err:", err)
}
}
若有一个需求需要统计hello
方法执行耗时,最简单的方法即为修改hello
方法,在其中加入耗时统计。
func hello(wr http.ResponseWriter, r *http.Request) {
timeStart := time.Now()
wr.Write([]byte("hello"))
timeElapsed := time.Since(timeStart)
fmt.Println("use time:", timeElapsed)
}
但假如有更多的方法需要加入耗时统计功能,则需要每个方法都需适配。这样导致每个方法都包含一段与业务无关的代码,且后续的维护成本也更高。例如,我们需要将每个接口运行耗时上报的监控系统,那么我们就需要把每个方法的代码都修改一遍。
上述,我们犯了一个最大错误,就是把业务代码和非业务代码揉在了一起,且有很多重复代码。为此,我们可以使用中间件剥离非业务逻辑。
package main
import (
"fmt"
"net/http"
"time"
)
func hello(wr http.ResponseWriter, r *http.Request) {
fmt.Println("\nwrite response hello...\n")
wr.Write([]byte("hello"))
}
func timeMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
fmt.Println("time middleware start...")
timeStart := time.Now()
// next handler
next.ServeHTTP(wr, r)
timeElapsed := time.Since(timeStart)
fmt.Println("time middleware end, use time:", timeElapsed)
})
}
func main() {
http.Handle("/", timeMiddleware(http.HandlerFunc(hello)))
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("err:", err)
}
}
启动server后请求,输出结果:
time middleware start...
write response hello...
time middleware end, use time: 0s
这样就非常轻松地实现了业务与非业务之间的剥离,魔法就在于这个timeMiddleware.
优雅实现中间件
上一节中解决了业务功能代码和非业务功能代码的解耦,但也提到了,看起来并不美观,如果需要修改这些函数的顺序,或者增删中间件还是有点费劲,本节我们来进行一些“写法”上的优化。
看一个例子:
r := NewRouter()
r.Use(timeMiddleware)
r.Use(helloMiddleware)
r.Add("/", http.HandlerFunc(hello))
通过多步设置,我们拥有了和上一节差不多的执行函数链。胜在直观易懂,如果我们要增加或者删除中间件,只要简单地增加删除对应的Use()调用就可以了。非常方便。
从框架的角度来讲,怎么实现这样的功能呢?也不复杂:
package main
import (
"fmt"
"net/http"
"time"
)
func hello(wr http.ResponseWriter, r *http.Request) {
fmt.Println("\nwrite response hello...\n")
wr.Write([]byte("hello"))
}
func timeMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
fmt.Println("time middleware start...")
timeStart := time.Now()
// next handler
next.ServeHTTP(wr, r)
timeElapsed := time.Since(timeStart)
fmt.Println("time middleware end, use time:", timeElapsed)
})
}
func helloMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
fmt.Println("hello middelware begin!")
// next handler
next.ServeHTTP(wr, r)
fmt.Println("hello middleware end !")
})
}
type middleware func(http.Handler) http.Handler
type Router struct {
middlewareChain []middleware
mux map[string]http.Handler
}
func NewRouter() *Router {
m := make(map[string]http.Handler, 10)
newR := &Router{
mux: m,
}
return newR
}
func (r *Router) Use(m middleware) {
r.middlewareChain = append(r.middlewareChain, m)
}
func (r *Router) Add(route string, h http.Handler) {
var mergedHandler = h
for i := len(r.middlewareChain) - 1; i >= 0; i-- {
mergedHandler = r.middlewareChain[i](mergedHandler)
}
r.mux[route] = mergedHandler
}
func main() {
// 增加中间件
r := NewRouter()
r.Use(timeMiddleware)
r.Use(helloMiddleware)
r.Add("/", http.HandlerFunc(hello))
http.Handle("/", r.mux["/"])
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("err:", err)
}
}
r.mux["/"]可以理解为:
timeMiddleWare(helloMiddelWare(hello))
遂执行顺序为:
timeMiddleWare(helloMiddelWare) END!
| |
| echo:time middleware start < echo:time middleware end...
V |
helloMiddelWare(hello) |
| |
| echo:hello middelware begin! < echo:hello middleware end !
V |
hello() |
| |
|echo:write response hello... |
| |
------------------»----------------↑
理清楚执行顺序后,可以看看实际程序输出是否与我们预想相同:
time middleware start...
hello middelware begin!
write response hello...
hello middleware end !
time middleware end, use time: 976.5µs