有一次做压力测试,客户端经常出现如下错误:
http2: Transport: cannot retry err [http2: Transport received Server's graceful shutdown GOAWAY] after Request.Body was written; define Request.GetBody to avoid this error
是 Golang 中使用 HTTP/2 协议时,客户端收到服务端的 GOAWAY 信号(表示服务端准备关闭连接)时发生的错误。
错误含义简要解释:
GOAWAY
是 HTTP/2 协议中的一个信号,表示服务端打算关闭连接。- Golang HTTP 客户端如果在连接被关闭时还打算重试请求,而请求已经包含了
Body
,就必须有办法重新读取请求体。 - 如果没有为
Request.Body
定义GetBody
方法,那么 Golang 无法重新读取请求体,自然也就无法重试。
解决方案
✅ 方案一:为请求显式设置 GetBody
字段
如果你手动构造了 http.Request
对象并包含了 Body
,就应该设置 GetBody
,示例如下:
body := []byte("your request body here")
req, err := http.NewRequest("POST", url, bytes.NewReader(body))
if err != nil {
// handle error
}
req.GetBody = func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(body)), nil
}
这样,如果连接重试时 Golang 可以重新从 GetBody()
获取请求体。
✅ 方案二:使用 http.Client
重试逻辑时避免重试不可重读的请求体
例如上传文件、流式数据时不要依赖 Golang 自动重试,可以改为手动管理连接、错误捕获和重试逻辑。
✅ 方案三:禁用 HTTP/2(仅作为最后手段)
如果你确定服务端的 HTTP/2 实现有问题,或者你不依赖 HTTP/2,可以选择禁用它:
tr := &http.Transport{
TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
}
client := &http.Client{Transport: tr}
但一般不推荐,除非你知道自己在做什么。
总结
如果你遇到这个错误,大概率是你构造了带 Body
的请求,但是没有设置 GetBody
。设置好它即可解决该问题。
如果你愿意贴一下你的请求代码,我可以帮你具体改一下。