1
0

More than 1 year has passed since last update.

【Go】middleware からハンドラ関数に値を渡すときに用いるcontext

Posted at

はじめに

Goで middleware からハンドラ関数に値を渡したい場合,contextを使って以下のように書くことが多いと思います.

middleware内
import (
    "context"
    "net/http"
)

func exampleMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), "hoge", hoge)
        next.ServeHTTP(w, r.WithContext(ctx))
    }
}
ハンドラ関数

func exampleHandlerFunc(w http.ResponseWriter, r *http.Request) {
    v := (r.Context()).Value("hoge")
	hoge, ok := v.(string)
}

今日は,このコードの理解を深めようと思います.

context とは

contextのパッケージ によると,context の概要は以下の通りです.

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
(和訳)Package contextはContext型を定義し、デッドライン、キャンセルシグナル、その他のリクエストスコープに含まれる値をAPI境界やプロセス間で伝達します。

今回の例だと,context は「リクエストスコープに含まれる値をAPI境界やプロセス間で伝達する」ために使われていそうです.

context を用いて middlewareからハンドラ関数に値を渡すときのフロー

context を用いて middlewareからハンドラ関数に値を渡すとき,以下のようなフローになっていることが分かりました.

  1. (middleware) 渡したい値が含まれる context の生成
  2. (middleware) 1 で生成した context をrequestにセット
  3. (ハンドラ関数) request にセットされている context から値を取り出す.
middleware内
import (
    "context"
    "net/http"
)

func exampleMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), "hoge", hoge) // 1
        next.ServeHTTP(w, r.WithContext(ctx)) // 2
    }
}
ハンドラ関数

func exampleHandlerFunc(w http.ResponseWriter, r *http.Request) {
    v := (r.Context()).Value("hoge") // 3
	hoge, ok := v.(string)
}

それぞれ詳しく説明していきます.

1. 渡したい値が含まれるcontext の生成

まず,middleware 内で以下を実行することにより,渡したい値(ここではhoge)が含まれるcontext ctx を生成します.

// r *http.Request
ctx := context.WithValue(r.Context(), "hoge", hoge)

context.withValue 関数の概要は,contextのパッケージ によると以下の通りです.

WithValue returns a copy of parent in which the value associated with key is val.
(和訳)WithValue は、key に関連付けられた値を val とする parent のコピーを返す。

ここでのコピー元 parent は r.Context() ということになるのですが, r.Context() について調べてみます.
net/httpのrequest.goのソースコードを見てみると,Request 構造体にcontextが定義されていました.

request.go
type Request struct {
    ...
    ctx context.Context
}

r.Context() は,このRequest構造体に定義されてるcontextを取ってくるようです.

request.go
func (r *Request) Context() context.Context {
	if r.ctx != nil {
		return r.ctx
	}
	return context.Background()
}

2. 1 で生成したcontextをrequestにセット

次に,以下のコードの r.WithContext(ctx) において,1で生成したcontext ctx をrequestにセットしています.

 next.ServeHTTP(w, r.WithContext(ctx))

ちなみに,r.WithContext(ctx) のソースコードは以下のようになっています.

request.go
func (r *Request) WithContext(ctx context.Context) *Request {
	if ctx == nil {
		panic("nil context")
	}
	r2 := new(Request)
	*r2 = *r
	r2.ctx = ctx // ここで新たなctxをrequestにset
	return r2
}

3. requestにセットされているcontextから値を取り出す.

最後に,ハンドラ関数内の以下の部分で,requestにセットされているcontextから値を取り出します.

ハンドラ関数
v := (r.Context()).Value("hoge")

r.Context() で,requestにセットされているcontextを取得して,Value("hoge") で値を取り出しています.

以上のフローで, request にセットされた context を介して,middlewareからハンドラ関数に値を渡すことができるみたいです.

最後に

今回は,context を使って middleware からハンドラ関数に値を渡したい場合について,ソースコードを見ながらフローを追ってみました!これを見て,私と同じように少しでも理解が深まった方がいたらうれしいです!

参考文献

https://pkg.go.dev/context
https://github.com/golang/go/blob/master/src/net/http/request.go
https://moneyforward.com/engineers_blog/2020/07/28/go-context/

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0