Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go Gin框架路由bug

Go Gin框架路由相关bug分析

作者:cainmusic

这篇文章主要为大家介绍了Go Gin框架路由相关bug分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

注:本文原文有错误,原文不改动,但在结尾进行了勘误,注意读到文章结尾。

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的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文