| // Package proxyutil 提供统一的代理配置功能 | |
| // | |
| // 支持的代理协议: | |
| // - HTTP/HTTPS: 通过 Transport.Proxy 设置 | |
| // - SOCKS5: 通过 Transport.DialContext 设置(客户端本地解析 DNS) | |
| // - SOCKS5H: 通过 Transport.DialContext 设置(代理端远程解析 DNS,推荐) | |
| // | |
| // 注意:proxyurl.Parse() 会自动将 socks5:// 升级为 socks5h://, | |
| // 确保 DNS 也由代理端解析,防止 DNS 泄漏。 | |
| package proxyutil | |
| import ( | |
| "context" | |
| "fmt" | |
| "net" | |
| "net/http" | |
| "net/url" | |
| "strings" | |
| "golang.org/x/net/proxy" | |
| ) | |
| // ConfigureTransportProxy 根据代理 URL 配置 Transport | |
| // | |
| // 支持的协议: | |
| // - http/https: 设置 transport.Proxy | |
| // - socks5: 设置 transport.DialContext(客户端本地解析 DNS) | |
| // - socks5h: 设置 transport.DialContext(代理端远程解析 DNS,推荐) | |
| // | |
| // 参数: | |
| // - transport: 需要配置的 http.Transport | |
| // - proxyURL: 代理地址,nil 表示直连 | |
| // | |
| // 返回: | |
| // - error: 代理配置错误(协议不支持或 dialer 创建失败) | |
| func ConfigureTransportProxy(transport *http.Transport, proxyURL *url.URL) error { | |
| if proxyURL == nil { | |
| return nil | |
| } | |
| scheme := strings.ToLower(proxyURL.Scheme) | |
| switch scheme { | |
| case "http", "https": | |
| transport.Proxy = http.ProxyURL(proxyURL) | |
| return nil | |
| case "socks5", "socks5h": | |
| dialer, err := proxy.FromURL(proxyURL, proxy.Direct) | |
| if err != nil { | |
| return fmt.Errorf("create socks5 dialer: %w", err) | |
| } | |
| // 优先使用支持 context 的 DialContext,以支持请求取消和超时 | |
| if contextDialer, ok := dialer.(proxy.ContextDialer); ok { | |
| transport.DialContext = contextDialer.DialContext | |
| } else { | |
| // 回退路径:如果 dialer 不支持 ContextDialer,则包装为简单的 DialContext | |
| // 注意:此回退不支持请求取消和超时控制 | |
| transport.DialContext = func(_ context.Context, network, addr string) (net.Conn, error) { | |
| return dialer.Dial(network, addr) | |
| } | |
| } | |
| return nil | |
| default: | |
| return fmt.Errorf("unsupported proxy scheme: %s", scheme) | |
| } | |
| } | |