改造httprouter使其支援中介軟體

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。# 改造httprouter使其支援中介軟體## 寫在前面`httprouter`在業界廣受好評,主要就是因為它的效能`httprouter`項目地址:[httprouter](https://github.com/julienschmidt/httprouter)`httprouter`的原理:[點這裡點這裡~](http://www.okyes.me/2016/05/08/httprouter.html)而`httprouter`預設是不支援中介軟體等功能的`README`中說:`Where can I find Middleware X?This package just provides a very efficient request router with a few extra features. The router is just a http.Handler, you can chain any http.Handler compatible middleware before the router, for example the Gorilla handlers. Or you could just write your own, it's very easy!` 而我目前在寫的Bingo架構也是基於`httprouter`的,所以準備對它進行改造,讓他支援中介軟體的功能我的項目地址[silsuer/bingo](https://github.com/silsuer/bingo)## 開始改造 1. 原理 查看httprouter的原始碼,可以看到,它使用一個首碼樹來管理註冊的路由,而掛載到這棵樹上的,是一個`Handle`類型的方法, 這個方法長這樣`type Handle func(http.ResponseWriter, *http.Request, Params)` 使用`httprouter.Handle(args)`方法,會將路由根據路徑放置在這棵樹中, 在每一個http請求進來的時候,會走到`ServeHttp`方法中,這個方法就是一個多路的路由器,代碼如下: ```go func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { // 判斷panic函數 if r.PanicHandler != nil { defer r.recv(w, req) } // 開始去尋找註冊的路由函數 if root := r.trees[req.Method]; root != nil { path := req.URL.Path if handle, ps, tsr := root.getValue(path); handle != nil { // 尋找到了,執行這個函數 handle(w, req, ps) return } else if req.Method != "CONNECT" && path != "/" { // 沒有找到,開始進行重新導向或者其他動作 code := 301 // Permanent redirect, request with GET method if req.Method != "GET" { // Temporary redirect, request with same method // As of Go 1.3, Go does not support status code 308. code = 307 } if tsr && r.RedirectTrailingSlash { if len(path) > 1 && path[len(path)-1] == '/' { req.URL.Path = path[:len(path)-1] } else { req.URL.Path = path + "/" } http.Redirect(w, req, req.URL.String(), code) return } // Try to fix the request path if r.RedirectFixedPath { fixedPath, found := root.findCaseInsensitivePath( CleanPath(path), r.RedirectTrailingSlash, ) if found { req.URL.Path = string(fixedPath) http.Redirect(w, req, req.URL.String(), code) return } } } } // Handle 405 if r.HandleMethodNotAllowed { for method := range r.trees { // Skip the requested method - we already tried this one if method == req.Method { continue } handle, _, _ := r.trees[method].getValue(req.URL.Path) if handle != nil { if r.MethodNotAllowed != nil { r.MethodNotAllowed(w, req) } else { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed, ) } return } } } // 如果定義了NotFound函數的話,在尋找不成功的時候會執行這個函數,否則執行預設的NotFound方法 // Handle 404 if r.NotFound != nil { r.NotFound(w, req) } else { http.NotFound(w, req) } } ``` 而所謂中介軟體,就是在尋找成功之後,首先執行中介軟體的方法,然後再執行handle方法,那麼我們的思路就有了 不再在tree上掛載handle方法,而是掛載一個我們的自訂的結構體,當尋找成功的時候,先查看這個結構體是否有中介軟體 如果有執行,如果沒有,直接執行handle方法 2. 自訂結構體 以前我寫的兩篇文章裡 [使用Go寫一個簡易的MVC的Web架構](https://studygolang.com/articles/12818) [使用Go封裝一個便捷的ORM](https://studygolang.com/articles/12825) 也介紹過,我們的路由結構體是這樣的: ```go type Route struct { Path string // 路徑 Target Handle // 對應的控制器路徑 Controller@index 這樣的方法 Method string // 訪問類型 是get post 或者其他 Alias string // 路由的別名,並沒有什麼卵用的樣子....... Middleware []Handle // 中介軟體名稱 } ``` 其中的Handle是使用上面`httprouter`定義的方法, 接下來我們改造一下這個結構體 ```go // 上下文結構體 type Context struct { Writer http.ResponseWriter // 響應 Request *http.Request // 請求 Params Params //參數 } type TargetHandle func(context *Context) type MiddlewareHandle func(context *Context) *Context // 中介軟體需要把上下文返回回來,用來傳入TargetHandle中 type Route struct { Path string // 路徑 Target TargetHandle // 要執行的方法 Method string // 訪問類型 是get post 或者其他 Alias string // 路由的別名,並沒有什麼卵用的樣子....... Middleware []MiddlewareHandle // 中介軟體名稱,在執行TargetHandle之前執行的方法 } ``` 我們將原來Handle中的參數都裝入一個上下文結構體中,然後在Route結構體中指明函數的類型 3. 把Route結構體掛載到Tree上 查看`httprouter`的註冊路由的方法: ```go func (r *Router) Handle(method, path string, handle Handle) { // 判斷路徑的格式是否正確 if path[0] != '/' { panic("path must begin with '/' in path '" + path + "'") } // 如果首碼樹是空的,就建立一顆 if r.trees == nil { r.trees = make(map[string]*node) } root := r.trees[method] // 如果樹的根節點為空白,就建立一個根節點 if root == nil { root = new(node) r.trees[method] = root } // 根據路徑,把要執行的方法掛載到樹上 root.addRoute(path, handle) } ``` 現在我們要把其中的Handle類型的資料都改成我們自己的Route類型, 很簡單,代碼就不貼了,想看的請看[commit變更記錄](https://github.com/silsuer/bingo/commit/b651757e328b9a711ad2fe274ece8326c954d762) 接下來更改整個tree檔案,實際上就是把`tree`的`node`結構體中的`handle`改為route 然後將tree檔案中用到`handle`的地方,都用`n.route.Target`代替,雖然是無腦操作,但是要改不少行 也不貼代碼了... commit記錄在這裡... [commit變更記錄](https://github.com/silsuer/bingo/commit/b651757e328b9a711ad2fe274ece8326c954d762) 改完之後的 `ServeHttp`是這樣滴~ ```go func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { if r.PanicHandler != nil { defer r.recv(w, req) } // 在尋找之前,要先看看是否存在中介軟體 // 註冊路由的時候,應該把中介軟體也放在此處 // 開始去尋找註冊的路由函數 if root := r.trees[req.Method]; root != nil { path := req.URL.Path if route, ps, tsr := root.getValue(path); route.Target != nil { // 封裝上下文 context := &Context{w,req,ps} // 執行目標函數 route.Target(context) return } else if req.Method != "CONNECT" && path != "/" { code := 301 // Permanent redirect, request with GET method if req.Method != "GET" { // Temporary redirect, request with same method // As of Go 1.3, Go does not support status code 308. code = 307 } if tsr && r.RedirectTrailingSlash { if len(path) > 1 && path[len(path)-1] == '/' { req.URL.Path = path[:len(path)-1] } else { req.URL.Path = path + "/" } http.Redirect(w, req, req.URL.String(), code) return } // Try to fix the request path if r.RedirectFixedPath { fixedPath, found := root.findCaseInsensitivePath( CleanPath(path), r.RedirectTrailingSlash, ) if found { req.URL.Path = string(fixedPath) http.Redirect(w, req, req.URL.String(), code) return } } } } // Handle 405 if r.HandleMethodNotAllowed { for method := range r.trees { // Skip the requested method - we already tried this one if method == req.Method { continue } route, _, _ := r.trees[method].getValue(req.URL.Path) if route.Target != nil { if r.MethodNotAllowed != nil { r.MethodNotAllowed(w, req) } else { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed, ) } return } } } // Handle 404 if r.NotFound != nil { r.NotFound(w, req) } else { http.NotFound(w, req) } } ``` 可以看到,在尋找到節點後,我封裝了一個`Context`,接下來執行了TargetHandle方法, 4.更改代碼,支援中介軟體: ```go // 封裝上下文 context := &Context{w,req,ps} // 判斷路由是否有中介軟體列表,如果有,就執行 if len(route.Middleware)!=0{ for _,middleHandle:= range route.Middleware{ context = middleHandle(context) // 順序執行中介軟體,得到的返回結果重新注入到上下文中 } } // 執行目標函數 route.Target(context) return ``` 現在,我們定義如下一個路由: ```go var R = []bingo.Route{ { Path: "/home", Method: bingo.GET, Target: home.Index, Middleware: []bingo.MiddlewareHandle{ home.M1, home.M2, }, }, } ``` 其中, Index,M1,M2定義如下: ```go func M1(c *bingo.Context) *bingo.Context { fmt.Fprintln(c.Writer,"這是中介軟體1") return c } func M2(c *bingo.Context) *bingo.Context { fmt.Fprintln(c.Writer,"這是中介軟體2") return c } func Index(c *bingo.Context) { fmt.Fprint(c.Writer,"Hello World") } ``` 然後執行 `go run start.go` ,瀏覽器訪問 `localhost:12345` ,就可以看到中介軟體執行成功的痕迹了, 改造成功 Bingo! 歡迎star,歡迎PR~~~~484 次點擊  
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.