十分鐘學會用Go編寫Web中間件

本文首發於公衆號,關注文末公衆號回覆gohttp03 獲取文章所用完整源代碼。shell

中間件(一般)是一小段代碼,它們接受一個請求,對其進行處理,每一箇中間件只處理一件事情,完成後將其傳遞給另外一箇中間件或最終處理程序,這樣就作到了程序的解耦。若是沒有中間件那麼咱們必須在最終的處理程序中來完成這些處理操做,這無疑會形成處理程序的臃腫和代碼複用率不高的問題。中間件的一些常見用例是請求日誌記錄,Header操縱、HTTP請求認證和ResponseWriter劫持等等。編程

畫外音:上面這段描述中間件的文字,跟我兩年前在Laravel源碼解析之中間件寫的幾乎同樣(其實這圖也是從那裏拿過來的)。再次說明作開發時間長了之後掌握一些編程的思想有時候比掌握一門編程語言更重要,這不我們就又用Go來寫中間件了。瀏覽器

建立中間件

接下來咱們用Go建立中間件,中間件只將http.HandlerFunc做爲其參數,在中間件裏將其包裝並返回新的http.HandlerFunc供服務器服務複用器調用。這裏咱們建立一個新的類型Middleware,這會讓最後一塊兒鏈式調用多箇中間件變的更簡單。bash

type Middleware func(http.HandlerFunc) http.HandlerFunc 複製代碼

下面的中間件通用代碼模板讓咱們平時編寫中間件變得更容易。服務器

中間件代碼模板

中間件是使用裝飾器模式實現的,下面的中間件通用代碼模板讓咱們平時編寫中間件變得更容易,咱們在本身寫中間件的時候只須要往樣板裏填充須要的代碼邏輯便可。app

func createNewMiddleware() Middleware {
    // 建立一個新的中間件
    middleware := func(next http.HandlerFunc) http.HandlerFunc {
        // 建立一個新的handler包裹next
        handler := func(w http.ResponseWriter, r *http.Request) {

            // 中間件的處理邏輯
						......
            // 調用下一個中間件或者最終的handler處理程序
            next(w, r)
        }

        // 返回新建的包裝handler
        return handler
    }

    // 返回新建的中間件
    return middleware
}
複製代碼

使用中間件

咱們建立兩個中間件,一個用於記錄程序執行的時長,另一個用於驗證請求用的是不是指定的HTTP Method,建立完後再用定義的Chain函數把http.HandlerFunc和應用在其上的中間件鏈起來,中間件會按添加順序依次執行,最後執行處處理函數。完整的代碼以下:編程語言

package main

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

type Middleware func(http.HandlerFunc) http.HandlerFunc // 記錄每一個URL請求的執行時長 func Logging() Middleware {

    // 建立中間件
    return func(f http.HandlerFunc) http.HandlerFunc {

        // 建立一個新的handler包裝http.HandlerFunc
        return func(w http.ResponseWriter, r *http.Request) {

            // 中間件的處理邏輯
            start := time.Now()
            defer func() { log.Println(r.URL.Path, time.Since(start)) }()

            // 調用下一個中間件或者最終的handler處理程序
            f(w, r)
        }
    }
}

// 驗證請求用的是不是指定的HTTP Method,不是則返回 400 Bad Request
func Method(m string) Middleware {

    return func(f http.HandlerFunc) http.HandlerFunc {

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

            if r.Method != m {
                http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
                return
            }

            f(w, r)
        }
    }
}

// 把應用到http.HandlerFunc處理器的中間件
// 按照前後順序和處理器自己鏈起來供http.HandleFunc調用
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
    for _, m := range middlewares {
        f = m(f)
    }
    return f
}

// 最終的處理請求的http.HandlerFunc 
func Hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world")
}

func main() {
    http.HandleFunc("/", Chain(Hello, Method("GET"), Logging()))
    http.ListenAndServe(":8080", nil)
}
複製代碼

運行程序後會打開瀏覽器訪問http://localhost:8080會有以下輸出:函數

2020/02/07 21:07:52 / 359.503µs
2020/02/07 21:09:17 / 34.727µs
複製代碼

到這裏怎麼用Go編寫和使用中間件就講完,也就十分鐘吧。不過這裏更多的是探究實現原理,那麼在生產環境怎麼本身使用編寫的這些中間件呢,咱們接着往下看。學習

使用gorilla/mux應用中間件

上面咱們探討了如何建立中間件,可是使用上每次用Chain函數連接多箇中間件和處理程序仍是有些不方便,並且在上一篇文章中咱們已經開始使用gorilla/mux提供的Router做爲路由器了。好在gorrila.mux支持向路由器添加中間件,若是發現匹配項,則按照添加中間件的順序執行中間件,包括其子路由器也支持添加中間件。ui

gorrila.mux路由器使用Use方法爲路由器添加中間件,Use方法的定義以下:

func (r *Router) Use(mwf ...MiddlewareFunc) {
	for _, fn := range mwf {
		r.middlewares = append(r.middlewares, fn)
	}
}
複製代碼

它能夠接受多個mux.MiddlewareFunc類型的參數,mux.MiddlewareFunc的類型聲明爲:

type MiddlewareFunc func(http.Handler) http.Handler
複製代碼

跟咱們上面定義的Middleware類型很像也是一個函數類型,不過函數的參數和返回值都是http.Handler接口,在《深刻學習用 Go 編寫 HTTP 服務器》中咱們詳細講過http.Handler它 是net/http中定義的接口用來表示處理 HTTP 請求的對象,其對象必須實現ServeHTTP方法。咱們把上面說的中間件模板稍微更改下就能建立符合gorrila.mux要求的中間件:

func CreateMuxMiddleware() mux.MiddlewareFunc {

	// 建立中間件
	return func(f http.Handler) http.Handler {

		// 建立一個新的handler包裝http.HandlerFunc
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

			// 中間件的處理邏輯
			......

			// 調用下一個中間件或者最終的handler處理程序
			f.ServeHTTP(w, r)
		})
	}
}
複製代碼

接下來,咱們把上面自定義的兩個中間件進行改造,而後應用到咱們一直在使用的http_demo項目上,爲了便於管理在項目中新建middleware目錄,兩個中間件分別放在log.gohttp_method.go

//middleware/log.go
func Logging() mux.MiddlewareFunc {

	// 建立中間件
	return func(f http.Handler) http.Handler {

		// 建立一個新的handler包裝http.HandlerFunc
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

			// 中間件的處理邏輯
			start := time.Now()
			defer func() { log.Println(r.URL.Path, time.Since(start)) }()

			// 調用下一個中間件或者最終的handler處理程序
			f.ServeHTTP(w, r)
		})
	}
}

// middleware/http_demo.go
func Method(m string) mux.MiddlewareFunc {

	return func(f http.Handler) http.Handler {

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

			if r.Method != m {
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}

			f.ServeHTTP(w, r)
		})
	}
}
複製代碼

而後在咱們的路由器中進行引用:

func RegisterRoutes(r *mux.Router) {
	r.Use(middleware.Logging())// 全局應用
	indexRouter := r.PathPrefix("/index").Subrouter()
	indexRouter.Handle("/", &handler.HelloHandler{})

	userRouter := r.PathPrefix("/user").Subrouter()
	userRouter.HandleFunc("/names/{name}/countries/{country}", handler.ShowVisitorInfo)
	userRouter.Use(middleware.Method("GET"))//給子路由器應用
}
複製代碼

再次編譯啓動運行程序後訪問

http://localhost:8080/user/names/James/countries/NewZealand
複製代碼

從控制檯裏能夠看到,記錄了這個請求的處理時長:

2020/02/08 09:29:50 Starting HTTP server...
2020/02/08 09:55:20 /user/names/James/countries/NewZealan 51.157µs

複製代碼

到這裏咱們探究完了編寫Web中間件的過程和原理,在實際開發中只須要根據本身的需求按照咱們給的中間件代碼模板編寫中間件便可,在編寫中間件的時候也要注意他們的職責範圍,不要全部邏輯都往裏放。

前文回顧:

深刻學習用 Go 編寫 HTTP 服務器

使用gorilla/mux加強Go HTTP服務器的路由能力

在公衆號裏關鍵字回覆gohttp03能夠拿到本篇文章中完整的源代碼,喜歡個人文章幫忙轉發點贊。

tWbHIMFsM3.png
相關文章
相關標籤/搜索