以下のようにしておくとcontext.Contextにgin.Contextを渡した際にValue()
などを利用できる。
r := gin.Default()
r.ContextWithFallback = true
説明
例えばIntoContextのようにcontext.Contextを受け取ってWithValueで値をセットしたcontext.Contextを返す関数や
context.ContextからValueでセットしてある値を取り出す関数を利用する場合。
type usernameKey struct{}
func IntoContext(ctx context.Context, username string) context.Context {
return context.WithValue(ctx, usernameKey{}, username)
}
func FromContext(ctx context.Context) (string, error) {
value := ctx.Value(usernameKey{})
if value == nil {
return "", errors.New("not found")
}
username, ok := value.(string)
if !ok {
return "", errors.New("invalid type")
}
return username, nil
}
r.ContextWithFallback
がfalse
(初期値) の場合gin.Contextをそのまま渡すと FromContext
内のctx.Value
でnil
が返ってくる。
func handle(ctx *gin.Context) {
username, err := FromContext(ctx)
if err != nil {
panic(err) // not foundなerrorでパニックになる
}
}
ctx.Request.Context()を渡せば取得できる。
func handle(ctx *gin.Context) {
username, err := FromContext(ctx.Request.Context())
if err != nil {
panic(err) // パニックにならない
}
println(username) // IntoContextでセットした値が出力される
}
ctx.Request.Context()
を毎回渡すのはちょっと・・・。
c := ctx.Request.Context()
とするのもちょっと・・・。
やはりctx *gin.Context
のctx
をそのまま渡したい。
ということでコードを読んでみると ContextWithFallback
が true
の場合は ctx.Request.Context().Value()
にフォールバックし、false
の場合はnilを返すように実装されていました。
// https://github.com/gin-gonic/gin/blob/v1.9.1/context.go#L1184-L1188
func (c *Context) hasRequestContext() bool {
hasFallback := c.engine != nil && c.engine.ContextWithFallback
hasRequestContext := c.Request != nil && c.Request.Context() != nil
return hasFallback && hasRequestContext
}
// https://github.com/gin-gonic/gin/blob/v1.9.1/context.go#L1229-L1232
func (c *Context) Value(key any) any {
// 一部省略...
if !c.hasRequestContext() {
return nil
}
return c.Request.Context().Value(key)
}
gin.Default()
や内部で使われているgin.New()
ではContextWithFallback
に値をセットしていないのでデフォルトのfalse
になっています。
そのため、gin.Default()
やgin.New()
の後にContextWithFallback
にtrue
をセットすることで想定した動作をさせることができます。
r := gin.Default()
r.ContextWithFallback = true
test
package main
import (
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func TestGinContext(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []struct {
name string
contextWithFallback bool
wantErr bool
}{
{
name: "ContextWithFallbackがfalse",
contextWithFallback: false,
wantErr: true,
},
{
name: "ContextWithFallbackがtrue",
contextWithFallback: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, r := gin.CreateTestContext(w)
c.Request = httptest.NewRequest("GET", "http://localhost", nil)
r.ContextWithFallback = tt.contextWithFallback
c.Request = c.Request.WithContext(IntoContext(c, "user"))
username, err := FromContext(c)
if tt.wantErr {
if err == nil {
t.Fatal("err is nil")
}
return
}
if err != nil {
t.Fatal(err)
}
if username != "user" {
t.Fatal("invalid username got: ", username)
}
})
}
}
実行結果
Running tool: /usr/bin/go test -timeout 30s -run ^TestGinContext$ gin-context
=== RUN TestGinContext
=== RUN TestGinContext/ContextWithFallbackがfalse
--- PASS: TestGinContext/ContextWithFallbackがfalse (0.00s)
=== RUN TestGinContext/ContextWithFallbackがtrue
--- PASS: TestGinContext/ContextWithFallbackがtrue (0.00s)
--- PASS: TestGinContext (0.00s)
PASS
ok gin-context 0.005s