GolagでHTTPリクエストを受け付けるとき、http.HandlerFunc
という型の関数で処理を受け付けます。
シンプルなechoサーバーなどの実装であればこれで十分なのですが、実際アプリケーションを開発することを想定するとこの関数がもつ情報だけでは力不足なケースがほとんどです。
この投稿では、より開発しやすいようにリクエストを受け付ける関数の実装方法について記していきます。
普通にHTTPリクエストを受け取る処理
http.HandleFunc
に受け付けるパスとそれに対応する処理関数(http.HandlerFunc
)を与えます。
import (
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!!"))
})
http.ListenAndServe(":8080", nil)
}
アプリケーションのコンテキストを参照したい場合
通常、アプリケーションを作る時はアプリケーションのコンテキストのようなアプリケーションに関する基底情報を持ちます。
(例えば、アプリケーションの設定値だとか、アプリケーションの実行環境だとか)
これをhttp.HandlerFunc
から愚直に参照しようとすると以下のようになるでしょうか?
import (
"net/http"
)
var appCtx = map[string]interface{}{
"env": os.Getenv("Environment"),
"config": &struct{}{},
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var res string
if appCtx["env"] == "production" {
res = "Hello production!!"
} else {
res = "Hello development!!"
}
w.Write([]byte(res))
})
http.ListenAndServe(":8080", nil)
}
グローバル変数としてappCtx
を定義し、これを処理関数内から参照するようにします。
しかし、この方法にはいくつか問題があります。
1つはアプリケーションのコンテキストがリクエストユーザーによって情報が変わるケースに対応出来ないことです。
グローバル変数で定義しているため、アプリケーションの起動時に値が決定してしまいます。
ユーザーのリクエスト毎にアプリケーションのコンテキストを設定する余地がありません。
2つ目は、何らかの理由でグローバルに定義したappCtx
を更新したいときにレースコンディションが発生してしまうことです。
1つ目の理由に関連するのですが、appCtx
にユーザー情報を乗せて置きたいという場合に、
appCtx
に設定したユーザー情報がアクセスしたユーザーのものである保証が持てなくなってしまいます。
リクエスト毎にアプリケーションのコンテキストを生成する
グローバルにアプリケーションのコンテキストを作ることには問題があることを説明しました。
この問題を解決するために高階関数という関数を利用します。
高階関数は関数を引数や返り値に取る関数のことです。
リクエスト処理関数を引数にとり、http.HandlerFunc
型の関数を返すように出来れば問題を解決することが出来ます。
以下のようなリクエスト処理関数を定義します。
type AppContext = map[string]interface{}
type AppHandlerFunc = func(AppContext, http.ResponseWriter, *http.Request)
リクエスト処理関数を引数に取り、http.HandlerFunc
を返す関数を定義します。
http.HandlerFunc
の実装内部でアプリケーションのコンテキストを生成するため、他のリクエストからのレースコンディションを避けることが出来るようになります。
func CreateAppHandlerFunc(f AppHandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := AppContext{
"env": os.Getenv("Environment"),
"config": &struct{}{},
}
f(ctx, w, r)
}
}
このテクニックを覚えることで、他にも様々なことが出来るようになります。
http.ResponseWriterや*http.Requestの代わりに独自の型を利用する
http.HandlerFunc
が引数の取るhttp.ResponseWriter
や*http.Request
は一般的なサーバーアプリケーションの開発に利用するには力不足を感じます。
(例えば、JSONのレスポンスを返したいがそのための手順が煩雑になるなど)
このため、アプリケーションを構築するために即した独自のRequestやResponseのオブジェクトの必要性が高まってきます。
例えば以下のような…
type AppRequest struct {
raw *http.Request
}
type AppResponse struct {
raw http.ResponseWriter
}
func (res *AppResponse) sendJSON(data interface{}) {
// JSONを送る処理
}
上記のオブジェクトはhttp.HandlerFunc
の中で直接生成しても問題ありませんが、各リクエスト毎にこのコードを書くのか?と考えるとそれは煩雑です。
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
req := &AppRequest{raw: r}
res := &AppResponse{raw: w}
res.sendJSON(struct{}{})
})
こういった煩雑さを解消するためにも、先述したhttp.HandlerFunc
を返す高階関数が利用できます。
type AppHandlerFunc = func(*Request, *Response)
func CreateHandlerFunc(f AppHandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
req := &AppRequest{raw: r}
res := &AppResponse{raw: w}
f(req, res)
}
}
http.HandleFunc("/", CreateHandlerFunc(func(_ *Request, res *Response) {
res.sendJSON(struct{}{})
})
前後に処理を挟めるようにする
WAFを利用すると、よくリクエストを処理する前と後ろに何らかの処理を挟むことが出来るかと思います。
ここでは、Action
というオブジェクトを定義してこれらの機能を実現します。
以下のようなインターフェースを定義します。
Before
は前処理、After
は後処理、Dispatch
が主処理といった処理を持つことを想定します。
type Action interface {
Before()
After()
Dispatch()
}
先程定義したAction
インターフェースに適合するように、適当なオブジェクトを用意します。
/*
FooAction
*/
type FooAction struct {
w http.ResponseWriter
r *http.Request
}
func NewFooAction(w http.ResponseWriter, r *http.Request) Action {
return &FooAction{
w: w,
r: r,
}
}
func (a *FooAction) Before() {}
func (a *FooAction) After() {}
func (a *FooAction) Dispatch() {
a.w.Write([]byte("Foo"))
}
/*
BarAction
*/
type BarAction struct {
w http.ResponseWriter
r *http.Request
}
func NewBarAction(w http.ResponseWriter, r *http.Request) Action {
return &BarAction{
w: w,
r: r,
}
}
func (a *BarAction) Before() {}
func (a *BarAction) After() {}
func (a *BarAction) Dispatch() {
a.w.Write([]byte("Bar"))
}
各Action毎に使うか使わないか分からないBefore
、After
メソッドを実装するのは煩雑ですので基底オブジェクトを作って各Actionに埋め込みます。
type BaseAction {}
func (a *BaseAction) Before() {}
func (a *BaseAction) After() {}
/*
FooAction
*/
type FooAction struct {
*BaseAction
w http.ResponseWriter
r *http.Request
}
func NewFooAction(w http.ResponseWriter, r *http.Request) Action {
return &FooAction{
BaseAction: &BaseAction{},
w: w,
r: r,
}
}
func (a *FooAction) Dispatch() {
a.w.Write([]byte("Foo"))
}
/*
BarAction
*/
type BarAction struct {
*BaseAction
w http.ResponseWriter
r *http.Request
}
func NewBarAction(w http.ResponseWriter, r *http.Request) Action {
return &BarAction{
BaseAction: &BaseAction{},
w: w,
r: r,
}
}
func (a *BarAction) Dispatch() {
a.w.Write([]byte("Bar"))
}
ここまで出来たら最後にActionインターフェースを返す関数を引数にとって、http.HandlerFunc
を返す高階関数を定義して完成です。
type ActionCreator = func(http.ResponseWriter, *http.Request) Action
func CreateHandlerFunc(f ActionCreator) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
a := f(w, r)
a.Before()
a.Dispatch()
a.After()
}
}
func main() {
http.Handle("/foo", CreateHandlerFunc(NewFooAction))
http.Handle("/bar", CreateHandlerFunc(NewBarAction))
http.ListenAndServe(":8080", nil)
}
ここまでにいくつかのテクニックを紹介しましたが、どれも共通することはhttp.HandlerFunc
を返す関数を定義することです。
このことさえ覚えておけばここで紹介したこと以外でも、様々なことが可能となります。
上手く活用して保守性の高いコードを書いていきたいですね!