中间件
原创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")
}
