這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go語言經典庫流量分析,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續系列。覺得有協助的話,順手分享到朋友圈吧,感謝支援。
在我們編寫web服務端程式的時候,我們可能會對一些甚至全部的Http Request統一處理,比如我們記錄每個訪問的Request,對提交的Form表單進行映射等,要達到這些目的,比較優雅的做法是Http 中介軟體。
中介軟體,顧名思義,強調的是中間,他是一種業務無關的,在正常的的業務handler處理前後的,獨立的邏輯處理片段。一般調用順序如下:
1 |
ServeMux路由分發->調用中介軟體1->調用中介軟體2……->調用真正的業務處理邏輯 |
因為中介軟體非常獨立,可以我們不用的時候,去掉即可;需要用的時候,加上,並不會修改真正的業務處理邏輯代碼,可謂非常簡潔方便。
這裡我選用Gorilla Handlers這個中介軟體庫示範如何使用和定義一個中介軟體,這一篇主要講Gorilla Handlers的使用,下一篇會講Gorilla Handlers裡每個中介軟體的實現原理。
安裝
Gorilla Handlers是一個很簡單,但是很有代表性的中介軟體庫,所以拿他來分析,更容易理解handler中介軟體。在使用之前,我們要先安裝,該中介軟體庫已經託管在Github上,所以我們直接使用go get即可。
1 |
$ go get github.com/gorilla/handlers |
安裝之後,我們在代碼裡使用如下代碼即可匯入使用。
1 |
import "github.com/gorilla/handlers" |
Gorilla Handlers以函數的方式提供了好幾種中介軟體,比如CombinedLoggingHandler、CompressHandler、ContentTypeHandler、LoggingHandler等,下面我們就一一介紹他們。
LoggingHandler
我們應該都用過Nginx,Nginx的訪問日誌,類似於如下這樣:
1 |
[05/Aug/2017:21:06:24 +0800] "GET /favicon.ico HTTP/1.1" 200 11 |
從中我們可以看到訪問的什麼資源,使用的什麼協議,返回的HTTP狀態以及請求的大小是多少,這種一種日誌風格,和Apache Common Log Format很像,LoggingHandler中介軟體就是幫我們做這個事情的。
它可以記錄request的日誌,輸出到一個io.Writer裡。
123456789101112131415161718 |
func main() {http.Handle("/",useLoggingHandler(handler()))http.ListenAndServe(":1234",nil)}func handler() http.Handler{return http.HandlerFunc(myHandler)}func myHandler(rw http.ResponseWriter, r *http.Request) {rw.WriteHeader(http.StatusOK)io.WriteString(rw,"Hello World")}func useLoggingHandler(next http.Handler) http.Handler {return handlers.LoggingHandler(os.Stdout,next)} |
我這裡的io.Writer是一個os.Stdout,也就是標準的控制台輸出,所以我們訪問http://localhost:1234/的時候,可以看到控制台列印的Request日誌資訊輸出。
12 |
::1 - - [05/Aug/2017:21:06:24 +0800] "GET / HTTP/1.1" 200 11::1 - - [05/Aug/2017:21:06:24 +0800] "GET /favicon.ico HTTP/1.1" 200 11 |
從handlers.LoggingHandler函數的參數我們可以看出,它接受一個Handler,然後返回一個Handler,其實就是對現有的Handler的一次封裝,這就是中介軟體。
這裡的輸出參數類型是io.Writer,所以我們可以輸出檔案等實現了該介面的任何類型。
Go語言經典庫流量分析,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續系列。覺得有協助的話,順手分享到朋友圈吧,感謝支援。
CombinedLoggingHandler
還有一種日誌格式,這種日誌格式輸出的日誌資訊更詳細,更全面,比如包含UA等資訊,這種格式被稱為Apache Combined Log Format。
CombinedLoggingHandler就是為我們提供輸出一種這種格式的中介軟體,使用方式和LoggingHandler一樣。
123 |
func useCombinedLoggingHandler(next http.Handler) http.Handler {return handlers.CombinedLoggingHandler(os.Stdout,next)} |
看下這個中介軟體輸出的日誌資訊
1 |
::1 - - [05/Aug/2017:21:39:45 +0800] "GET /favicon.ico HTTP/1.1" 200 11 "http://localhost:1234/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36" |
UA+HOST,日誌資訊更全面了。
CompressHandler
這是一個壓縮response的中介軟體,支援gzip和deflate壓縮。如果用戶端的要求標頭裡包含Accept-Encoding,並且值為gzip或者deflate,該中介軟體就會壓縮返回的response,這樣就可以減少response的大小,減少響應的時間,使用訪問也很簡單,這裡簡單舉個例子。
123456789101112131415161718 |
func main() {http.Handle("/gzip",useCompressHandler(handler()))http.ListenAndServe(":1234",nil)}func handler() http.Handler{return http.HandlerFunc(myHandler)}func myHandler(rw http.ResponseWriter, r *http.Request) {rw.WriteHeader(http.StatusOK)rw.Header().Set("Content-Type", "text/plain")io.WriteString(rw,"Hello World")}func useCompressHandler(next http.Handler) http.Handler {return handlers.CompressHandler(next)} |
這裡尤其注意的是我們返回的response的內容類型要特別指定一下,不然就會被自動解析為gzip,就變成下載一個gz檔案了。我這裡強制指定文本類型,這樣就可以看到返回的內容Hello World。
該中介軟體還有一個函數CompressHandlerLevel可以指定壓縮的層級,層級是gzip.BestSpeed和gzip.BestCompression之間的值,如果大家不想用預設壓縮層級,可以使用這個函數指定。
還記得Nginx可以開啟Gzip加速吧,差不多也是這麼個實現。
ContentTypeHandler
這也是一個很有意思的中介軟體,他的作用是只處理支援的內容類型,如果不支援,則返回415狀態代碼。該中介軟體只對PUT,POST,PATCH方法有效,其他方法則不做處理,也就相當於沒有使用這個中介軟體。
123 |
func useContentTypeHandler(next http.Handler) http.Handler {return handlers.ContentTypeHandler(next,"application/x-www-form-urlencoded")} |
最後一個參數是可變參數,我們可以指定多個ContextType類型,這些類型是被我們支援的,其他類型則不支援。如果我們使用PUT、POST、PATCH方法提交的請求的內容類型不在我們支援的內容類型範圍內,則返回415錯誤。
CanonicalHost
這是一個重新導向的中介軟體,他可以把一個Request請求重新定向到另外一個網域名稱上,並且會帶上原請求的Path和Query。
123456789101112131415161718 |
func main() {http.Handle("/flysnow",useCanonicalHost(handler()))http.ListenAndServe(":1234",nil)}func handler() http.Handler{return http.HandlerFunc(myHandler)}func myHandler(rw http.ResponseWriter, r *http.Request) {rw.WriteHeader(http.StatusOK)rw.Header().Set("Content-Type", "text/plain")io.WriteString(rw,"Hello World")}func useCanonicalHost(next http.Handler) http.Handler {return handlers.CanonicalHost("http://www.flysnow.org/",http.StatusFound)(next)} |
上面的樣本,當我們在瀏覽器內輸入http://localhost:1234/flysnow的訪問的時候,會自動跳轉到http://www.flysnow.org/flysnow。
小結
其他的一些不常用的中間的使用方式也類似,大家可以自己看下文檔測試下。Go Http的中介軟體,有點類似於HTTP的攔截器,通過一層層的封裝,我們可以組成一個中介軟體的處理鏈,便於我們處理我們需要處理的通用性問題,如果哪個中介軟體不想要,去掉即可,不用對業務代碼做任何修改。
例子中的示範,都是一個url對應一個中介軟體的處理,這個主要是為了示範方面。如果我們相對所有的請求都使用某個中介軟體怎麼做呢?肯定不能使用我們例子中的方式了,因為這樣會寫很多個。
對於以上這種方式,我們可以藉助HTTP Mux路由,因為一個路由也是一個Handler,只用對這個路由應用中介軟體,就可以攔截處理所有的請求了。
下一篇開始分享這些常用中介軟體的實現原理和原始碼的分析。
Go語言經典庫流量分析,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續系列。覺得有協助的話,順手分享到朋友圈吧,感謝支援。