跳至主要內容

中间件

程序员李某某原创Golang框架源码中间件大约 3 分钟

中间件

中间件是什么

  • 中间件(middlewares),简单说,就是非业务的技术类组件

  • Web 框架本身不可能去理解所有的业务,因而不可能实现所有的功能。

  • 因此,框架需要有一个插口,允许用户自己定义功能,嵌入到框架中,仿佛这个功能是框架原生支持的一样。

  • 因此,对中间件而言,需要考虑2个比较关键的点:

    • 插入点在哪?使用框架的人并不关心底层逻辑的具体实现,
      • 如果插入点太底层,中间件逻辑就会非常复杂。
      • 如果插入点离用户太近,那和用户直接定义一组函数,每次在 Handler 中手工调用没有多大的优势了。
    • 中间件的输入是什么?中间件的输入,决定了扩展能力。暴露的参数太少,用户发挥空间有限。 那对于一个 Web 框架而言,中间件应该设计成什么样呢?接下来的实现,基本参考了 Gin 框架

设计

  • 中间件的定义与路由映射的 Handler 一致,处理的输入是Context对象。我们就可以像Params那样,将每个中间件添加到对应的context上,中间件的调用是有顺序的,所有需要一个索引记录当前执行的中间件
      type Context struct {
          Writer     http.ResponseWriter
          Req        *http.Request
          StatusCode int
          Path       string
          Method     string
          Params     map[string]string
    
          // middleware
          handlers []HandleFunc
          index    int
      }
    
  • 插入点是框架接收到请求初始化Context对象后,允许用户使用自己定义的中间件做一些额外的处理,例如记录日志等,以及对Context进行二次加工。
  • 另外通过调用(*Context).Next()函数,中间件可等待用户自己定义的 Handler处理结束后,再做一些额外的操作,例如计算本次处理所用时间等。
func (ctx *Context) Next() {
	ctx.index++
	s := len(ctx.handlers)
	for ; ctx.index < s; ctx.index++ {
		// 执行中间件
		ctx.handlers[ctx.index](ctx)
	}
}

与Group关联

  • 我们在 分组控制 中封装RGroup时,故意留了一个字段 middlewares,用来存储绑定的中间件们
  • 定义一个Use方法来进行绑定动作
func (g *RGroup) Use(middlewares ...HandleFunc) {
	g.middlewares = append(g.middlewares, middlewares...)
}

最后就是在ServeHTTP被调用的时候依次使用中间件了

func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	var middlewares []HandleFunc

	for _, group := range e.groups {
		if strings.HasPrefix(r.URL.Path, group.pre) {
			middlewares = append(middlewares, group.middlewares...)
		}
	}

	ctx := newContext(w, r)
	ctx.handlers = middlewares
	tailNode, params := e.router.getRoute(ctx.Method, ctx.Path)
	if tailNode != nil {
		k := r.Method + "_" + tailNode.path // 处理函数的key

		ctx.Params = params                                      // 存到 context
		ctx.handlers = append(ctx.handlers, e.router.handles[k]) // // 取出,也追加到context中
	} else {
		ctx.handlers = append(ctx.handlers, func(ctx *Context) {
			ctx.String(http.StatusNotFound, "404 NOT FOUND: %s\n", ctx.Path)
		})
	}

	ctx.Next() // 挨个执行中间件和处理函数
}

func newContext(w http.ResponseWriter, req *http.Request) *Context {
	return &Context{
		Path:   req.URL.Path,
		Method: req.Method,
		Req:    req,
		Writer: w,
		index:  -1,
	}
}

通用中间件

web框架一般都会自带一些通用的中间件,比如 日志记录执行时间,我们也来实现一个

func Logger() HandleFunc {
	return func(c *Context) {
		// Start timer
		t := time.Now()
		// Process request
		c.Next()
		// Calculate resolution time
		log.Printf("############# logger [%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t))
	}
}

测试

func onlyForV2() lee.HandleFunc {
	return func(c *lee.Context) {
		// Start timer
		t := time.Now()
		// Calculate resolution time
		log.Printf("### v2 ### [%d] %s in %v for group v2", c.StatusCode, c.Req.RequestURI, time.Since(t))
	}
}

func main() {
	r := lee.New()
	r.Use(lee.Logger()) // global midlleware
	r.Get("/", func(c *lee.Context) {
		c.String(http.StatusOK, "<h1>Hello lee</h1>")
	})

	v2 := r.Group("/v2")
	v2.Use(onlyForV2()) // v2 group middleware
	{
		v2.Get("/hello/:name", func(c *lee.Context) {
			// expect /hello/leektutu
			c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
		})
	}

	r.Run(":8080")
}

上次编辑于:
贡献者: ext.liyuanhao3