gojiはgolangのWebApplicationFrameworkです。
gojiにはMiddlewareという仕組みがあり、これを使うことで、リクエストごとに共通して必要な処理を集約することができます。
gojiはリクエストのロギングを行うMiddlewareが用意されているので、それを参考に見ていきます。
func Logger(c *web.C, h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
reqID := GetReqID(*c)
printStart(reqID, r)
lw := mutil.WrapWriter(w)
t1 := time.Now()
h.ServeHTTP(lw, r)
if lw.Status() == 0 {
lw.WriteHeader(http.StatusOK)
}
t2 := time.Now()
printEnd(reqID, lw, t2.Sub(t1))
}
return http.HandlerFunc(fn)
}
上記のLoggerでは、簡単にいうと、以下を行っています。
- リクエストIDのログ出力(リクエストIDの生成は別のmiddlewareで行われています。)
- リクエスト自体の処理
- リクエストIDとともに、処理にかかった時間や返却したHTTPステータスをログ出力
Middlewareはこのように、リクエストを処理する前後に処理を挟み込むことができます。
作り方
Middlewareとして、任意の名称の関数を作ります。func Logger(c *web.C, h http.Handler) http.Handler
middlewareの関数定義はこちら(Mux -> mStack -> mLayer)
その中で、golangでの標準のhttp.HandlerFuncfunc(w http.ResponseWriter, r *http.Request)
を実装している関数を返しています。
h.ServeHTTP(r,w)
が、リクエスト自体の処理になるので、その前に時間を記録して、その後の時間との差分で処理時間を出しています。
Middlewareは複数登録可能
また、Middlewareは、複数登録が可能です。
gojiでも、DefaultMuxには、以下が登録されています。
- リクエストIDの生成
- リクエストログの記録
- panicリカバリ
- URL内の文字列をパラメータとして扱う処理
Middlewareは登録順に処理されます。そのため、他のMiddlewareが入っていること前提で処理を作る場合は、順番に気をつけましょう。
特定のパスだけに適用したいMiddlewareがある場合
認証処理をMiddlewareで作った際に、特定のパスには、必要としない場合は、Muxを分離します。例えば、ログインページは、そもそも認証するところなので、認証処理が入っていると、誰もログインできなくなります。
loginMux := web.New()
loginMux.Hundle("/login", func(...))
resourceMux := web.New()
resourceMux.Hundle("/resource/users", func(...))
resourceMux.Use(AuthCheck) // AuthCheckは自分で作る認証Middleware
rootMux := web.DefaultMux
rootMux.Handle("/login", loginMux)
rootMux.Handle("/resource/*", resourceMux)
こんな感じで、rootを作って、パスごとにmuxを登録します。(ここはrootの代わりにgoji.Handle()などで登録してもOKです)
この場合、rootに登録されているMiddlewareが実行され、その後それぞれのMuxのMiddlewareの順番に実行されます。そのため、認証処理はresouceのmuxにだけ登録するようにすればOKです。
他にも監視用のエンドポイントだけ特定処理をする/しないといった使い方が出来そうです。
補足
他にも、共通のレスポンスヘッダ挿入や、プロキシ下にいる場合のx-forwarded-forヘッダへの切り替えなど、参考になるMiddlewareがgojiにはあります。
Logger内のmutil.WrapWriter(w)
これは何をしているかというと、通常のhttp.ResponseWriterでは、出力処理を行うところでしか、httpステータスを知ることができません。セットするだけで、後から見る機能がないためです。gojiでは、WrapWriterで、処理をフックして、httpステータスを後から参照できるようにしています。
こういったテクニックは、ソースを読んでみると勉強になります。