深入理解与使用go之中间件--实现

深入理解与使用go之--中间件实现

目录

引子

中间件

定义

原理

简单实现

多个中间件

优化

扩展

gin框架实现


引子

我们在做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++
      }
    }

    是不是有种豁然开朗的感觉,与上面示例不同的是,这里的传递的参数是上下文

  • 22
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值