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


# はじめに

Goで middleware からハンドラ関数に値を渡したい場合，contextを使って以下のように書くことが多いと思います．
```go: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))
    }
}
```

```go:ハンドラ関数

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

```


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

# context とは
[contextのパッケージ](https://pkg.go.dev/context#pkg-overview) によると，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 から値を取り出す．

```go: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
    }
}
```

```go:ハンドラ関数

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

```

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


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

まず，middleware 内で以下を実行することにより，渡したい値（ここでは`hoge`）が含まれるcontext `ctx` を生成します．
```go
// r *http.Request
ctx := context.WithValue(r.Context(), "hoge", hoge)
```
`context.withValue` 関数の概要は，[contextのパッケージ](https://pkg.go.dev/context#WithValue) によると以下の通りです．
> 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のソースコード](https://github.com/golang/go/blob/master/src/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にセットしています．
```go
 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から値を取り出します．
```go:ハンドラ関数
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/