タイトル通り、前のエントリーの続きでデバッグ用のproxyサーバーを書いたのでそのときに引っ掛かったところをメモ書き。
httputil - The Go Programming Language
GoでProxyサーバーをたてるにはhttputilパッケージにあるNewSingleHostReverseProxy
を呼んで
ReverseProxy
をこねくり回してたてるのが一般的なので今回のProxyサーバーもこれを使って実装していく。
Proxyの部分は上でOKで次にデバッグのところだが、シンプルに受け取ったリクエスト/レスポンスをプリントしてもいいのだけれど、httputilパッケージにはDumpRequest
とDumpResponse
の2つの関数があるのでこれを使う。
dumps, err := httputil.DumpRequest(req, true)
or
dumps, err := httputil.DumpResponse(resp, true)
たったこれだけでリクエスト/レスポンスに含まれるヘッダーとボディのダンプしたバイト列を取得することが出来る。
そして、これら2つの関数を利用したhttp.RoundTripper
を実装してやればProxyサーバーの出来上がりです。
実装はこんな感じになります。
この例ではhttp.RoundTripper
の実装にはリクエストだけ、レスポンスだけを出力するためのフラグを持たせています。
func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
if t.DebugRequest {
dumps, err := httputil.DumpRequest(req, true)
if err != nil {
return nil, err
}
debugPrint(string(dumps))
}
resp, err = t.RoundTripper.RoundTrip(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
b = bytes.Replace(b, []byte("server"), []byte("schmerver"), -1)
body := ioutil.NopCloser(bytes.NewReader(b))
resp.ContentLength = int64(len(b))
resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
resp.Body = body
if t.DebugResponse {
_body, err := httputil.DumpResponse(resp, true)
if err != nil {
return nil, err
}
debugPrint(string(_body))
}
return resp, nil
}
ちなみに、RoundTripper
のコメントに次のような一文がある。
// RoundTrip should not modify the request, except for
// consuming and closing the Request's Body.
see: http - The Go Programming Language
今回はX-Forward-For
ヘッダーなりを付与する必要がなかったので忠告に従ってそのまま後ろにいるサーバーにリクエストを投げるように実装しました。
今回初めてhttp.RoundTrip
の低レベルなところを触ってhttp周りだけでもまだまだGoの知らないところあったということを再認識した。
そして、標準でここまで揃えてくれてるGoはいつもながら素敵ですねと。
yuokada/debugproxy: Proxy server for request debug
- go - Golang: how to read response body of ReverseProxy? - Stack Overflow
- Go net/httpパッケージの概要とHTTPクライアント実装例 - Qiita
- Go http.RoundTripper 実装ガイド - Qiita