SODA Engineering Blog
📝

gin.Contextとgin.Context.Request.Context()はどちらを使えば良い?

2023/04/28に公開

注:この記事はginのv1.9.0とそれ以前のバージョンを対象としています。

gin.Contextはcontext.Contextインターフェースを実装しています。

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key any) any
}

ですがgin.Contextはhttp.Request構造体を型にとるRequestフィールドを持っていて、http.Requestにもcontext.Contextを実装するctxフィールドを持っています。(http.Request.ctxはhttp.Request.Context()を使えば取得できます)

この2つのContextはどう違うのでしょうか?
とりあえずどちらを使えばいいのでしょうか?
状況によって使い分けるのでしょうか?

ソースコードを見ていきます。
Deadline()、Done()、Err()は直前にif文に当てはまる場合はゼロ値を返しますが基本的にhttp.Request.Context()と同じものを返しています。

func (c *Context) Deadline() (deadline time.Time, ok bool) {
	if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
		return
	}
	return c.Request.Context().Deadline()
}

func (c *Context) Done() <-chan struct{} {
	if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
		return nil
	}
	return c.Request.Context().Done()
}

func (c *Context) Err() error {
	if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
		return nil
	}
	return c.Request.Context().Err()
}

問題はValue()です。
Value()が返す値は5通りあるようです。

  1. 引数が0のときはc.Requestそのものを返す
  2. "_gin-gonic/gin/contextkey"という文字列のときは*gin.Context自体を返す
  3. その他の文字列のときはc.Keysに一致するキーがあればそれを返す
  4. Context.Request.Context()かc.Requestがnilの場合はnilを返す
  5. 1〜4までの条件に当てはまらなかったか3で一致するキーが無かった場合はc.Request.Context().Value()をの戻り値を返す
func (c *Context) Value(key any) any {
	if key == 0 {
		return c.Request
	}
	if key == ContextKey {
		return c
	}
	if keyAsString, ok := key.(string); ok {
		if val, exists := c.Get(keyAsString); exists {
			return val
		}
	}
	if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil {
		return nil
	}
	return c.Request.Context().Value(key)
}

1〜3の戻り値はすべて他のメソッドやフィールドから直接取得できるため、わざわざ設けている理由はわかりませんでした。
もしかしたら将来的にValue()からのみ取得できるようにして行くのかもしれません。(要確認

v1.8.0以前

gin.Contextの各メソッドが上記のような値を返すようになったのはv1.8.0からであり、それ以前もcontext.Contextを実装してはいたものの、Deadline()、Done()、Err()はnilしか返さず、コメントではc.Request.Context()の使用を推奨していました。Value()は上で書いた戻り値の1と3とそれらに当てはまらなかった場合はnilを返していて、c.Request.Context()の値は全く使用されていませんでした。

結論

調査が足りていないですがv1.8.0以降を導入する場合はgin.Context、それ以前のバージョンでcontext.Contextを使う場合はgin.Context.Requestを使うのが良さそうです。

SODA Engineering Blog
SODA Engineering Blog

Discussion