Golang请求API报connection reset by peer 同环境curl/Python正常
Golang调用Qualys API触发connection reset by peer问题解决
问题描述
同一机器环境下,使用curl、Python requests库发起完全相同的Qualys平台API请求可正常获得响应,使用Golang标准库net/http编写的HTTP客户端发起相同请求时始终返回错误:read: connection reset by peer。已在Go 1.10.8、1.17.2、1.18.3三个版本测试,均稳定复现该问题。
复现信息
- 接口预期返回内容
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE SIMPLE_RETURN SYSTEM "https://qualysapi.qg2.apps.qualys.eu/api/2.0/simple_return.dtd"> <SIMPLE_RETURN> <RESPONSE> <DATETIME>2022-07-02T07:50:26Z</DATETIME> <CODE>2000</CODE> <TEXT>Bad Login/Password</TEXT> </RESPONSE> </SIMPLE_RETURN>
- 可正常执行的curl命令
curl -H "X-Requested-With:curl" -u "asdf:asdfdasf" "https://qualysguard.qg2.apps.qualys.eu/api/2.0/fo/asset/host/?action=list"
- 可正常执行的Python代码
# test.py import requests url = 'https://qualysguard.qg2.apps.qualys.eu/api/2.0/fo/asset/host/?action=list' res = requests.get(url, headers={"X-Requested-With": "curl"}, auth=("asdf", "asdfas")) print(res.text)
执行命令python3 test.py即可正常获得接口返回。
- 运行失败的Golang代码
package main import ( "encoding/base64" "fmt" "io/ioutil" "log" "net/http" ) func main() { url := "https://qualysguard.qg2.apps.qualys.eu/api/2.0/fo/asset/host/?action=list" client := http.Client{} req, err := http.NewRequest("GET", url, nil) if err != nil { panic(err) } req.Header.Set("X-Requested-With", "curl") req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("asdf:asdfas"))) resp, err := client.Do(req) if err != nil { panic(err) } body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalln(err) } sb := string(body) fmt.Println(sb) }
根因
Qualys欧洲区qg2节点的API网关对HTTP/2协议的兼容存在缺陷,Golang默认的HTTP客户端会在TLS握手时通过ALPN协商优先使用HTTP/2协议,网关识别到HTTP/2请求后会直接重置连接,触发connection reset by peer错误。而curl、Python requests默认优先使用HTTP/1.1协议,不会触发该网关bug。
修复方案
自定义HTTP客户端Transport配置,强制禁用HTTP/2协商,让客户端走HTTP/1.1协议发起请求即可,修改后的可正常运行Golang代码如下:
package main import ( "crypto/tls" "encoding/base64" "fmt" "io/ioutil" "log" "net/http" ) func main() { url := "https://qualysguard.qg2.apps.qualys.eu/api/2.0/fo/asset/host/?action=list" // 自定义Transport,禁用HTTP/2协商 client := http.Client{ Transport: &http.Transport{ TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), }, } req, err := http.NewRequest("GET", url, nil) if err != nil { panic(err) } req.Header.Set("X-Requested-With", "curl") // 可选:显式设置和curl一致的User-Agent,避免被WAF规则拦截 req.Header.Set("User-Agent", "curl/7.68.0") req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("asdf:asdfas"))) resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatalln(err) } fmt.Println(string(body)) }
补充排查点
如果上述修改后仍报错,可通过tcpdump/Wireshark抓包对比curl、Python请求和Golang请求的差异,重点对齐以下配置:
- TLS握手阶段的加密套件(Cipher Suites)列表,确保和正常请求使用的套件匹配
- 请求头的
Accept、Accept-Encoding字段,和正常请求保持一致 - 禁用TCP自动窗口缩放、TCP快速打开等可能被网关异常拦截的TCP特性
内容的提问来源于stack exchange,提问作者Xiaorong Liao
相关产品推荐
相关产品推荐

