「讀源碼」為什么注冊(cè)路由時(shí)沒(méi)有傳入上下文,在接口方法中卻能取到?
這篇文章回答一下知識(shí)星球里星友的提問(wèn),是一個(gè)非常好的問(wèn)題,也歡迎大家留言討論。
提問(wèn)
在gofame框架的demo案例中,如下圖所示:
為什么左側(cè)路由綁定這里沒(méi)有向controller中傳入context的值,在controller中卻能取到值?
如何賦值和接收context?
先說(shuō)結(jié)論
關(guān)于ctx context.Context上下文,Server組件會(huì)自動(dòng)從請(qǐng)求中獲取并傳遞給接口方法,聲明并初始化了context初始值為context.Background()
可以通過(guò)GetCtx、SetCtx、GetCtxVar、SetCtxVar這些方法輕松的為context賦值和取值
通過(guò)示例代碼輕松可知:我們可以通過(guò)ghttp.Request實(shí)例輕松的操作context。
解答
問(wèn)題1. 為什么左側(cè)路由綁定這里沒(méi)有向controller中傳入context的值,在controller中卻能取到值?
先說(shuō)結(jié)論:關(guān)于ctx context.Context上下文,Server組件會(huì)自動(dòng)從請(qǐng)求中獲取并傳遞給接口方法。
關(guān)鍵代碼是上圖中的s := g.Server():
追蹤一下它的源碼可以發(fā)現(xiàn):聲明并初始化了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
}
我們可以發(fā)現(xiàn)這里返回了一個(gè)non-nil, empty Context
問(wèn)題2. 如何賦值和接收context?
在GoFrame框架中,官方推薦的正是使用Context上下文對(duì)象來(lái)處理流程共享的上下文變量,甚至將該對(duì)象進(jìn)一步傳遞到依賴的各個(gè)模塊方法中。
該Context對(duì)象類型實(shí)現(xiàn)了標(biāo)準(zhǔn)庫(kù)的context.Context接口,該接口往往會(huì)作為模塊間調(diào)用方法的第一個(gè)參數(shù),該接口參數(shù)也是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{})
簡(jiǎn)要說(shuō)明:
GetCtx方法用于獲取當(dāng)前的context.Context對(duì)象,作用同Context方法。
SetCtx方法用于設(shè)置自定義的context.Context上下文對(duì)象。
GetCtxVar方法用于獲取上下文變量,并可給定當(dāng)該變量不存在時(shí)的默認(rèn)值。
SetCtxVar方法用于設(shè)置上下文變量。
使用示例
示例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()
}
可以看到:我們可以通過(guò)SetCtxVar和GetCtxVar來(lái)設(shè)置和獲取自定義的變量,該變量生命周期僅限于當(dāng)前請(qǐng)求流程。
執(zhí)行后,訪問(wèn) http://127.0.0.1:8199/ ,頁(yè)面輸出內(nèi)容為:1234567890abcd
示例2,SetCtx
SetCtx方法常用于中間件中整合一些第三方的組件,例如第三方的鏈路跟蹤組件等等。
為簡(jiǎn)化示例,這里我們將上面的例子通過(guò)SetCtx方法來(lái)改造一下來(lái)做演示。
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) {
//看到這里的示例代碼,更能解答問(wèn)題1,通過(guò)ghttp.Request可以輕松獲得上下文對(duì)象
r.Response.Write(r.Context().Value(TraceIdName))
// 也可以使用
// r.Response.Write(r.GetCtxVar(TraceIdName))
})
})
s.SetPort(8199)
s.Run()
}
執(zhí)行后,訪問(wèn) http://127.0.0.1:8199/ ,頁(yè)面輸出內(nèi)容為:1234567890abcd
總結(jié)
通過(guò)上面的示例,我們能更好的理解這位星友提出的困惑:
關(guān)于ctx context.Context上下文,Server組件會(huì)自動(dòng)從請(qǐng)求中獲取并傳遞給接口方法,聲明并初始化了context初始值為context.Background()
可以通過(guò)GetCtx、SetCtx、GetCtxVar、SetCtxVar這些方法輕松的為context賦值和取值
通過(guò)示例代碼輕松可知:我們可以通過(guò)ghttp.Request實(shí)例輕松的操作context。