uokadaの見逃し三振は嫌いです

ここで述べられていることは私の個人的な意見に基づくものであり、私が所属する組織には一切の関係はありません。

GoでHTTPリクエストのdebug proxyを書いた

f:id:uokada:20170515000805j:plain

タイトル通り、前のエントリーの続きでデバッグ用のproxyサーバーを書いたのでそのときに引っ掛かったところをメモ書き。

httputil - The Go Programming Language

GoでProxyサーバーをたてるにはhttputilパッケージにあるNewSingleHostReverseProxy を呼んで ReverseProxy をこねくり回してたてるのが一般的なので今回のProxyサーバーもこれを使って実装していく。

Proxyの部分は上でOKで次にデバッグのところだが、シンプルに受け取ったリクエスト/レスポンスをプリントしてもいいのだけれど、httputilパッケージにはDumpRequestDumpResponseの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

  1. go - Golang: how to read response body of ReverseProxy? - Stack Overflow
  2. Go net/httpパッケージの概要とHTTPクライアント実装例 - Qiita
  3. Go http.RoundTripper 実装ガイド - Qiita