学习Golang的HTTP中间件机制

因为 Golang 内置的 net/http 天生就支持 HTTP 中间件机制,所以即便不用 gin 之类的 Web 框架,我们也可以写出扩展性很好的 Web 应用。

假如你不了解 Golang 的 HTTP 中间件机制的话,那么可以把它看成是一个洋葱:

通过洋葱看中间件

每一个中间件都是一层洋葱皮,其中每一个中间件都可以改变请求和响应,我们可以很自然的把不同的逻辑放到不同的洋葱皮里,更代码更符合单一职责原则:

1package main

import (
    "net/http"
)

func foo(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("foo("))
        next(w, r)
        w.Write([]byte(")"))
    }
}

func bar(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("bar("))
        next(w, r)
        w.Write([]byte(")"))
    }
}

func test(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("test"))
}

func main() {
    http.Handle("/", foo(bar(test)))
    http.ListenAndServe(":8080", nil)
}

运行结果显示如下,它形象的说明了中间件的执行过程:

foo(bar(test))

联想一下洋葱的结构,基本就能明白 Golang 的 HTTP 中间件机制了,不过不爽的是不易维护,假如中间件很多的话,视觉上会呈现出复杂的嵌套,比如:

1middleware(
    middleware(
        middleware(
            middleware(
                middleware(
                    handler
                )
            )
        )
    )
)

我可不想维护这样的代码,下面看看如何简化编码方式:

1package pipeline

import "net/http"

type Pipeline struct {
    middlewares []Middleware
}

type Middleware func(http.HandlerFunc) http.HandlerFunc

func New(ms ...Middleware) Pipeline {
    return Pipeline{ms}
}

func (p Pipeline) Pipe(ms ...Middleware) Pipeline {
    return Pipeline{append(p.middlewares, ms...)}
}

func (p Pipeline) Process(h http.HandlerFunc) http.HandlerFunc {
    for i := range p.middlewares {
        h = p.middlewares[len(p.middlewares)-1-i](h)
    }

    return h
}

### 27... 

func main() {
    http.Handle("/", pipeline.New(foo).Pipe(bar).Process(test))
    http.ListenAndServe(":8080", nil)
}

对比一下在修改代码前后发生了什么改变,:

  • 修改前:foo(bar(test))
  • 修改后:pipeline.New(foo).Pipe(bar).Process(test)

虽然表面上看代码更长了,但是通过使用 Pipeline,我们把原本嵌套的结构改成了链式的结构,这不仅提高了代码的可维护性,而且也提高了代码的可复用性,设想一下,你有很多路由,它们有很多公共的中间件,利用 Pipeline,很简单就可以完成复用,类似的开源项目有很多,比如:「Alice – Painless middleware chaining for Go」,我就不多说了。

1