
王中阳Go
2023/02/20阅读:23主题:萌绿
「读源码」为什么注册路由时没有传入上下文,在接口方法中却能取到?
这篇文章回答一下知识星球里星友的提问,我之前没有像他考虑的这么细致。
研究了一下源码和文档,也欢迎大家留言讨论。
提问
在gofame框架的demo案例中,如下图所示:

-
为什么左侧路由绑定这里没有向controller中传入context的值,在controller中却能取到值? -
如何赋值和接收context?
先说结论

-
关于 ctx context.Context
上下文,Server组件会自动从请求中获取并传递给接口方法,声明并初始化了context初始值为context.Background()
-
可以通过GetCtx、SetCtx、GetCtxVar、SetCtxVar这些方法轻松的为context赋值和取值 -
通过示例代码轻松可知:我们可以通过 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.Context
func (r *Request) SetCtx(ctx context.Context)
func (r *Request) GetCtxVar(key interface{}, def ...interface{}) *gvar.Var
func (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
总结
通过上面的示例,我们能更好的理解这位星友提出的困惑:
-
关于 ctx context.Context
上下文,Server组件会自动从请求中获取并传递给接口方法,声明并初始化了context初始值为context.Background()
-
可以通过GetCtx、SetCtx、GetCtxVar、SetCtxVar这些方法轻松的为context赋值和取值 -
通过示例代码轻松可知:我们可以通过 ghttp.Request
实例轻松的操作context。
参考链接
-
路由注册-规范路由[1]
-
请求输入-Context[2]
参考资料
路由注册-规范路由: https://goframe.org/pages/viewpage.action?pageId=30736904
[2]请求输入-Context: https://goframe.org/pages/viewpage.action?pageId=73224791
作者介绍

王中阳Go
专注Go语言的学习经验分享和简历优化