Go Gin框架路由相关bug分析
作者:cainmusic
引言
注:本文原文有错误,原文不改动,但在结尾进行了勘误,注意读到文章结尾。
Gin相关版本v1.9.1
当你按如下方法注册两个路由的时候,bug会发生。
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()
上面的代码会报错:
panic: runtime error: index out of range [0] with length 0
分析
虽然构建路由树的时候,Gin本身就会主动产生很多panic,但上面这个panic显然是个意外。
这个bug由catchAll通配符的特异性导致。
catchAll通配符虽然写作*paramname
,但其构建路由树的时候会向前匹配一位/
。
因为catchAll通配符通常是为了匹配路径而存在的,catchAll通配符在gin中的经典应用就是配置静态文件服务器。
参考gin项目的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
相关函数配置静态文件服务,最后都会调用到上面的方法。
其中用你传入的relativePath
和/*filepath
组合为最终的url:relativePath/*filepath
。
假如你传入的路径是/static
,则最终url为/static/*filepath
。
这个路由会匹配所有以/static/
开头的url,并将后面的所有内容赋值到filepath
。
没错,是所有,包括后面的/
,比如html/group1/page1.html
,也就是说可以通过filepath
访问到子目录。
但上面描述的内容实际上有一个错误,你以为filepath
保存的内容是html/group1/page1.html
。
实际上是/html/group1/page1.html
。
catchAll通配符会尝试向前多匹配一个/
,如果你的路由中没有这个/
,会报错。
这个特性的特异之处导致gin中有一个bug,就是当你已经注册了/static/
路由之后,再注册/static/*file
的时候,我们会在/static/
节点上插入*filepath
而不是/*filepath
,这导致在程序判断这是一个catchAll路由后,会去向前匹配一位/
,这时i--
后,变成了负数,就导致了index out of range
的错误。
你可能会觉得报错是对的啊,那么你需要注意分清报错和bug产生的panic。
i-- if path[i] != '/' { panic("no / before catch-all in path '" + fullPath + "'") }
我说的报错产生在panic("no / before catch-all in path '" + fullPath + "'")
。
而bug产生的panic产生在i--
变为负数后查询if path[i] != '/'
时。
简单处理的话,这里应该对i
的值进行判断,然后主动panic抛出可以让人领悟的报错。
当然,为了解决这个问题本身,可以将上面的代码修改为:
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()
第一个路由不要加末尾的/
,就可以规避这个bug。
勘误
前文提到panic: runtime error: index out of range [0] with length 0
报错,但这显然不是index为负的报错,是我之前预判i--
为负时一厢情愿了。
实际上这个错误发生在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
长度实际为0,但这里却默认children
有内容。
看panic报错的内容:catch-all wildcard "path" in new path "fullPath" conflicts with existing path segment "pathSeg" in existing prefix "n.path" + "pathSeg"
。
大意上还是catchAll通配符和当前路径冲突,但这里Gin默认此时n.children
不为空的逻辑我还是没太想明白。
以上就是Go Gin框架路由相关bug分析的详细内容,更多关于Go Gin框架路由bug的资料请关注脚本之家其它相关文章!