[轉]HTTP中間件機制實現與原理 – 從零開始寫GO-API框架

 

轉,原文: http://www.guoxiaolong.cn/blog/?id=8600

 

 

-----------------

 

中間件Middleware

所謂中間件,就是連接上下級不同功能的函數或者軟件,通常進行一些包裹函數的行爲,爲被包裹函數提供添加一些功能或行爲。前文的HandleFunc就能把簽名爲 func(w http.ResponseWriter, r *http.Reqeust)的函數包裹成handler。這個函數也算是中間件。

這裏我們以HTTP請求的中間件爲例子,提供一個log中間件,能夠打印出每一個請求的log。

go的http中間件很簡單,只要實現一個函數簽名爲func(http.Handler) http.Handler的函數即可。http.Handler是一個接口,接口方法我們熟悉的爲serveHTTP。返回也是一個handler。因爲go中的函數也可以當成變量傳遞或者或者返回,因此也可以在中間件函數中傳遞定義好的函數,只要這個函數是一個handler即可,即實現或者被handlerFunc包裹成爲handler處理器。

func middlewareHandler(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
        // 執行handler之前的邏輯
        next.ServeHTTP(w, r)
        // 執行完畢handler後的邏輯
    })
}

這種方式在Elixir的Plug框架中很流行,思想偏向於函數式範式。熟悉python的朋友一定也想到了裝飾器。閒話少說,來看看go是如何實現的吧:

func loggingHandler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("Started %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Printf("Comleted %s in %v", r.URL.Path, time.Since(start))
    })
}

func main() {
    http.Handle("/", loggingHandler(http.HandlerFunc(index)))
    http.ListenAndServe(":8000", nil)
}

loggingHandler即是一箇中間件函數,將請求的和完成的時間處理。可以看見請求或go的輸出:

2016/12/04 21:18:13 Started GET /
2016/12/04 21:18:13 Comleted / in 13.365µs
2016/12/04 21:18:20 Started GET /
2016/12/04 21:18:20 Comleted / in 17.541µs

既然中間件是一種函數,並且簽名都是一樣,那麼很容易就聯想到函數一層包一層的中間件。再添加一個函數,然後修改main函數:

func hook(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("before hook")
        next.ServeHTTP(w, r)
        log.Println("after hook")

    })
}

func main() {
    http.Handle("/", hook(loggingHandler(http.HandlerFunc(index))))
    http.ListenAndServe(":8000", nil)
}

在loggingHandler再包了一層hook,可以看到輸出爲:

2016/12/04 21:26:30 before hook
2016/12/04 21:26:30 Started GET /
2016/12/04 21:26:30 Comleted / in 14.016µs
2016/12/04 21:26:30 after hook

函數調用形成了一條鏈,可以是在這條鏈上做很多事情。當然go的寫法上,比起elixir的|>的符號,優雅性略差。



作者:dongzd
鏈接:https://www.jianshu.com/p/9b081b179e71
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

---------------------

HTTP中間件機制實現與原理 – 從零開始寫GO-API框架

 

大家好,很高興您能閱讀這篇文章。

最近在投稿公衆號時發現從未做過自我介紹,首先請允許我介紹一下自己。

我叫張曉亮,就職於新浪微博,Golang的忠實粉絲,平時的愛好看看書、擼擼碼,典型的程序員性格,最近喜歡上寫博客,發現不僅能從中瞭解到很多細節,也能幫助到其他人。

引言

因golang內置的net/http天生就支持http 中間件 機制,所以即使不使用開源web框架,我們也可以寫出擴展性很好的應用。

一個好的中間件有一個責任就是可插拔並且自足,這就意味着你可以在接口級別嵌入你的中間件他就能直接運行,且不會影響你的編碼方式,這不是框架,僅僅是在請求處理裏面的一層而已。

可以想象每一箇中間件都是一層洋蔥皮,其中每一箇中間件都可以改變請求響應,我們可以很自然的把不同的邏輯放到不同的洋蔥皮裏,讓代碼更符合單一原則。

你可以使用中間件做什麼?

 1、重置HTTP請求路由
2、統一安全限制、信息上報
3、Header操作、http請求認證
4、屏蔽爬蟲
5、提供調試信息
6、請求日誌記錄

還有很多,可以自行發掘下  

 

 


 

 

中間件通常會是一小段代碼,它可以接受一個請求,對其進行處理,每個中間件只處理一件事,完成後將其傳遞給下一層或最終處理。這樣就做到了程序的 解耦 。

如果沒有中間件我們必須在最終處理程序中來完成這個邏輯操作,這無疑會造成你現有業務邏輯的臃腫和代碼複用率不高的問題.

http中間件使用案例

慣例我們來看使用中間件的案例:

測試代碼

 func  Middleware Loger() koala.HandlerFunc {
return func(ctx *koala.Context) {

fmt.Printf("%+v", ctx.Req)
fmt.Println("middloger 中間件")
// fmt.Printf("%+V 進入到中間件了\n", ctx)
}
}

func main() {
    app := koala.New()
app.Use(middlewareLoger())
app.Add("GET", "/ profile /xiaoliang", func(ctx *koala.Context) {
ctx.Text("profile.xiaoliang")
})
}
  

web請求地址: 輸出

 [KOALA-DEBUG][啓動中]
Add GET /profile/xiaoliang 
Add GET /profile/xiaoliang1 
Add GET /member/:id 
[KOALA-DEBUG]監聽端口[:8080]
[KOALA-DEBUG][服務啓動成功]

此處是中間件輸出信息
&{Method:GET URL:/profile/xiaoliang1 Proto:HTTP/1.1 ...... ctx:0xc00005c4c0}middlewareLoger 中間件

  

實現擴展HTTP中間件機制

以上講解了中間件的相關基礎知識,也看了使用案例,下面我們來實現一下中間件是如何嵌入到web框架中,並以此來實現各種解耦的功能。

先來看張圖

 

 


 

 

可以看看,如果我們把路由函數xiaoliang看做漢堡裏的肉餅,中間件函數看成麪包,那麼 middleware Loger包住了肉餅。

這裏可以實現很多層的中間件,爲了實現簡單,我們這裏就做一層。

擼代碼

準備工作完成,我們開始擼代碼吧。我比較喜歡這個環節_.

以下代碼邏輯,是按照實現先後順序展示

代碼位置:

 type Middleware interface {}

// 定義 koala引擎結構
type Koala struct {
...代碼摺疊

// 中間件
middleware []HandlerFunc

}

// 註冊中間件
func(k *Koala) Use(m ...Middleware) {
for i := range m {
if m[i] != nil {
    // 註冊中間件放入切片數組
k.middleware = append(k.middleware, warpMiddleware(m[i]))
}
}
}
  

處理不同中間件類型,在這裏我們就很容易擴展HTTP中間件機制了

目前僅實現request,後續會陸續擴展

 func warpMiddleware(m Middleware) HandlerFunc {
    // 斷言當前函數類型
switch m:= m.(type) {
case HandlerFunc:
return m
case func (*Context):
return m
default:
fmt.Printf("%+V", m)
panic("沒找到相關中間件")
}
}
  

next.ServHTTP中間件用戶態實現

 // 實現net/http 需要的servehttp服務
func (k *Koala) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

    ...代碼摺疊

// 執行相關操作
ctx.Next()

...代碼摺疊
}
  

代碼位置:

 // 首先處理中間件,然後處理路由句柄
func (c *Context) Next() {
// http中間件處理
c.middleware()

// 路由映射處理
c.koala. router ().HandlerRouter(c)
}


// 執行HTTP中間件
func (c *Context) middleware() {
for m := range c.koala.middleware{
    // c http上下文傳遞給註冊的中間件函數,這時候已經拿到了用戶側web請求數據,可以進行響應的邏輯操作
c.koala.middleware[m](c)
}
}

  

曉亮嘚吧嘚,請聽下回分解

看到這裏大夥也發現中間件代碼部分實際上不復雜,複雜是一些概念上的知識。

作者原是phper,對於中間件的瞭解基本屬於黑洞。所以這章基礎知識講解的多了一些,一自己鞏固知識點,二讓看文章的您也可以跟着我的節奏複習一下。

文章還會陸續更新,最近在研究數據庫連接池相關知識,準備利用這個框架的輪廓,我們在寫一個關於連接池相關的包。

作者本人golang相關知識有限,如講解不正確,還希望大牛們能幫忙指正,曉亮先感謝您~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章