如何在Golang内置HTTP客户端中获取HTTP代理响应头?
问题
我正在使用Golang默认的net/http库通过HTTP代理发送简单的HTTP GET请求,希望读取代理对CONNECT方法请求的回复内容(也就是第一个代理回复)。
代理回复的明文格式如下:
HTTP/1.1 200 OK Request-Uid: <some id> <another header>: <another value>
我的现有代码如下:
... proxyUrlParsed, errUrl := url.Parse(proxyUrl) tr := &http.Transport{ Proxy: http.ProxyURL(proxyUrlParsed), } client := &http.Client{ Transport: tr, } request, errReq := http.NewRequest("GET", targetUrl, nil) response, errDo := client.Do(request) // 这里的Response只包含目标资源的响应头,没有中间代理的响应头
我曾通过DialContext部分解决该问题,但需要手动实现部分协议内容,后续维护成本较高且不够便捷。请问是否有更简便巧妙的实现方式?
解决方案
可以通过手动发起CONNECT请求建立代理连接的方式,直接捕获代理的响应,同时复用该连接发送后续的GET请求,无需深入修改http.Transport的底层逻辑,代码更易维护:
package main import ( "bufio" "fmt" "net" "net/http" "net/url" "strings" ) func main() { proxyUrl := "http://your-proxy:port" targetUrl := "https://example.com" // 解析代理和目标URL proxyParsed, err := url.Parse(proxyUrl) if err != nil { panic(err) } targetParsed, err := url.Parse(targetUrl) if err != nil { panic(err) } // 1. 建立到代理的TCP连接 conn, err := net.Dial("tcp", proxyParsed.Host) if err != nil { panic(err) } defer conn.Close() // 2. 发送CONNECT请求到代理 connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", targetParsed.Host, targetParsed.Host) _, err = conn.Write([]byte(connectReq)) if err != nil { panic(err) } // 3. 读取并解析代理的CONNECT响应 respReader := bufio.NewReader(conn) proxyResp, err := http.ReadResponse(respReader, nil) if err != nil { panic(err) } defer proxyResp.Body.Close() // 这里可以获取代理响应的Header,比如Request-Uid fmt.Println("代理响应头:") for k, v := range proxyResp.Header { fmt.Printf("%s: %s\n", k, strings.Join(v, ", ")) } if proxyResp.StatusCode != http.StatusOK { panic(fmt.Sprintf("代理CONNECT请求失败:%s", proxyResp.Status)) } // 4. 复用该连接,创建自定义Transport发送GET请求到目标服务器 tr := &http.Transport{ DialContext: func(ctx net.Context, network, addr string) (net.Conn, error) { // 直接返回已经建立好的代理连接 return conn, nil }, } client := &http.Client{Transport: tr} getReq, err := http.NewRequest("GET", targetUrl, nil) if err != nil { panic(err) } // 发送GET请求并处理目标响应 targetResp, err := client.Do(getReq) if err != nil { panic(err) } defer targetResp.Body.Close() fmt.Println("\n目标服务器响应状态:", targetResp.Status) // 后续处理目标响应内容... }
方案优势
- 无需手动实现完整的HTTP代理协议,直接复用标准库的
http.ReadResponse解析代理响应,减少维护成本 - 逻辑清晰:先处理代理连接,再复用连接发送业务请求,步骤明确
- 可以完整获取代理的响应头和状态码,满足需求
如果需要更通用的封装,还可以把上述逻辑包装成自定义的http.RoundTripper,方便在多个请求中复用。
内容的提问来源于stack exchange,提问作者greggyNapalm
相关产品推荐
相关产品推荐

