go 的中间件开发思想

2022-02-13  本文已影响0人  wayyyy
package main

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

func main() {
    http.HandleFunc("/", hello)
    err := http.ListenAndServer(":8080", nil)
}

这是一个简单的web服务,现在来了一个新需求,想要统计之前写的hello服务的处理耗时,于是对上面的程序做少许修改。

var logger = log.New(os.Stdout, "", 0)

func hello(wr http.ResponseWriter, r *http.Request) {
    timeStart := time.Now()
    wr.Write([]byte("hello"))
    timeElapsed := time.Since(timeStart)
    logger.Println(timeElapsed)
}

这样便可以再每次接收到http请求时,打印出当前请求所消耗的时间。但是这样处理很麻烦,我们需要在每个接口处理的函数里面去加这些代码。

使用中间件剥离非业务逻辑

对于大多数场景来说,非业务的需求都是在HTTP请求处理前做一些事情,并且在响应后做一些事情。如同上面的计时操作,那么有没有一种办法可以让我们把非功能业务的代码剥离出去呢?

我们可以使用一种 函数适配器 的方法来对 helloHandler进行包装:

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

func timeMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
        timeStart := time.Now()
        next.ServerHTTP(wr, r)
        timeElapsed := time.Since(timeStart)
        logger.Println(timeElapsed)    
    })
}

func main() {
    http.Handle("/", timeMiddleware(http.HandlerFunc(hello)))
    err := http.ListenAndServer(":8080", nil)
}

http.Handler 定义在 net/http 包中,这里,我们可以梳理一下Handler HandlerFunc ServerHTTP 之间的关系。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServerHTTP(w ResponseWriter, r *http.Request) {
    f(w, r)
}

只要你的函数签名是func (ResponseWriter, *Request),那么就可以将该函数进行类型转换,转换为http.HandlerFunc

所以,我们中间件要做的事情就是通过一个或多个函数对我们自己的业务处理函数进行包装,返回一个包括了各个中间件逻辑的函数链。

customizedHandler = logger(timeout(ratelimit(helloHandler)))
image.png
更好的中间件实现写法

TODO

上一篇下一篇

猜你喜欢

热点阅读