前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >我给 gin 提交了一行代码

我给 gin 提交了一行代码

作者头像
gopher云原生
发布2021-10-18 11:12:35
1.2K1
发布2021-10-18 11:12:35
举报
文章被收录于专栏:gopher云原生

这篇文章记录一次给 gin-gonic/gin[1] 提交了一行代码的经历,虽然没什么含金量,但是对我而言还是挺开心的哈哈。

缘由

事情是这样的,gin 默认的 404 页面返回的是 404 page not found ,我们项目中需要自定义该页面进行跳转,第一直觉肯定是 gin 会有相应的 API ,事实如此,gin 有一个 NoRoute 方法可以自定义 404 页面的 handler ,它的源码如下:

代码语言:javascript
复制
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
 engine.noRoute = handlers
 engine.rebuild404Handlers()
}

后面我还想要路由找不到对应的 Method 时也进行自定义处理,习惯性的翻看了对应的 API 源码:

代码语言:javascript
复制
// NoMethod sets the handlers called when... TODO.
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
 engine.noMethod = handlers
 engine.rebuild405Handlers()
}

发现 NoMethod 方法注释竟然是被标记为 TODO ,此时我还没去细究,想着先写个简单例子测试一下:

代码语言:javascript
复制
package main

import (
 "fmt"
 "github.com/gin-gonic/gin"
 "net/http"
)

func main() {
 r := gin.Default()
 r.NoRoute(func(c *gin.Context) {
  c.String(http.StatusOK, "NoRoute")
 })
 r.NoMethod(func(c *gin.Context) {
  fmt.Println("NoMethod")
  c.String(http.StatusOK, "NoMethod")
 })
 r.POST("/", func(c *gin.Context) {
  c.String(http.StatusOK, "/")
 })
 r.Run(":8080")
}

这段代码很简单,我的预期结果应该是这样的:

代码语言:javascript
复制
$ curl http://localhost:8080
NoMethod

$ curl -X POST http://localhost:8080
/

$ curl -X PUT http://localhost:8080
NoMethod

但实际上,它却是这样的:

代码语言:javascript
复制
$ curl http://localhost:8080
NoRoute

$ curl -X POST http://localhost:8080
/

$ curl -X PUT http://localhost:8080
NoRoute

这说明 NoMethod 并没有生效,此时以为难道真的是因为处于 TODO 状态还未实现吗,所以开始了深入研究其源码来解开我的疑惑。

源码分析

代码语言:javascript
复制
// NoMethod sets the handlers called when... TODO.
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
 engine.noMethod = handlers
 engine.rebuild405Handlers()
}

NoMethod 方法与 NoRoute 方法的逻辑是一致的。NoMethod 将用户传入的自定义 handlers 赋给 enginenoMethod 字段存储,接下去的 engine.rebuild405Handlers():

代码语言:javascript
复制
func (engine *Engine) rebuild405Handlers() {
 engine.allNoMethod = engine.combineHandlers(engine.noMethod)
}

engine.combineHandlers(engine.noMethod)engine.noMethod 相当于我们刚刚用户传入的 handlers 参数,engine.combineHandlers 是一个重组 handlers 的方法:

代码语言:javascript
复制
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
 finalSize := len(group.Handlers) + len(handlers)
 if finalSize >= int(abortIndex) {
  panic("too many handlers")
 }
 mergedHandlers := make(HandlersChain, finalSize)
 copy(mergedHandlers, group.Handlers)
 copy(mergedHandlers[len(group.Handlers):], handlers)
 return mergedHandlers
}

所以 rebuild405Handlers 只是将 engine.Handlersengine.noMethod 组合起来一起赋给 allNoMethod ,这里都不能分析出什么,我们得进一步去看 allNoMethod 使用的地方,也就是路由进入后判断的地方,即 ServeHTTP 中的 engine.handleHTTPRequest(c) 这一步。

handleHTTPRequest 方法的源码:

代码语言:javascript
复制
func (engine *Engine) handleHTTPRequest(c *Context) {
 httpMethod := c.Request.Method
 rPath := c.Request.URL.Path
 unescape := false
 if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
  rPath = c.Request.URL.RawPath
  unescape = engine.UnescapePathValues
 }

 if engine.RemoveExtraSlash {
  rPath = cleanPath(rPath)
 }

 // Find root of the tree for the given HTTP method
 t := engine.trees
 for i, tl := 0, len(t); i < tl; i++ {
  if t[i].method != httpMethod {
   // 如果 method 对应不上,则继续下一次循环
   continue
  }
  root := t[i].root
  // 在 tree 中找到 route
  value := root.getValue(rPath, c.params, unescape)
  if value.params != nil {
   c.Params = *value.params
  }
  if value.handlers != nil {
   c.handlers = value.handlers
   c.fullPath = value.fullPath
   // 跳转到对应的 handlers (中间件或用户handlers)
   c.Next()
   c.writermem.WriteHeaderNow()
   return
  }
  if httpMethod != http.MethodConnect && rPath != "/" {
   if value.tsr && engine.RedirectTrailingSlash {
    redirectTrailingSlash(c)
    return
   }
   if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
    return
   }
  }
  break
 }

 // 关键之处,只有 HandleMethodNotAllowed 为 true 时才会执行
 if engine.HandleMethodNotAllowed {
  for _, tree := range engine.trees {
   if tree.method == httpMethod {
    continue
   }
   // 找不到对应的 method,405 处理
   if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
    c.handlers = engine.allNoMethod
    serveError(c, http.StatusMethodNotAllowed, default405Body)
    return
   }
  }
 }

 // 找不到路由 404 处理
 c.handlers = engine.allNoRoute
 serveError(c, http.StatusNotFound, default404Body)
}

发现在关键之处,是实现了 NoMethod 的逻辑处理的,只是有个前提,engine.HandleMethodNotAllowed 必须为 true 。所以 NoMethod 并不是 TODO 状态。

验证

在上面的测试例子中增加一行 r.HandleMethodNotAllowed = true

代码语言:javascript
复制
......
r := gin.Default()
r.HandleMethodNotAllowed = true
......

得到了我的预期结果:

代码语言:javascript
复制
$ curl http://localhost:8080
NoMethod

$ curl -X POST http://localhost:8080
/

$ curl -X PUT http://localhost:8080
NoMethod

提 PR

既然 NoMethod 是可使用状态,那不应该被标记为 TODO ,而且文档注释中没有提醒用户需要将 engine.HandleMethodNotAllowed 设为 true ,所以我尝试将 NoMethod 方法的代码修改为:

代码语言:javascript
复制
// NoMethod sets the handlers called when NoMethod.
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
 engine.HandleMethodNotAllowed = true
 engine.noMethod = handlers
 engine.rebuild405Handlers()
}

然后提交了 PR[2]

在 11 天后通过了

哈哈,容我开心一小阵 ~

PS.

不过在后面的一次 PR 中,又更改了我提交的代码

NoMethod 中不默认开启 engine.HandleMethodNotAllowed = true ,而是通过注释文档提醒用户。

你们觉得哪种方式更好些呢?关注我加我好友或者点击下方阅读原文可留言哦!

参考资料

[1]

gin-gonic/gin: https://github.com/gin-gonic/gin

[2]

PR: https://github.com/gin-gonic/gin/pull/2872

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 gopher云原生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 缘由
  • 源码分析
  • 验证
  • 提 PR
  • PS.
    • 参考资料
    相关产品与服务
    消息队列 TDMQ
    消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档