Context
今回は、contextを使って値を持ち回りながらHandler間で共有しようという話です。
Motivation
Wrapperで認証などの前処理を行ったあとに処理結果などをAPIに渡したい。
APIの引数にその度に渡す方法だと拡張性に乏しいので何かないか探していた。
実践
AppEngineのcontextを取得してから、これを親としたcontextを作成していきます。
// 前後の処理は割愛します
type contextHandler func(ctx context.Context, w http.ResponseWriter, r *http.Request)
type Wrapper struct {
handler contextHandler
}
func (w *Wrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// AppEngineのcontextを取得します
ctx := appengine.NewContext(r)
res, err := getHogehoge(foo, bar)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// ここでresを保持するcontextを作成します
newCtx := context.WithValue(ctx, "hoge", res)
w.handler(newCtx, w, r)
}
contextの内部の値はinterface{]で保持されるため、reflectを使って取得していきます。
func exampleHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
// ここではhogeは単純に文字列を保持しているとします
// structなどはまた違う取り出しかたが必要です
hoge := reflect.ValueOf(ctx.Value("hoge")).String()
do(hoge)
}
まとめ
このようにcontextを使用することでstructや変数を減らすことが出来るのでは、と思いました。
複数の値を保持する場合は、さらに重ねるようにしてcontextを作成します。
まだ使ったばかりなので、contextの生成コストを調べていないです。
使いながらパフォーマンスが気になったら、また新しいことを考えようと思います。
間違いの指摘などいつでもお願いします
追記
https://code.google.com/p/googleappengine/issues/detail?id=12725
ここにある通り、golang.org/x/net/context/ctxhttp/cancelreq.go
のbuildタグを削除しないと上手く動かないです。