Go Gin框架路由相关bug分析

 更新时间:2023年12月14日 09:40:02   作者: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的资料请关注脚本之家其它相关文章!

相关文章

  • Golang中crypto/ecdsa库实现数字签名和验证

    Golang中crypto/ecdsa库实现数字签名和验证

    本文主要介绍了Golang中crypto/ecdsa库实现数字签名和验证,将从ECDSA的基本原理出发,详细解析如何在Go语言中实现数字签名和验证,具有一定的参考价值,感兴趣的可以了解一下
    2024-02-02
  • 基于go微服务效率工具goctl深度解析

    基于go微服务效率工具goctl深度解析

    这篇文章主要为大家介绍了基于go微服务效率工具goctl深度解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Go使用缓存加速外部资源访问提高性能效率

    Go使用缓存加速外部资源访问提高性能效率

    缓存是架构设计中的常用概念,本文基于Go实现了一个简单的缓存组件,支持最基本的缓存操作,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • 解决电脑用GoLand太卡将VsCode定制成Go IDE步骤过程

    解决电脑用GoLand太卡将VsCode定制成Go IDE步骤过程

    这篇文章主要为大家介绍了解决电脑用GoLand太卡,将VsCode定制成Go IDE步骤过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • Golang 语言控制并发 Goroutine的方法

    Golang 语言控制并发 Goroutine的方法

    本文我们介绍了不同场景中分别适合哪种控制并发 goroutine 的方式,其中,channel 适合控制少量 并发 goroutine,WaitGroup 适合控制一组并发 goroutine,而 context 适合控制多级并发 goroutine,感兴趣的朋友跟随小编一起看看吧
    2021-06-06
  • 浅谈Go连接池的设计与实现

    浅谈Go连接池的设计与实现

    本文主要介绍了浅谈Go连接池的设计与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Go与C语言的互操作实现

    Go与C语言的互操作实现

    在Go与C语言互操作方面,Go更是提供了强大的支持。尤其是在Go中使用C,你甚至可以直接在Go源文件中编写C代码,本文就详细的介绍一下如何使用,感兴趣的可以了解一下
    2021-12-12
  • Golang安装和使用protocol-buffer流程介绍

    Golang安装和使用protocol-buffer流程介绍

    这篇文章主要介绍了Golang安装和使用protocol-buffer过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-09-09
  • Golang之sync.Pool对象池对象重用机制总结

    Golang之sync.Pool对象池对象重用机制总结

    这篇文章主要对Golang的sync.Pool对象池对象重用机制做了一个总结,文中有相关的代码示例和图解,具有一定的参考价值,需要的朋友可以参考下
    2023-07-07
  • 如何用go操作iptables和ipset设置黑白名单

    如何用go操作iptables和ipset设置黑白名单

    这篇文章主要介绍了如何用go操作iptables和ipset设置黑白名单问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-06-06

最新评论