Go Web學習(2)——實現中介軟體(middleware)

PedroGao發表於2018-01-24

昨天我們探討了Go語言使用標準庫實現簡單的web版的HelloWorld,大致瞭解了Go實現server應用的流程,今天我們來探討一下用Go語言實現http的Middleware。

我們知道,絕大部分web應用會將邏輯與功能的實現寫在middleware裡面更整個結構更加分明,middleware大致在web應用裡面大致分為兩種,即處理response和處理request(個人拙見,如有錯誤請以指正),接下來我將以處理request請求的形式來實現兩種middleware的寫法。

第一種 以型別的形式實現

上篇部落格中,我們探討過Go語言實現Web最核心的部分:

http.ListenAndServe(":8000", handler)
複製程式碼

http包裡面的ListenAndServe函式接受兩個引數,即監聽地址和處理介面handler,handler是一個介面,我們需要實現這個介面中的唯一方法ServeHTTP便可以實現上述的函式,因此我們處理的整個邏輯和流程都會在這個handler裡面,下面我們先來看一個最簡單的handler實現。

package main

import (
	"net/http"
)

func myHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
}

func main() {
	http.ListenAndServe(":8000", http.HandlerFunc(myHandler))
}
複製程式碼

上面的程式碼中我們定義一個myHandler,它接受http.ResponseWriter,*http.Request兩個引數然後再向ResponseWriter中寫入Hello World,在main函式中,我們直接使用了ListenAndServe方法監聽本地的8000埠,注意由於Go語言的強型別性,ListenAndServe的第二個引數型別是Handler,因此我們想要將myHandler傳遞給ListenAndServe就必須實現ServeHTTP這個方法。但其實Go原始碼裡面已經幫我們實現了這個方法。

// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}
複製程式碼

可以看到,Go語言將func(ResponseWriter, *Request)這種型別的函式直接定義了型別HandlerFunc,而且還實現了ServeHTTP這個方法,但是這個方法本身並沒有實現任何邏輯,需要我們自己來實現。因此我們實現了myHandler這個方法,它將輸出一個最簡單的HelloWorld響應。隨後我們可以用curl來測試一下:

$ curl localhost:8000
Hello World
複製程式碼

可以看到,我們通過curl請求本地的8000埠,返回我們一個HelloWorld。這便是一個最簡單的Handler實現了。但是我們的目標是實現中介軟體,有了上述的所採用的的方法我們就可以大致明白,myHandler應該作為最後的呼叫,在它之前才是中介軟體應該作用的地方,那麼我們有了一個大致的方向,我們可以實現一個邏輯用來包含這個myHandler,但它本身也必須實現Handler這個介面,因為我們要把它傳遞給ListenAndServe這個方法。好,我們先大致闡述一下這個中介軟體的作用,它會攔截一切請求除了這個請求的host是我們想要的host,當然這個host有我們定義。

type SingleHost struct {
	handler     http.Handler
	allowedHost string
}
複製程式碼

於是我們定義了一個SingleHost的結構體,它裡面有兩個成員一個是Handler,它將是我們上述的myHandler,另一個是我們允許來請求Server的使用者,這個使用者他有唯一的Host,只有當他的Host滿足我們的要求是才讓他請求成功,否則一律返回403。

因為我們需要將這個SingleHost例項化並傳遞給ListenAndServe這個方法,因此它必須實現ServeHTTP這個方法,所以在ServeHTTP裡面可以直接定義我們用來實現中介軟體的邏輯。即除非來請求的使用者的Host是allowedHost否則一律返回403。

func (this *SingleHost) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if (r.Host == this.allowedHost) {
		this.handler.ServeHTTP(w, r)
	} else {
		w.WriteHeader(403)
	}
}
複製程式碼

好,可以清楚的看到只有Request的Host==allowedHost的時候,我們才呼叫handler的ServeHTTP方法,否則返回403.下面是完整程式碼:

package main

import (
	"net/http"
)

type SingleHost struct {
	handler     http.Handler
	allowedHost string
}

func (this *SingleHost) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if (r.Host == this.allowedHost) {
		this.handler.ServeHTTP(w, r)
	} else {
		w.WriteHeader(403)
	}
}

func myHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
}

func main() {
	single := &SingleHost{
		handler:http.HandlerFunc(myHandler),
		allowedHost:"refuse.com",
	}
	http.ListenAndServe(":8000", single)
}

複製程式碼

然後我們用curl來請求本地的8000埠,

$ curl --head localhost:8000
HTTP/1.1 403 Forbidden
Date: Sun, 21 Jan 2018 08:32:47 GMT
Content-Type: text/plain; charset=utf-8
複製程式碼

可以看到我們在中介軟體中實現了只允許host為refuse.com來訪問的邏輯實現了,由於curl的Host是localhost所以我們的伺服器直接返回了它一個403。接下來我們改變一下allowedHost

allowedHost:"localhost:8000",
複製程式碼

我們將allowedHost變成為localhost:8000,然後用curl測試

$ curl localhost:8000
Hello World
複製程式碼

可以看到curl通過了中介軟體的並直接獲得了myHandler返回的HelloWorld。

第二種 以函式的形式實現

好,在上面我們實現了以型別為基礎的中介軟體,可能對Node.js較熟悉的人都習慣以函式的形式實現中介軟體。首先,因為我們是以函式來實現中介軟體的因此這個函式返回的便是Handler,它會接受兩個引數,一個是我們定義的myHandler,一個是allowedHost。

func SingleHost(handler http.Handler, allowedHost string) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		if r.Host == allowedHost {
			handler.ServeHTTP(w, r)
		} else {
			w.WriteHeader(403)
		}
	}
	return http.HandlerFunc(fn)
}
複製程式碼

可以看到,我們在函式內部定義可一個匿名函式fn,這個匿名函式便是我們要返回的Handler,如果請求使用者的Host滿足allowedHost,便可以將呼叫myHandler的函式返回,否則直接返回一個操作403的函式。整個程式碼如下:

package main

import "net/http"

func SingleHost(handler http.Handler, allowedHost string) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		if r.Host == allowedHost {
			handler.ServeHTTP(w, r)
		} else {
			w.WriteHeader(403)
		}
	}
	return http.HandlerFunc(fn)
}

func myHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
}

func main() {
	single := SingleHost(http.HandlerFunc(myHandler), "refuse.com")
	http.ListenAndServe(":8000", single)
}
複製程式碼

我們還是通過curl來測試一下

$ curl --head localhost:8000
HTTP/1.1 403 Forbidden
Date: Sun, 21 Jan 2018 08:45:39 GMT
Content-Type: text/plain; charset=utf-8
複製程式碼

可以看到由於不滿足refuse.com的條件,我們會得到一個403,讓我們將refuse.com改為localhost:8000測試一下。

$ curl localhost:8000
Hello World
複製程式碼

與剛才一樣我們得到了HelloWorld這個正確結果。好,我們通過以函式的形式實現了上面同樣的功能,當然,這兩種方法都可行,主要看個人喜好了,喜歡函數語言程式設計的我還是喜歡後者(笑)。今天就到這裡了,祝掘金越辦越好!!!

相關文章