GoのHTTPクライアントでは、transportがプロキシを選択します。変更前に標準のtransportを複製すれば、デフォルトクライアントを使う他のコードは従来の経路を保てます。

package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
    "net/url"
    "time"
)

func main() {
    proxyURL, err := url.Parse("http://203.0.113.10:8080")
    if err != nil {
        log.Fatal(err)
    }

    transport := http.DefaultTransport.(*http.Transport).Clone()
    transport.Proxy = http.ProxyURL(proxyURL)
    defer transport.CloseIdleConnections()

    client := &http.Client{
        Transport: transport,
        Timeout:   15 * time.Second,
    }

    response, err := client.Get("https://example.com/")
    if err != nil {
        log.Fatal(err)
    }
    defer response.Body.Close()

    fmt.Println(response.StatusCode)
    if _, err := io.Copy(io.Discard, response.Body); err != nil {
        log.Fatal(err)
    }
}

このアドレスは文書用の仮の値です。HTTPプロキシ一覧で選んだ候補など、実際のHTTPプロキシのIPとポートに置き換えてください。例で使うのはGoの標準ライブラリだけです。このHTTPプロキシリクエストにSOCKSパッケージやプロセス全体の環境変数は不要です。

DefaultTransportを直接変更しない理由

デフォルトのtransportは共有されています。書き換えると、そのプロキシの利用を求めていないコードまで動作が変わる可能性があります。リクエストで使われている最中の変更は、避けられるはずの並行処理の問題も招きます。専用のtransportを使えば、経路をこのクライアントの設定に含められます。

http.ProxyURLは、渡されたURLを一貫して選ぶセレクターを返します。これはプロキシ変数やバイパス規則を考慮するデフォルトの環境セレクターとは異なります。HTTP一覧の特定の1件をテストするのが目的なら、明示的なセレクターでこの曖昧さを取り除けます。

レスポンスを閉じ、クライアントを再利用する

例ではレスポンスボディを最後まで読み、閉じています。最後まで読むと、可能な場合にtransportが接続を再利用しやすくなります。同じプロキシ経由で複数回リクエストするプログラムでは、毎回新しい組を作らず、クライアントとtransportを保持してください。

クライアントのタイムアウトは、ソケットを開く時間だけでなく、レスポンスボディの読み取りを含むリクエストのライフサイクルも制限します。Getが返った後の読み取りエラーにも対処が必要です。403などのステータスはGoのtransportエラーではないため、StatusCodeとerrは別々に調べます。

関連記事