写点什么

「读源码」为什么注册路由时没有传入上下文,在接口方法中却能取到?

作者:王中阳Go
  • 2023-02-20
    北京
  • 本文字数:2262 字

    阅读完需:约 7 分钟

「读源码」为什么注册路由时没有传入上下文,在接口方法中却能取到?

作为一个工作 8 年的老程序员告诉你:阅读源码和查看官方文档,是解决问题最高效的办法。不信你来看,这个困扰了读者半天的问题我查了源码和文档后瞬间解决。

前言

上周五有位读者私信我一个问题,说困扰了他半天,研究了一个上午也没搞明白。


是一位运维转 Go 的朋友,最近有不少运维、测试、甚至 Java、PHP 转 Go 的朋友加我。


这个问题并不是三言两语就能讲清楚的,我就整理了一篇文章给他。

快乐


正如上图所示,反馈特别的好,更文的快乐又找到了,我把这个问题和解答又好好整理了一下,分享给大家,希望对大家有帮助.


也分享一下我作为一个工作 8 年的老程,解决问题的心得:


阅读源码和查看官方文档,是解决问题最高效的办法。

提问

在 gofame 框架的 demo 案例中,如下图所示:



  1. 为什么左侧路由绑定这里没有向 controller 中传入 context 的值,在 controller 中却能取到值?

  2. 如何赋值和接收 context?

先说结论


  1. 关于ctx context.Context上下文,Server 组件会自动从请求中获取并传递给接口方法,声明并初始化了 context 初始值为context.Background()

  2. 可以通过 GetCtx、SetCtx、GetCtxVar、SetCtxVar 这些方法轻松的为 context 赋值和取值

  3. 通过示例代码轻松可知:我们可以通过ghttp.Request实例轻松的操作 context。

解答

问题 1. 为什么左侧路由绑定这里没有向 controller 中传入 context 的值,在 controller 中却能取到值?


先说结论:关于ctx context.Context上下文,Server 组件会自动从请求中获取并传递给接口方法。


关键代码是上图中的s := g.Server():


追踪一下它的源码可以发现:声明并初始化了 ctx 的初始值:context.Background()



再带大家看一下context.Background()的源码和注释。


// Background returns a non-nil, empty Context. It is never canceled, has no// values, and has no deadline. It is typically used by the main function,// initialization, and tests, and as the top-level Context for incoming// requests.func Background() Context {  return background}
复制代码


我们可以发现这里返回了一个non-nil, empty Context



问题 2. 如何赋值和接收 context?


在 GoFrame 框架中,官方推荐的正是使用 Context 上下文对象来处理流程共享的上下文变量,甚至将该对象进一步传递到依赖的各个模块方法中。


该 Context 对象类型实现了标准库的 context.Context 接口,该接口往往会作为模块间调用方法的第一个参数,该接口参数也是 Golang 官方推荐的在模块间传递上下文变量的推荐方式。

方法列表:

func (r *Request) GetCtx() context.Contextfunc (r *Request) SetCtx(ctx context.Context)func (r *Request) GetCtxVar(key interface{}, def ...interface{}) *gvar.Varfunc (r *Request) SetCtxVar(key interface{}, value interface{})
复制代码

简要说明:

  • GetCtx 方法用于获取当前的 context.Context 对象,作用同 Context 方法。

  • SetCtx 方法用于设置自定义的 context.Context 上下文对象。

  • GetCtxVar 方法用于获取上下文变量,并可给定当该变量不存在时的默认值。

  • SetCtxVar 方法用于设置上下文变量。

使用示例

示例 1,SetCtxVar/GetCtxVar

package main
import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp")
const ( TraceIdName = "trace-id")
func main() { s := g.Server() s.Group("/", func(group *ghttp.RouterGroup) { group.Middleware(func(r *ghttp.Request) { //向context中赋值 r.SetCtxVar(TraceIdName, "1234567890abcd") r.Middleware.Next() }) group.ALL("/", func(r *ghttp.Request) { //从context中取值 r.Response.Write(r.GetCtxVar(TraceIdName)) }) }) s.SetPort(8199) s.Run()}
复制代码


可以看到:我们可以通过 SetCtxVar 和 GetCtxVar 来设置和获取自定义的变量,该变量生命周期仅限于当前请求流程。


执行后,访问 http://127.0.0.1:8199/ ,页面输出内容为:1234567890abcd

示例 2,SetCtx

SetCtx 方法常用于中间件中整合一些第三方的组件,例如第三方的链路跟踪组件等等。


为简化示例,这里我们将上面的例子通过 SetCtx 方法来改造一下来做演示。


package main
import ( "context" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp")
const ( TraceIdName = "trace-id")
func main() { s := g.Server() s.Group("/", func(group *ghttp.RouterGroup) { group.Middleware(func(r *ghttp.Request) { ctx := context.WithValue(r.Context(), TraceIdName, "1234567890abcd") r.SetCtx(ctx) r.Middleware.Next() }) group.ALL("/", func(r *ghttp.Request) { //看到这里的示例代码,更能解答问题1,通过ghttp.Request可以轻松获得上下文对象 r.Response.Write(r.Context().Value(TraceIdName)) // 也可以使用 // r.Response.Write(r.GetCtxVar(TraceIdName)) }) }) s.SetPort(8199) s.Run()}
复制代码


执行后,访问 http://127.0.0.1:8199/ ,页面输出内容为:1234567890abcd

总结

通过上面的示例,我们能更好的理解这位星友提出的困惑:


  1. 关于ctx context.Context上下文,Server 组件会自动从请求中获取并传递给接口方法,声明并初始化了 context 初始值为context.Background()

  2. 可以通过 GetCtx、SetCtx、GetCtxVar、SetCtxVar 这些方法轻松的为 context 赋值和取值

  3. 通过示例代码轻松可知:我们可以通过ghttp.Request实例轻松的操作 context。

参考链接


一起学习

可以通过下方作者栏的信息联系我,一起学习进步。

发布于: 2023-02-20阅读数: 34
用户头像

王中阳Go

关注

靠敲代码在北京买房的程序员 2022-10-09 加入

【微信】wangzhongyang1993【公众号】程序员升职加薪之旅【成就】InfoQ专家博主👍掘金签约作者👍B站&掘金&CSDN&思否等全平台账号:王中阳Go

评论 (1 条评论)

发布
用户头像
阅读源码和查看官方文档,是解决问题最高效的办法。不信你来看,这个困扰了读者半天的问题我查了源码和文档后瞬间解决。
2023-02-20 09:58 · 北京
回复
没有更多了
「读源码」为什么注册路由时没有传入上下文,在接口方法中却能取到?_Go_王中阳Go_InfoQ写作社区