天天看點

Go語言的Http 中間件實作

當你正在建構一個web應用程式有可能要運作許多(甚至全部)的http請求一些共享功能,你可能想記錄每一個request,gzip壓縮的每個response,或者做一些繁重的處理或者緩存檢查。

實作這個共享功能的一種方法是将其設定為中間件,他可以作為一個獨立的程式,在正常的handlers處理之前。根本不需要重寫代碼:如果你想用一個中間件,就把它加上應用中;如果你改變主意了,去掉就好了。就這麼簡單。

servemux => middleware handler => application handler 

這篇文章,我會給大家介紹怎麼自己去實作一個自定義的middleware模式。以及通過使用第三方的中間件軟體包的一些具體的執行個體。

基本原則:

在go語言中實作和使用middleware是非常簡單的。

使我們的中間件能搞滿足 http.handlers 這個接口

建立一個 handlers 鍊,使其能夠滿足中間件的 handler 和 正常應用的 handler,并且能夠注冊到 http.servemux

我來解釋如何實作:

首先你要知道go 的http handle,這裡假設你是知道的

func messagehandler(message string) http.handler { 

return http.handlerfunc(func(w http.responsewriter, r *http.request) { 

w.write([]byte(message) 

}) 

這上面這個代碼片段裡面我們的邏輯很簡單隻是一個簡單的 w.write() 然後我們使用 http.handlerfunc 擴充卡來轉化這個閉包,并傳回。

我們可以使用一個相同的方法來建立一個 handler 鍊。可以使用 handler 代替參數 string 傳進閉包,然後把控制 handler 給傳進來的 handler,并且調用 servehttp() 方法。

這給了我們一個完整的模式建構中間件:

func examplemiddleware(next http.handler) http.handler { 

// our middleware logic goes here... 

next.servehttp(w, r) 

你注意到這個中間件有一個這樣的函數結構 func(http.handler) http.handler 。它接受一個 handler 作為參數,并且傳回一個 handler。這裡有兩個很有用的原因:

因為這個函數傳回一個句柄可以直接供中間件注冊

我們可以建立任意長度的 handler 鍊來通過中間件的方法互相嵌套

比如:

http.handle("/", middlewareone(middlewaretwo(finalhandler))) 

控制流說明:

讓我們來看一個帶有多個中間件的例子,并且把日志輸出到控制台:

package main 

import ( 

"log" 

"net/http" 

func middlewareone(next http.handler) http.handler { 

log.println("executing middlewareone") 

log.println("executing middlewareone again") 

func middlewaretwo(next http.handler) http.handler { 

log.println("executing middlewaretwo") 

if r.url.path != "/" { 

return 

log.println("executing middlewaretwo again") 

func final(w http.responsewriter, r *http.request) { 

log.println("executing finalhandler") 

w.write([]byte("ok")) 

func main() { 

finalhandler := http.handlerfunc(final) 

http.listenandserve(":3000", nil) 

然後我們執行 go run main.go 在浏覽器打開http://localhost:3000。 你會看到下面的輸出。

Go語言的Http 中間件實作

我們能夠很清楚的看到handle的流程控制。我們嵌套他們的傳回順序。我們可以通過中間件中得 return 随時停止handle鍊的控制。

在上面的代碼中我們在middlewaretwo function包含了retrun 語句。我們在浏覽器中打開http://localhost:3000/foo,我們會看到。

2015/12/19 04:21:57 executing middlewareone 

2015/12/19 04:21:57 executing middlewaretwo 

2015/12/19 04:21:57 executing middlewareone again 

我們實作一個真實的項目的示例:

我們實作一個判斷請求是不是xml的功能,我們要實作一個中間件。用來檢查的請求體的存在。檢查請求體,以確定它是xml。如果其中檢查失敗,我希望我們的中間件輸出錯誤資訊然後終止我們的handle處理。

"bytes" 

func enforcexmlhandler(next http.handler) http.handler { 

// check for a request body 

if r.contentlength == 0 { 

http.error(w, http.statustext(400), 400) 

// check its mime type 

buf := new(bytes.buffer) 

buf.readfrom(r.body) 

if http.detectcontenttype(buf.bytes()) != "text/xml; charset=utf-8" { 

http.error(w, http.statustext(415), 415) 

http.handle("/", enforcexmlhandler(finalhandler)) 

為了檢驗我們的中間件是否實作了這個功能,我們首先建立一個xml檔案。

$ cat > books.xml  

h. g. wells  

8.50   

然後通過使用curl來進行模拟請求:

$ curl -i localhost:3000 

http/1.1 400 bad request 

content-type: text/plain; charset=utf-8 

content-length: 12 

bad request 

$ curl -i -d "this is not xml" localhost:3000 

http/1.1 415 unsupported media type 

content-length: 23 

unsupported media type 

$ curl -i -d @books.xml localhost:3000 

http/1.1 200 ok 

date: fri, 17 oct 2014 13:42:10 gmt 

content-length: 2 

ok 

接下來給大家介紹一下第三方中間件的使用:

秉承不造輪子的原則,其實在github上有很多實作了一些功能的中間件。比如這裡給大家介紹2個基礎驗證的中間件goji/httpauth和gorilla’s logginghandler

首先我們需要引入第三方包

$ go get github.com/goji/httpauth 

"github.com/goji/httpauth" 

authhandler := httpauth.simplebasicauth("username", "password") 

http.handle("/", authhandler(finalhandler)) 

如果你運作這個例子,你應該得到你所期望的有效和無效的憑證響應

$ curl -i username:password@localhost:3000 

$ curl -i username:wrongpassword@localhost:3000 

http/1.1 401 unauthorized 

www-authenticate: basic realm=""restricted"" 

content-length: 13 

unauthorized 

gorilla’s logginghandler和apache-style logs有一些差別

以下是我們在其中寫入日志到server.log檔案一個簡單的例子:

首先還是引入第三包

go get github.com/gorilla/handlers 

"github.com/gorilla/handlers" 

"os" 

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, finalhandler)) 

在一個簡單的情況下,這樣我們的代碼是相當清楚的。但是,如果我們想用logginghandler作為一個更大的中間件鍊中的一部分會發生什麼?我們可以很容易地結束了一個聲明,看起來像這樣:

http.handle("/", handlers.logginghandler(logfile, authhandler(enforcexmlhandler(finalhandler)))) 

不過這看起來太糟糕了。

我們可以通過建立一個構造函數打來整理一下我們給它取名為(mylogginghandler)

和signature func(http.handler) http.handler.這樣就會是我們的代碼更加整潔和可讀性:

func mylogginghandler(h http.handler) http.handler { 

return handlers.logginghandler(logfile, h) 

http.handle("/", mylogginghandler(finalhandler)) 

$ cat server.log 

127.0.0.1 - - [21/oct/2014:18:56:43 +0100] "get / http/1.1" 200 2 

127.0.0.1 - - [21/oct/2014:18:56:36 +0100] "post / http/1.1" 200 2 

127.0.0.1 - - [21/oct/2014:18:56:43 +0100] "put / http/1.1" 200 2 

這裡還有一個比較完整結構的中間件使用的示例:

logfile, err := os.openfile("server.log", os.o_rdwr|os.o_create|os.o_append, 0666) 

indexhandler := http.handlerfunc(index) 

http.handle("/", mylogginghandler(authhandler(enforcexmlhandler(indexhandler)))) 

func index(w http.responsewriter, r *http.request) { 

有很多人不太喜歡中間件的設計模式,不過我還是慢喜歡的。

作者:何妍 

來源:51cto

繼續閱讀