给 Go HTTP 客户端单独配置代理
克隆 Go 的默认 transport,为选定的代理 IP 设置 ProxyURL,避免影响其他客户端的代理行为。
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。