Go代理服务器访问特定网站时固定使用单一子代理问题排查
问题根源分析
- HTTPS请求未被正确处理:goproxy的
OnRequest()仅针对HTTP请求生效,HTTPS请求的CONNECT握手阶段不会触发该回调。这部分请求会复用goproxy初始的代理配置,导致固定使用某台子代理。 - 全局资源并发冲突:直接修改全局的
proxy.Tr和proxy.ConnectDial,多个请求并发时会互相覆盖代理设置,同时无法保证每个请求都能正确获取到ServerPool分配的子代理。
修复方案
1. 针对HTTPS的CONNECT请求添加处理逻辑
使用OnConnect()回调处理HTTPS的握手阶段,为每个CONNECT请求分配子代理:
proxy.OnConnect().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { serverUrl, err := serverPool.GetNextServer() if err != nil { return goproxy.RejectConnect, "" } // 指示goproxy通过选中的子代理建立CONNECT连接 return goproxy.OkConnect, serverUrl.String() })
2. 避免修改全局Transport,为每个HTTP请求使用独立实例
不要修改全局的proxy.Tr,通过上下文为每个请求绑定独立的Transport实例,避免并发冲突:
proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { serverUrl, err := serverPool.GetNextServer() if err != nil { return nil, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusServiceUnavailable, "No available proxy server") } // 复制全局Transport,避免修改全局配置 customTr := proxy.Tr.Clone() customTr.Proxy = func(req *http.Request) (*url.URL, error) { return serverUrl, nil } // 将自定义Transport绑定到当前请求上下文 ctx.Transport = customTr return req, nil })
3. 确保ServerPool的GetNextServer线程安全
检查GetNextServer()方法是否正确加锁,以下是轮询模式的实现示例:
import "errors" func (sp *ServerPool) GetNextServer() (*url.URL, error) { sp.mu.Lock() defer sp.mu.Unlock() if len(sp.servers) == 0 { return nil, errors.New("empty server pool") } // 轮询:取出第一个服务器,移到列表末尾 server := sp.servers[0] sp.servers = append(sp.servers[1:], server) return server, nil }
额外说明
- 之前的代码中,全局修改
proxy.ConnectDial会导致所有CONNECT请求共享同一个代理,未触发OnRequest的HTTPS请求自然会固定使用该代理,只有重启服务时才会重新初始化这个值。 - 确保子代理的IPv6配置正确,Go标准库默认支持IPv6连接,无需额外配置。
内容的提问来源于stack exchange,提问作者mejdenovac
相关产品推荐
相关产品推荐

