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 代理 IP 和端口,例如从HTTP 代理列表中挑选的候选。示例只使用 Go 标准库;这次 HTTP 代理请求不需要 SOCKS 包,也不需要设置进程级环境变量。

为什么不直接修改 DefaultTransport?

默认 transport 是共享的。修改它可能改变本来没有要求使用代理的代码的行为;在请求正在使用 transport 时修改它,也会带来本可避免的并发问题。使用专用 transport,就能把路由明确放在这个客户端的配置中。

http.ProxyURL 返回的选择器会始终选用传入的 URL。这与默认的环境选择器不同,后者会考虑代理变量和绕过规则。如果你想测试 HTTP 列表中的某一个具体条目,显式选择器就能排除这部分歧义。

关闭响应体,并复用客户端

示例会读完并关闭响应体。完整读取响应体,有助于 transport 在条件允许时复用连接。程序若通过同一代理发送多次请求,应保留客户端和 transport,而不是每次都重新创建一对。

客户端超时不只约束打开 socket,还覆盖请求的生命周期,包括读取响应体。Get 返回后,读取错误仍然需要处理。403 这样的状态不是 Go transport 错误,因此应分别检查 StatusCode 和 err。

相关文章