深入理解与使用go之--中间件实现
目录
引子
我们在做web开发的时候,经常会遇到下面一些需求:
-
统计耗时:想程序内部统计某个路由的请求耗时
-
预处理:接口需要登录鉴权后才能继续进行
-
错误捕获:当程序运行发生错误时,我们需要及时捕获
-
日志记录:有时候需要记录请求与响应的参数
那怎么办呢,调用内部硬编码吗
func bbHandle(writer http.ResponseWriter, request *http.Request) {
// 3. 错误捕获
defer func() {
if p := recover(); p != nil {
log.Panicln(p)
}
}()
// 1. 统计耗时
start := time.Now()
// 2. 登录校验
if request.Header.Get("loginStatus") != "login in" {
io.WriteString(writer, "need login")
}
// 4. 日志记录
log.Println("hello bb", request)
io.WriteString(writer, "hello bb")
fmt.Println("耗时", time.Now().Sub(start).Milliseconds())
}
func main() {
l, err := net.Listen("tcp", ":8088")
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/bb", bbHandle)
http.Serve(l, nil)
}
现在是路由 /bb 的处理方法里包含了这几个,如果我希望
http.HandleFunc("/cc", ccHandle)
http.HandleFunc("/dd", ddHandle)
http.HandleFunc("/ee", eeHandle)
都包含应该怎么处理呢?难道每个里面都重复硬编码一次么
中间件
定义
中间件说白了就是你的web请求之前或者之后执行你要加的功能
原理
既然是请求前后做处理,我们自然而然的就想到了在处理函数
func bbHandle(writer http.ResponseWriter, request *http.Request)
// 即函数原型
type HandlerFunc func(ResponseWriter, *Request)
之上包一层,如下
func XXXMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// todo here...
next(w, r)
// todo here...
}
}
简单实现
根据原理,我们包装实现一个耗时功能
func CostTimeMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next(w, r)
fmt.Println("耗时", time.Now().Sub(start).Microseconds(), "us")
}
}
func bbHandle(writer http.ResponseWriter, request *http.Request) {
log.Println("hello bb", request)
io.WriteString(writer, "hello bb")
}
http函数处理部分就变更为
http.HandleFunc("/bb", CostTimeMiddleware(bbHandle))
多个中间件
如果我们再加两个功能 日志和错误捕获
func LoggerMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("pre", r)
next(w, r)
log.Println("after", w)
}
}
func RecoveryMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if p := recover(); p != nil {
log.Panicln(p)
}
}()
next(w, r)
}
}
http函数处理部分就变更为
http.HandleFunc("/bb", RecoveryMiddleware(CostTimeMiddleware(LoggerMiddleware(bbHandle))))
优化
如上,我们加更多的中间件,就会一层套一层,函数嵌套越来越多,我们能否统一封装在一个容器里,由容器自身调用呢
-
首先 我们想到的是,我们函数的规律有点像流水线,传入一个函数HandlerFunc 输出的也是函数HandlerFunc
type MiddleFunc func(http.HandlerFunc) http.HandlerFunc
-
然后,这些函数是许多个
DealFuncList []MiddleFunc
-
初始时,我们还要加入主请求处理函数,那么就有如下结构体
type MiddleFunc func(http.HandlerFunc) http.HandlerFunc type MiddleWare struct { DealFuncList []MiddleFunc handle http.HandlerFunc }
-
我们的目的是每次把需要中间件加进来
func (m *MiddleWare) Use(next MiddleFunc) { m.DealFuncList = append(m.DealFuncList, next) }
-
最终,我们要给http处理函数使用的时候,我们需要给出所有调用包装好的最终结果
func (m *MiddleWare) GetFinalHandle() http.HandlerFunc { l := len(m.DealFuncList) if l == 0 { return nil } wrapper := m.handle for i := l - 1; i >= 0; i-- { wrapper = m.DealFuncList[i](wrapper) } return wrapper }
完整代码如下
type MiddleFunc func(http.HandlerFunc) http.HandlerFunc
type MiddleWare struct {
DealFuncList []MiddleFunc
handle http.HandlerFunc
}
func NewMiddleWare(h http.HandlerFunc) *MiddleWare {
return &MiddleWare{
handle: h,
}
}
func (m *MiddleWare) Use(next MiddleFunc) {
m.DealFuncList = append(m.DealFuncList, next)
}
func (m *MiddleWare) GetFinalHandle() http.HandlerFunc {
l := len(m.DealFuncList)
if l == 0 {
return nil
}
wrapper := m.handle
for i := l - 1; i >= 0; i-- {
wrapper = m.DealFuncList[i](wrapper)
}
return wrapper
}
然后 我们http处理函数部分就可调整为
m := NewMiddleWare(bbHandle)
m.Use(CostTimeMiddleware)
m.Use(LoggerMiddleware)
m.Use(RecoveryMiddleware)
http.HandleFunc("/bb", m.GetFinalHandle())
嗯, 瞬间清爽了许多,有点内味儿了
扩展
gin框架实现
啥味儿,我们看看gin 框架中间件的实现
-
先看看gin中是怎么使用中间件
g := gin.Default() g.Use(gin.Logger())
-
使用了 Use 方法,我们跟踪进去
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) // ---- }
-
又调了一层,继续
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) // ---- }
-
这里的Handlers 是啥呢
type RouterGroup struct { Handlers HandlersChain //--- }
-
我们继续看 HandlersChain , 是不是和我们上面自己实现的很像
type HandlersChain []HandlerFunc
区别就是 这个函数将参数换成了gin强大的上下文
type HandlerFunc func(*Context)
-
我们找到了定义,我们再看看使用, 首先调用
Run
g.Run(":8080")
-
跟踪进去
func (engine *Engine) Run(addr ...string) (err error) { // --- err = http.ListenAndServe(address, engine.Handler()) // --- }
-
核心是调用了 http.ListenAndServe, 这个方法第二个参数的对象必须实现
ServeHTTP
方法,那我们开始找 第二个参数func (engine *Engine) Handler() http.Handler { if !engine.UseH2C { return engine } // --- return h2c.NewHandler(engine, h2s) }
如上,如果没有启用http2, 那就直接返回本尊,否则再用http2包裹一层,感兴趣的可以看看包裹里面实现的 ServeHTTP 方法,其实最终还是调用了 engine 的 ServeHTTP
-
那我们直接看看 engine 的 ServeHTTP
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // --- engine.handleHTTPRequest(c) // --- }
-
继续看
func (engine *Engine) handleHTTPRequest(c *Context) { // Find root of the tree for the given HTTP method t := engine.trees for i, tl := 0, len(t); i < tl; i++ { // ---- if value.handlers != nil { // ---- c.Next() // ---- return } // ---- } // ---- }
我们看到里面有个最核心的方法 Next
-
那我们看看 Next 内容
func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } }
是不是有种豁然开朗的感觉,与上面示例不同的是,这里的传递的参数是上下文