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的单元测试和性能测试

    深入理解Golang的单元测试和性能测试

    Go语言提供了强大的测试工具,下面这篇文章主要给大家介绍了关于Golang单元测试和性能测试的相关资料,文中通过示例代码给大家详细介绍了单元测试和性能测试的相关内容,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-08-08
  • go语言使用中提示%!(NOVERB)的解决方案

    go语言使用中提示%!(NOVERB)的解决方案

    o语言的设计目标是提供一种简单易用的编程语言,同时保持高效性和可扩展性,它支持垃圾回收机制,具有强大的并发编程能力,可以轻松处理大规模的并发任务,Go语言还拥有丰富的标准库和活跃的开发社区,使得开发者能够快速构建出高质量的应用程序,需要的朋友可以参考下
    2023-10-10
  • 详解go中的引用类型

    详解go中的引用类型

    这篇文章主要介绍了go中的引用类型,文中给大家提到了值类型和引用类型的区别,通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • Golang分布式锁详细介绍

    Golang分布式锁详细介绍

    分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源时,需要通过一些互斥手段来防止彼此之间的干扰以保证一致性,在这种情况下,就需要使用分布式锁了
    2022-10-10
  • 一文带你了解Golang中interface的设计与实现

    一文带你了解Golang中interface的设计与实现

    本文就来详细说说为什么说 接口本质是一种自定义类型,以及这种自定义类型是如何构建起 go 的 interface 系统的,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-01-01
  • golang连接池检查连接失败时如何重试(示例代码)

    golang连接池检查连接失败时如何重试(示例代码)

    在Go中,可以通过使用database/sql包的DB类型的Ping方法来检查数据库连接的可用性,本文通过示例代码,演示了如何在连接检查失败时进行重试,感兴趣的朋友一起看看吧
    2023-10-10
  • Go语言中的变量声明和赋值

    Go语言中的变量声明和赋值

    这篇文章主要介绍了Go语言中的变量声明和赋值的方法,十分的细致全面,有需要的小伙伴可以参考下。
    2015-04-04
  • 探索Golang实现Redis持久化AOF实例

    探索Golang实现Redis持久化AOF实例

    这篇文章主要为大家介绍了Golang实现Redis持久化AOF实例探索,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • 基于golang如何实现error工具包详解

    基于golang如何实现error工具包详解

    Go 语言使用 error 类型来返回函数执行过程中遇到的错误,下面这篇文章主要给大家介绍了关于如何基于golang实现error工具包的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-09-09
  • 解决Golang 中使用WaitGroup的那点坑

    解决Golang 中使用WaitGroup的那点坑

    这篇文章主要介绍了解决Golang 中使用WaitGroup的那点坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04

最新评论