当构建 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()
}
next.ServeHTTP(w,r)indexabouthttp.Handle("/",logginHandler(indexHandler))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()

}
middlewareFirstmiddlewareSecondmiddlewareSecond



我们通过学习自己动手写了两个拦截器,输出日志和简单权限校验。这也是我们中间件通常要做的事。其实很多时候我们不需要自己做过多事,想喝咖啡我们还有必要去种、烘焙、研磨吗?直接麦当劳了。

"github.com/gorilla/handlers"
func index(w http.ResponseWriter, r *http.Request){
    log.Println("Execute index handler")
    fmt.Fprintf(w,"welcome")
}

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

我们先定义两个路由器,分别在浏览器输出文本。

logFile, err := os.OpenFile("server.log",os.O_WRONLY|os.O_CREATE|os.O_APPEND,0666)
    // logFile, err := os.OpenFile("server.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
server.log
http.Handle("/", handlers.LoggingHandler(logFile, handlers.CompressHandler(indexHandler))) 
    http.Handle("/about", handlers.LoggingHandler(logFile, handlers.CompressHandler( aboutHandler)))
handlersLoggingHandlerCompressHandler

完整代码如下

package main

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

    "github.com/gorilla/handlers"
)

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

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

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

func main(){
    http.HandleFunc("/favicon.icon",iconHandler)

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

    logFile, err := os.OpenFile("server.log",os.O_WRONLY|os.O_CREATE|os.O_APPEND,0666)
    // logFile, err := os.OpenFile("server.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil{
        panic(err)
    }

    http.Handle("/", handlers.LoggingHandler(logFile, handlers.CompressHandler(indexHandler))) 
    http.Handle("/about", handlers.LoggingHandler(logFile, handlers.CompressHandler( aboutHandler)))

    server := &http.Server{
        Addr: ":8080",
    }
    log.Println("Listening...")
    server.ListenAndServe()
}
::1 - - [24/Apr/2019:05:39:31 +0800] "GET / HTTP/1.1" 200 31
::1 - - [24/Apr/2019:05:39:31 +0800] "GET /favicon.ico HTTP/1.1" 200 31
::1 - - [24/Apr/2019:05:39:38 +0800] "GET /about HTTP/1.1" 200 31
::1 - - [24/Apr/2019:05:39:38 +0800] "GET /favicon.ico HTTP/1.1" 200 31
github.com/justinas/alice
commonHandlers := alice.New(loggingHandler, handlers.CompressHandler)

对我们日志进行整合封装。

package main

import(
    "io"
    "log"
    "net/http"
    "os"

    "github.com/justinas/alice"
    "github.com/gorilla/handlers"
)

func loggingHandler(next http.Handler) http.Handler{
    
    logFile, err := os.OpenFile("server.log",os.O_WRONLY|os.O_CREATE|os.O_APPEND,0666)

    if err != nil{
        panic(err)
    }

    return handlers.LoggingHandler(logFile,next)
}

func index(w http.ResponseWriter, r *http.Request){
    w.Header().Set(
        "Content-Type",
        "text/html",
    )
    io.WriteString(
        w,
        `<doctype html>
        <html>
            <head>
                <title>Index</title>
            </head>
            <body>
                Hello Zidea!
            </body>
        </html>`, )
}

func about(w http.ResponseWriter, r *http.Request){
    w.Header().Set(
        "Content-Type",
        "text/html",
    )
    io.WriteString(
        w,
        `<doctype html>
        <html>
            <head>
                <title>About</title>
            </head>
            <body>
                about HTTP Middleware
            </body>
        </html>`, )
}

func iconHandler(w http.ResponseWriter, r *http.Request) {
    // http.ServeFile(w, r, "./favicon.ico")
}

func main(){
    http.HandleFunc("/favicon.ico", iconHandler)
    indexHandler := http.HandlerFunc(index)
    aboutHandler := http.HandlerFunc(about)
    commonHandlers := alice.New(loggingHandler, handlers.CompressHandler)
    http.Handle("/", commonHandlers.ThenFunc(indexHandler))
    http.Handle("/about", commonHandlers.ThenFunc(aboutHandler))

    server := &http.Server{
        Addr: ":8080",
    }
    log.Println("Listening...")
    server.ListenAndServe()
}
::1 - - [24/Apr/2019:05:53:01 +0800] "GET /about HTTP/1.1" 200 110
::1 - - [24/Apr/2019:05:53:05 +0800] "GET /welcome HTTP/1.1" 200 103