概要
開発中にサーバレスポンスの内容を表示したい時があると思いますが
- クライアントが受け取ったレスポンス
- サーバが送ったレスポンス
のそれぞれをログなどで表示する方法を紹介します。
環境
- go 1.14.3
クライアントが受け取ったレスポンスを表示
クライアントでは受け取ったレスポンスはio.ReadCloser
です。
なので一度読み込むと以降は読み込めなくなるのでそこだけ注意が必要です。
io.Reader系で実処理ではreadする処理がほぼ必ず入るので、io.TeeReader を使うと良いです。
ref: Goのioパッケージのメソッドを図示 - Carpe Diem
io.TeeReader
はreadすると同時にio.Writerの方にストリームデータが流れるので、これをPrintすれば良いです。
今回は標準エラー出力に出します。
コード
type Foo struct { Name string `json:"name"` Age int `json:"age"` } func main() { resp, err := http.Get("http://localhost:8080") if err != nil { log.Fatal(err) } defer resp.Body.Close() r := io.TeeReader(resp.Body, os.Stderr) // here var foo Foo err = json.NewDecoder(r).Decode(&foo) if err != nil { log.Fatal(err) } fmt.Println(foo) }
サーバが送ったレスポンスを表示
今度は逆にサーバ側が送るレスポンスを表示する方法です。
前提として
- net/httpのみでWebフレームワークは使わない
- ミドルウェアとしてハンドリングする
とします。
イメージとしてはレスポンスを書き込む際にio.MultiWriterを使って
ref: Goのioパッケージのメソッドを図示 - Carpe Diem
レスポンスとデバッグログ用のio.Writerの両方に書きこみますが、レスポンスで扱っているhttp.ResponseWriterは単純なio.Writer
ではないので少し工夫が必要です。
コード
先にコードを紹介すると以下です。
type Foo struct { Name string `json:"name"` Age int `json:"age"` } type wrapper struct { http.ResponseWriter mw io.Writer } func (w *wrapper) Write(b []byte) (int, error) { return w.mw.Write(b) } func middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { buf := bytes.NewBuffer(nil) wrap := &wrapper{w, io.MultiWriter(w, buf)} next.ServeHTTP(wrap, r) fmt.Println("[DEBUG]", buf.String()) // here }) } func main() { mux := http.NewServeMux() mux.Handle("/", middleware(fooHandler())) if err := http.ListenAndServe(":8080", mux); err != nil { log.Fatal(err) } } func fooHandler() http.HandlerFunc { body := Foo{"Alice", 20} return func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(body) } }
ポイント
ポイントとしては
io.MultiWriter
で通常のレスポンス、デバッグログ用のbufferの両方に書き込む- wrapperでWrite()する時に
io.MultiWriter
で生成されたio.Writer
を使う
です。
wrap := &wrapper{w, io.MultiWriter(w, buf)} next.ServeHTTP(wrap, r)
でwrapperをハンドラーの書き込み先としてセットしているので、ハンドラーがWrite()する際はoverrideした
func (w *wrapper) Write(b []byte) (int, error) { return w.mw.Write(b) }
こちらが呼ばれます。
なのでこの時にio.MultiWriter
を使うことで、実レスポンスとデバッグログの両方に書き込むことができます。
サンプルコード
今回書いたサンプルコードはこちら
まとめ
ストリームデータであるサーバレスポンスを、io.TeeReader
やio.MultiWriter
を活用してデバッグ表示する方法を紹介しました。