Go Web學習(2)——實現中間件(middleware)

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

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

第一種 以類型的形式實現

上篇博客中,咱們探討過Go語言實現Web最核心的部分:bash

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源碼裏面已經幫咱們實現了這個方法。curl

// 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。ui

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.下面是完整代碼:this

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這個正確結果。好,咱們經過以函數的形式實現了上面一樣的功能,固然,這兩種方法均可行,主要看我的喜愛了,喜歡函數式編程的我仍是喜歡後者(笑)。今天就到這裏了,祝掘金越辦越好!!!

相關文章
相關標籤/搜索