Goのリバースプロキシーでレスポンスを書き換える

フューチャーアーキテクトアドベントカレンダーの5日目の
サーバーサイドレンダリングの代替としてPrerenderを試してみた
の最後で、「Go実装について 長くなったので説明は省略します。だれかがGoアドベントカレンダーを落としたら書くかも?」と書いていたら、早速落とす人がいたらしいので、ハイジャックします。

リバースプロキシーはnet/http/httputilパッケージの ReverseProxyを使えば簡単につくれますよ、という説明はよく見かけるのですが、レスポンスを書き換える方法はまとまった情報がなかったので紹介します。

リクエストを書き換える

go言語でリバースプロキシというブログのエントリーにある通りです。

向き先を書き換える、Directorというフック関数を設定してあげることで、向き先を変えられます。なお、このエントリーにあるように、この関数の中でrequestを書き換えることでドメインごと入れ替えたりとかもできますし、試してはないですが、当然ヘッダーを足すとかもできるでしょう。

    director := func(request *http.Request) {
        request.URL.Scheme = "http"
        request.URL.Host = ":9001"
    }
    rp := &httputil.ReverseProxy{Director: director}
    server := http.Server{
        Addr:    ":9000",
        Handler: rp,
    }

レスポンスを書き換える

レスポンスを書き換えるフック関数も用意されています。ModifyResponseというフィールドにこの関数を設定します。

modifier := func(res *http.Response) error {
    // 別途用意したheadless chromeの出力を分析したDOMのソース
    // を元に、goqueryを使ってレスポンスを書き換えたstring型のhtmlという
    // 変数を用意しています
    document, err := goquery.NewDocumentFromReader(res.Body)
    if err != nil {
        return err
    }
    :
    document.Find("head").AppendHtml(result.OGP)
    body := document.Find(route.BodySelector)
    body.SetHtml("")
    body.AppendHtml(result.InnerHTML)
    html, err := document.Html()
    if err != nil {
        return err
    }
    // ここからが汎用的なところで、この文字列を``io.ReadCloser``にして
    // res.Bodyを差し替えます
    b := []byte(html)
    res.Body = ioutil.NopCloser(bytes.NewReader(b))
    return nil
}

rp := &httputil.ReverseProxy{
    Director: director,
    ModifyResponse: modifier,
}

[]byteにしてからbytes.NewReaderで初期化していたのは、strings.NewReaderで良かった予感。
なお、内容を書き換える場合、Content-Lengthをわざわざ書き換えて上げる必要はありません。Bodyだけ置き換えればあとはhttputilパッケージが勝手にやってくれます。


追記:github.com/elazarl/goproxyというのもあるらしい