Go?Gin框架路由相關(guān)bug分析
引言
注:本文原文有錯(cuò)誤,原文不改動(dòng),但在結(jié)尾進(jìn)行了勘誤,注意讀到文章結(jié)尾。
Gin相關(guān)版本v1.9.1
當(dāng)你按如下方法注冊(cè)兩個(gè)路由的時(shí)候,bug會(huì)發(fā)生。
r := gin.Default() r.GET("/static/", func(c *gin.Context) { c.String(200, "static") }) r.GET("/static/*file", func(c *gin.Context) { c.String(200, "static file") }) r.Run()
上面的代碼會(huì)報(bào)錯(cuò):
panic: runtime error: index out of range [0] with length 0
分析
雖然構(gòu)建路由樹(shù)的時(shí)候,Gin本身就會(huì)主動(dòng)產(chǎn)生很多panic,但上面這個(gè)panic顯然是個(gè)意外。
這個(gè)bug由catchAll通配符的特異性導(dǎo)致。
catchAll通配符雖然寫(xiě)作*paramname
,但其構(gòu)建路由樹(shù)的時(shí)候會(huì)向前匹配一位/
。
因?yàn)閏atchAll通配符通常是為了匹配路徑而存在的,catchAll通配符在gin中的經(jīng)典應(yīng)用就是配置靜態(tài)文件服務(wù)器。
參考gin項(xiàng)目的routergroup.go
文件:
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { panic("URL parameters can not be used when serving a static folder") } handler := group.createStaticHandler(relativePath, fs) urlPattern := path.Join(relativePath, "/*filepath") // Register GET and HEAD handlers group.GET(urlPattern, handler) group.HEAD(urlPattern, handler) return group.returnObj() }
一旦你使用Static
相關(guān)函數(shù)配置靜態(tài)文件服務(wù),最后都會(huì)調(diào)用到上面的方法。
其中用你傳入的relativePath
和/*filepath
組合為最終的url:relativePath/*filepath
。
假如你傳入的路徑是/static
,則最終url為/static/*filepath
。
這個(gè)路由會(huì)匹配所有以/static/
開(kāi)頭的url,并將后面的所有內(nèi)容賦值到filepath
。
沒(méi)錯(cuò),是所有,包括后面的/
,比如html/group1/page1.html
,也就是說(shuō)可以通過(guò)filepath
訪問(wèn)到子目錄。
但上面描述的內(nèi)容實(shí)際上有一個(gè)錯(cuò)誤,你以為filepath
保存的內(nèi)容是html/group1/page1.html
。
實(shí)際上是/html/group1/page1.html
。
catchAll通配符會(huì)嘗試向前多匹配一個(gè)/
,如果你的路由中沒(méi)有這個(gè)/
,會(huì)報(bào)錯(cuò)。
這個(gè)特性的特異之處導(dǎo)致gin中有一個(gè)bug,就是當(dāng)你已經(jīng)注冊(cè)了/static/
路由之后,再注冊(cè)/static/*file
的時(shí)候,我們會(huì)在/static/
節(jié)點(diǎn)上插入*filepath
而不是/*filepath
,這導(dǎo)致在程序判斷這是一個(gè)catchAll路由后,會(huì)去向前匹配一位/
,這時(shí)i--
后,變成了負(fù)數(shù),就導(dǎo)致了index out of range
的錯(cuò)誤。
你可能會(huì)覺(jué)得報(bào)錯(cuò)是對(duì)的啊,那么你需要注意分清報(bào)錯(cuò)和bug產(chǎn)生的panic。
i-- if path[i] != '/' { panic("no / before catch-all in path '" + fullPath + "'") }
我說(shuō)的報(bào)錯(cuò)產(chǎn)生在panic("no / before catch-all in path '" + fullPath + "'")
。
而bug產(chǎn)生的panic產(chǎn)生在i--
變?yōu)樨?fù)數(shù)后查詢if path[i] != '/'
時(shí)。
簡(jiǎn)單處理的話,這里應(yīng)該對(duì)i
的值進(jìn)行判斷,然后主動(dòng)panic拋出可以讓人領(lǐng)悟的報(bào)錯(cuò)。
當(dāng)然,為了解決這個(gè)問(wèn)題本身,可以將上面的代碼修改為:
r := gin.Default() r.GET("/static", func(c *gin.Context) { c.String(200, "static") }) r.GET("/static/*file", func(c *gin.Context) { c.String(200, "static file") }) r.Run()
第一個(gè)路由不要加末尾的/
,就可以規(guī)避這個(gè)bug。
勘誤
前文提到panic: runtime error: index out of range [0] with length 0
報(bào)錯(cuò),但這顯然不是index為負(fù)的報(bào)錯(cuò),是我之前預(yù)判i--
為負(fù)時(shí)一廂情愿了。
實(shí)際上這個(gè)錯(cuò)誤發(fā)生在i--
的上面幾行:
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0] panic("catch-all wildcard '" + path + "' in new path '" + fullPath + "' conflicts with existing path segment '" + pathSeg + "' in existing prefix '" + n.path + pathSeg + "'") } // currently fixed width 1 for '/' i-- if path[i] != '/' { panic("no / before catch-all in path '" + fullPath + "'") }
這一行:pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0]
。
這里的children
長(zhǎng)度實(shí)際為0,但這里卻默認(rèn)children
有內(nèi)容。
看panic報(bào)錯(cuò)的內(nèi)容:catch-all wildcard "path" in new path "fullPath" conflicts with existing path segment "pathSeg" in existing prefix "n.path" + "pathSeg"
。
大意上還是catchAll通配符和當(dāng)前路徑?jīng)_突,但這里Gin默認(rèn)此時(shí)n.children
不為空的邏輯我還是沒(méi)太想明白。
以上就是Go Gin框架路由相關(guān)bug分析的詳細(xì)內(nèi)容,更多關(guān)于Go Gin框架路由bug的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解go程序如何在windows服務(wù)中開(kāi)啟和關(guān)閉
這篇文章主要介紹了一個(gè)go程序,如何在windows服務(wù)中優(yōu)雅開(kāi)啟和關(guān)閉,文中通過(guò)代碼示例和圖文講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-07-07如何利用Go語(yǔ)言實(shí)現(xiàn)LRU?Cache
這篇文章主要介紹了如何利用Go語(yǔ)言實(shí)現(xiàn)LRU?Cache,LRU是Least?Recently?Used的縮寫(xiě),是一種操作系統(tǒng)中常用的頁(yè)面置換算法,下面我們一起進(jìn)入文章了解更多內(nèi)容吧,需要的朋友可以參考一下2022-03-03如何在Go語(yǔ)言中靈活運(yùn)用匿名函數(shù)和閉包
這篇文章主要為大家介紹了如何在Go語(yǔ)言中靈活運(yùn)用匿名函數(shù)和閉包實(shí)現(xiàn)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10Go語(yǔ)言單線程運(yùn)行也會(huì)有的并發(fā)問(wèn)題解析
這篇文章主要為大家介紹了Go語(yǔ)言單線程運(yùn)行的并發(fā)問(wèn)題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12淺析Golang中調(diào)度器的關(guān)鍵機(jī)制與性能
Golang的調(diào)度器是其并發(fā)模型的核心組件,負(fù)責(zé)管理Goroutine的調(diào)度和執(zhí)行,本文將從理論和代碼層面分析Golang調(diào)度器的關(guān)鍵機(jī)制,感興趣的可以了解下2025-03-03go語(yǔ)言之給定英語(yǔ)文章統(tǒng)計(jì)單詞數(shù)量(go語(yǔ)言小練習(xí))
這篇文章給大家分享go語(yǔ)言小練習(xí)給定英語(yǔ)文章統(tǒng)計(jì)單詞數(shù)量,實(shí)現(xiàn)思路大概是利用go語(yǔ)言的map類型,以每個(gè)單詞作為關(guān)鍵字存儲(chǔ)數(shù)量信息,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2020-01-01