golang 网络编程(5)中间件

golang_real.jpg

当构建 web 应用程序,可能对所有的请求会共享一些功能。例如我们每一个请求都会写入日志。

  • 打印 http 请求或返回的日志
  • 压缩 http 请求的返回值
  • 将 http 请求头保持一致
  • 实现安全以及权限验证

    Middleware-web.jpg

在 go 语言 net/http 标准库中提供了中间件类似的函数 StripPrefix 和 TimeoutHandler。

如何自己编写一个中间件呢?答案是

  • 这个函数接收一个 http.Handler 作为参数,目的是可使用其他中间件或应用处理器,调用其他中间件或应用的方法就是调用其 HttpServe 的方法。
  • 并且将 http.Handler 作为返回值,这样其他中间件可以使用 http.Handler 作为输入的参数,达到链式调用。
func middlewareHandler(next http.Handler) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 // 在执行应用处理器前编译中间件的逻辑
 next.ServeHTTP(w, r)
 // 在执行应用处理器后将会执行的编译中间件的逻辑
 })
}

从上面示例我们可以看出 go 语言是支持高阶函数,通过向下传递 http.Handler 来实现中间件。

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func logginHandler(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
        start := time.Now()
        log.Printf("Started %s %s",r.Method, r.URL.Path)
        next.ServeHTTP(w,r)
        log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
    })
}

func index(w http.ResponseWriter,r *http.Request ){
    log.Println("Executing index handler")
    fmt.Fprintf(w,"welcome!")
}

func about(w http.ResponseWriter, r *http.Request){
    log.Println("Executing about handler")
    fmt.Fprintf(w,"Go Middleware")
}

func iconHandler(w http.ResponseWriter, r *http.Request){

}

func main()  {

    http.HandleFunc("/favicon.ico", iconHandler)

    indexHandler := http.HandlerFunc(index)
    aboutHandler := http.HandlerFunc(about)

    http.Handle("/",logginHandler(indexHandler))
    http.Handle("/about",logginHandler(aboutHandler))

    server :=  &http.Server{
        Addr:":8080",
    }

    log.Println("Listening...")
    server.ListenAndServe()
}
  • 上面的代码关键是 logginHandler 这个日志处理器,作为中间件会拦截请求,进行处理也就是输出日志,通过 next.ServeHTTP(w,r) 然后将请求向下进行传递
  • 这里创建两个简单路由处理器 indexabout 。都是向客户端输出不同的文本。
  • http.Handle("/",logginHandler(indexHandler)) 通过 logginHandler 函数进行包裹,从容我们日志中间件可以拦截到发起的请求进行一些业务逻辑,业务逻辑可以位于 next.ServeHTTP(w,r) 之前或之后。通过下面的输出我们可以清晰可见代码执行的顺序。
Listening...
 Started GET /
 Executing index handler
 Completed / in 41.129µs
 Started GET /
 Executing index handler
 Completed / in 50.475µs
 Started GET /about
 Executing about handler
 Completed /about in 49.483µs

许多书都提供代码示例,个人建议是不要 copy 过来运行看效果,自己手 coding 就可以发现一些问题,同时可以思考为什么要这样编写代码。

通过上面示例我们了解如何写一个简单中间件,这个个人看来和 nodejs 实现中间件没有什么差别。实际开发中我们往往会有多个中间件来执行业务,那么这些中间件执行顺序也是我们值得考虑的问题。

package main

import(
    "fmt"
    "log"
    "net/http"
)

func middlewareFirst(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
        log.Println("MiddlewareFirst - before Handler")
        next.ServeHTTP(w,r)
        log.Println("MiddlewareFirst - after Handler")
    })
}

func middlewareSecond(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
        log.Println("MiddlewareSecond - before Handler")
        if r.URL.Path == "/message"{
            if r.URL.Query().Get("password") == "123"{
                log.Println("Authorized to system...")
                next.ServeHTTP(w,r)
            }else{
                log.Println("Failed to authorize to the system")
                return
            }
        }else{
            next.ServeHTTP(w,r)
        }
        log.Println("MiddlewareScond - after Handler")      
    })
}

func index(w http.ResponseWriter, r *http.Request){
    log.Println("Executed index Handler")
    fmt.Fprintf(w,"welcome")
}

func message(w http.ResponseWriter, r *http.Request){
    log.Println("Executed message Handler")
    fmt.Fprintf(w,"message...")
}

func iconHandler(w http.ResponseWriter, r *http.Request){

}

func main() {
    http.HandleFunc("/favicon",iconHandler)
    indexHandler := http.HandlerFunc(index)
    messageHandler := http.HandlerFunc(message)

    http.Handle("/",middlewareFirst(middlewareSecond(indexHandler)))
    http.Handle("/message",middlewareFirst(middlewareSecond(messageHandler)))

    server  := &http.Server{
        Addr:":8080",
    }

    log.Println("Listen...")

    server.ListenAndServe()

}

在上面代码中其实也没有什么特别之处,就是我们创建了两个 middlewareFirstmiddlewareSecond 两个中间件并且有一定先后顺序,然后在 middlewareSecond 中写了一个对访问权限校验的逻辑。大家可以尝试地去运行一下。

th-7.jpeg