net/http.ClientにHookをかける
昨日のこと。jszwedko/go-circleci というパッケージを使用してCircleCI EnterprizeのAPIを叩くという処理を実装していたのですが、どうにもうまくいかない。正直に言ってこのパッケージはドキュメントがしっかりしている、という訳ではないし、エラーメッセージを見ても何がだめなのか(そもそも現在使用しているCircleCI Enterpriseで使用できるかもよくわかっていなかった)わからない。 しかしまぁ、自分でHTTP requestを作ったりしてあれやこれややるのもまぁ面倒であるので、なんとかデバッグしたいと思ったのですが、外部のパッケージをフォークして変更を加えてデバッグする・・・という様なことはもちろんやりたくないわけです。 このパッケージは*http.Client を指定できます。*http.Clientはインターフェースではなく構造体なので、別の実装に置き換えるということはできません。が、その実装はほぼほぼ後述するhttp.RoundTripperなため、http.RoundTripperをラップして、HTTP requestとHTTP responseをログに吐けばまぁ、何が問題かわかるだろう、と考えました。 そんなモノは誰かがすでに書いているだろう、というのはさておき、http.RoundTripperを実際にいじってみるということはやったことがなかったので、勉強がてらnasa9084/go-logtransport なるものを書きました。 書いていく途中で、考えたことなど、記録に残しておくのも良さそうと思ったため、本記事とします。 http.Clientとhttp.RoundTripper Go言語でHTTPのリクエストを発行するには基本として*http.Client というものを使用します。簡便のため、GET 、POST 、Head についてはパッケージグローバルの関数も用意されてはいるのですが、これらも内部的にはパッケージグローバルで宣言されたDefaultClient という*http.Client が使用されています。 *http.Clientはゼロ値で使用できるようにまとめられた構造体で、DefaultClient は*http.Client{}と宣言されています。 普段はこの*http.Clientを使用してHTTPの通信を行うわけですが、実は*http.Clientはそれほど多くの機能は持っていません。実際、持っているフィールドはたったの4つ(Go1.13時点)しかないのです。*http.Clientはリダイレクトやクッキーなどの一部の処理だけを受け持っていて、実際のHTTP通信のほとんどはフィールドとして保持しているhttp.RoudTripperが行います。 http.RoundTripperはインターフェースとして定義されていて、自由に差し替えをすることができます。特に指定していない場合は*http.Transportがデフォルトの実装として使用されます。 Goの他の標準パッケージの例に漏れず、http.RoundTripperは非常にシンプルなインターフェースで、次の様に定義されています。 1 2 3 type RoundTripper interface { RoundTrip(*Request) (*Response, error) } RoundTrip()がHTTP requestを受け取り、HTTP responseを返します。つまり、requestのログをとり、子RoundTripperのRoundTrip()を実行し、Responseのログをとってそのまま返す、という様なラッパーを書けば良さそうです。 1 2 3 4 5 6 7 8 9 func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { // Requestのログをとる resp, _ := t.Transport.RoundTrip(r) // responseのログをとる return resp, nil } 実際にrequestとresponseのログをとるには、net/http/httputilパッケージのDump系関数が使用できます。今回はクライアント側の実装なので、httputil.DumpRequestOutとhttputil.DumpResponseを使用します。...