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

相关文章

  • 详解go程序如何在windows服务中开启和关闭

    详解go程序如何在windows服务中开启和关闭

    这篇文章主要介绍了一个go程序,如何在windows服务中优雅开启和关闭,文中通过代码示例和图文讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-07-07
  • 如何利用Go语言实现LRU Cache

    如何利用Go语言实现LRU Cache

    这篇文章主要介绍了如何利用Go语言实现LRU Cache,LRU是Least Recently Used的缩写,是一种操作系统中常用的页面置换算法,下面我们一起进入文章了解更多内容吧,需要的朋友可以参考一下
    2022-03-03
  • Go基础语法的使用

    Go基础语法的使用

    本文主要介绍了Go基础语法的使用,包括标识符、关键字、行分隔符、var关键字、:=运算符、空格、注释、package、import、输入输出、运算符、条件控制、循环等,感兴趣的可以了解一下
    2023-11-11
  • 如何在Go语言中灵活运用匿名函数和闭包

    如何在Go语言中灵活运用匿名函数和闭包

    这篇文章主要为大家介绍了如何在Go语言中灵活运用匿名函数和闭包实现实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Go语言单线程运行也会有的并发问题解析

    Go语言单线程运行也会有的并发问题解析

    这篇文章主要为大家介绍了Go语言单线程运行的并发问题解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • goFrame的gqueue与channe的区别

    goFrame的gqueue与channe的区别

    这篇文章主要介绍了goFrame的gqueue与channe的区别,channel的作用是用于go协程间的通信,goroutine和channel是支持高并发的重要组成部分,更多两者详细介绍需要的小伙伴可以参考下面文章内容
    2022-06-06
  • 浅析Golang中调度器的关键机制与性能

    浅析Golang中调度器的关键机制与性能

    Golang的调度器是其并发模型的核心组件,负责管理Goroutine的调度和执行,本文将从理论和代码层面分析Golang调度器的关键机制,感兴趣的可以了解下
    2025-03-03
  • Go unsafe 包的使用详解

    Go unsafe 包的使用详解

    这篇文章主要介绍了Go unsafe 包的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01
  • go语言之给定英语文章统计单词数量(go语言小练习)

    go语言之给定英语文章统计单词数量(go语言小练习)

    这篇文章给大家分享go语言小练习给定英语文章统计单词数量,实现思路大概是利用go语言的map类型,以每个单词作为关键字存储数量信息,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2020-01-01
  • golang查看CPU使用率与内存的方法详解

    golang查看CPU使用率与内存的方法详解

    这篇文章主要给大家介绍了golang查看CPU使用率与内存的方法,以及拓展介绍源码里//go:指令,文中有详细的代码示例以及图文介绍,需要的朋友可以参考下
    2023-10-10

最新评论