[TOC]node
咱們在github
上看看官方簡介git
Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to httprouter. If you need performance and good productivity, you will love Gin.
Gin 是用 Go 開發的一個微框架,Web框架,相似 Martinier 的 API,接口簡潔,性能極高,也由於 httprouter的性能提升了 40 倍。github
若是你須要良好的表現和工做效率,你會喜歡Gin
。web
tag | 說明 |
---|---|
異常處理 | 服務始終可用,不會宕機 。Gin 能夠捕獲 panic,並恢復。並且有極爲便利的機制處理HTTP請求過程當中發生的錯誤。 |
路由分組 | 能夠將須要受權和不須要受權的API分組,不一樣版本的API分組。 並且分組可嵌套,且性能不受影響。 例如v1/xxx/xxx v2/xxx/xxx |
渲染內置 | 原生支持JSON,XML和HTML 的渲染。 |
JSON | Gin 能夠解析並驗證請求的JSON。這個特性對 Restful API 的開發尤爲有用。 |
中間件 | HTTP 請求,可先通過一系列中間件處理就向日志Logger,Authorization等。 中間件機制也極大地提升了框架的可擴展性。 |
gin的實戰演練咱們以前也有分享過,咱們再來回顧一下,gin大體都包含了哪些知識點算法
:路由
和*路由
要是朋友們對gin還有點興趣的話,能夠點進來看看,這裏有具體的知識點對應的案例gin實戰演練json
咱們再來了解一下路由是什麼數組
路由器是一種鏈接多個網絡或網段的網絡設備,它能將不一樣網絡或網段之間的數據信息進行「翻譯」,以使它們可以相互「讀」懂對方的數據,從而構成一個更大的網絡。
路由器有兩大典型功能網絡
包括轉發決定、背板轉發以及輸出鏈路調度等,通常由特定的硬件來完成數據結構
通常用軟件來實現,包括與相鄰路由器之間的信息交換、系統配置、系統管理等app
路由是web框架的核心功能。
寫過路由的朋友最開始是否是這樣看待路由的:
/
把路由切分紅多個字符串數組當須要尋址的時候,先把請求的 url
按照 /
切分,而後遍歷樹進行尋址,這樣子有點像是深度優先算法
的遞歸遍歷,從根節點開始,不停的向根的地方進行延伸,知道不能再深刻爲止,算是獲得了一條路徑
舉個栗子
定義了兩個路由 /v1/hi
,/v1/hello
那麼這就會構造出擁有三個節點的路由樹,根節點是 v1
,兩個子節點分別是 hi
hello
。
上述是一種實現路由樹的方式,這種是比較直觀,容易理解的。對 url 進行切分、比較,但是時間複雜度是 O(2n)
,那麼咱們有沒有更好的辦法優化時間複雜度呢?大名鼎鼎的GIn框架有辦法,日後看
再來提一提算法是啥。
算法是解決某個問題的計算方法、步驟,不只僅是有了計算機纔有算法這個名詞/概念的,
例如咱們小學學習的九九乘法表
中學學習的各類解決問題的計算方法,例如物理公式等等
如今各類吃播大秀廚藝,作法的流程和方法也是算法的一種
大學裏面學過算法這本書,算法是計算機的靈魂,面臨問題,好的算法可以輕易應對且健壯性好
面臨人生難題,好的解決方式,也一樣可以讓咱們走的更遠,更確切有點來講,應該是好的思惟模型。
算法有以下五大特徵
每一個事物都會有本身的特色,不然如何才能讓人記憶深入呢
那咱們開始進入進入正題,gin的路由算法,千呼萬喚始出來
gin的是路由算法相似於一棵前綴樹
只需遍歷一遍字符串便可,時間複雜度爲O(n)
。比上面提到的方式,在時間複雜度上來講真是大大滴優化呀
不過,僅僅是對於一次 http 請求來講,是看不出啥效果的
誒,敲黑板了,什麼叫作前綴樹呢?
Trie樹,又叫 字典樹、 前綴樹(Prefix Tree),是一種多叉樹結構
畫個圖,大概就能明白前綴樹是個啥玩意了
這棵樹還和二叉樹不太同樣,它的鍵不是直接保存在節點中,而是由節點在樹中的位置決定
一個節點的全部子孫都有相同的前綴,也就是這個節點對應的字符串,而根節點對應空字符串。
例如上圖,咱們一個一個的來尋址一下,會有這樣的字符串
前綴樹有以下幾個特色:
有沒有以爲這個和路由的樹一毛同樣?
gin的路由樹算法相似於一棵前綴樹. 不過並非只有一顆樹, 而是每種方法(POST, GET ,PATCH...)都有本身的一顆樹
例如,路由的地址是
那麼gin對應的樹會是這個樣子的
GO中 路由對應的節點數據結構是這個樣子的
type node struct { path string indices string children []*node handlers HandlersChain priority uint32 nType nodeType maxParams uint8 wildChild bool }
具體添加路由的方法,實現方法是這樣的
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { assert1(path[0] == '/', "path must begin with '/'") assert1(method != "", "HTTP method can not be empty") assert1(len(handlers) > 0, "there must be at least one handler") debugPrintRoute(method, path, handlers) // 此處能夠好好看看 root := engine.trees.get(method) if root == nil { root = new(node) engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) }
仔細看,gin的實現不像一個真正的樹
由於他的children []*node全部的孩子都會放在這個數組裏面,具體實現是,他會利用indices, priority變相的去實現一棵樹
咱們來看看不一樣註冊路由的方式有啥不一樣?每一種註冊方式,最終都會反應到gin的路由樹上面
普通註冊路由的方式是 router.xxx
,能夠是以下方式
router.POST("/hi", func(context *gin.Context) { context.String(http.StatusOK, "hi xiaomotong") })
也能夠以組Group
的方式註冊,以分組的方式註冊路由,便於版本的維護
v1 := router.Group("v1") { v1.POST("hello", func(context *gin.Context) { context.String(http.StatusOK, "v1 hello world") }) }
在調用POST, GET, PATCH
等路由HTTP相關函數時, 會調用handle
函數
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) // calculateAbsolutePath handlers = group.combineHandlers(handlers) // combineHandlers group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
calculateAbsolutePath
和 combineHandlers
還會再次出現
調用組的話,看看是咋實現的
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { return &RouterGroup{ Handlers: group.combineHandlers(handlers), basePath: group.calculateAbsolutePath(relativePath), engine: group.engine, } }
一樣也會調用 calculateAbsolutePath
和 combineHandlers
這倆函數,咱們來看看 這倆函數是幹啥的,看到函數名字,也許大概也能猜出個因此然了吧,來看看源碼
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) if finalSize >= int(abortIndex) { panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string { return joinPaths(group.basePath, relativePath) } func joinPaths(absolutePath, relativePath string) string { if relativePath == "" { return absolutePath } finalPath := path.Join(absolutePath, relativePath) appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/' if appendSlash { return finalPath + "/" } return finalPath }
joinPaths
函數在這裏至關重要,主要是作拼接的做用
從上面來看,能夠看出以下2點:
咱們也可使用中間件的方式來註冊路由,例如在訪問咱們的路由以前,咱們須要加一個認證的中間件放在這裏,必需要認證經過了以後,才能夠訪問路由
router.Use(Login())
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() }
不論是普通的註冊,仍是經過中間件的方式註冊,裏面都有一個關鍵的handler
handler
方法 調用 calculateAbsolutePath
和 combineHandlers
將路由拼接好以後,調用addRoute
方法,將路由預處理的結果註冊到gin Engine的trees上,來在看讀讀handler的實現
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) // <--- handlers = group.combineHandlers(handlers) // <--- group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
那麼,服務端寫好路由以後,咱們經過具體的路由去作http請求的時候,服務端是如何經過路由找到具體的處理函數的呢?
咱們仔細追蹤源碼, 咱們能夠看到以下的實現
... // 一棵前綴樹 t := engine.trees for i, tl := 0, len(t); i < tl; i++ { if t[i].method != httpMethod { continue } root := t[i].root // Find route in tree // 這裏經過 path 來找到相應的 handlers 處理函數 handlers, params, tsr := root.getValue(path, c.Params, unescape) if handlers != nil { c.handlers = handlers c.Params = params // 在此處調用具體的 處理函數 c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && path != "/" { if tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return } if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } break } ...
func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } }
當客戶端請求服務端的接口時, 服務端此處 handlers, params, tsr := root.getValue(path, c.Params, unescape)
, 經過 path 來找到相應的 handlers 處理函數,
將handlers
, params
複製給到服務中,經過 c.Next()
來執行具體的處理函數,此時就能夠達到,客戶端請求響應的路由地址,服務端能過對響應路由作出對應的處理操做了
好了,本次就到這裏,下一次 分享最經常使用的限流算法以及如何在http中間件中加入流控,
技術是開放的,咱們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。
我是小魔童哪吒,歡迎點贊關注收藏,下次見~