Golang使用multipart/form-data仅关闭一次就永久挂起问题咨询
问题原因分析
这个现象的核心原因和mime/multipart.Writer的实现逻辑、HTTP请求的构造顺序直接相关:
- 首先需要确认实际代码中
multiPartWriter.Close()的调用位置是否和示例一致:- 如果你是先调用
http.NewRequest,再调用Close(),就会出现永久挂起的问题:http.NewRequest接收bytes.Buffer作为请求体时,会当场读取Buffer的长度作为请求的Content-Length头的值- 此时还没调用
Close(),Buffer是空的,Content-Length被设为0 - 之后调用
Close()会向Buffer写入multipart结束边界符,Buffer长度增加,但Content-Length已经不会更新了 - 发送请求时,服务端收到
Content-Length: 0和multipart/form-data的内容类型,但没收到任何multipart数据,会一直等待后续数据传输,导致请求挂起
- 你所说的「调用两次Close就能正常运行」本质上是调用位置的巧合:如果第一次
Close()是在http.NewRequest之前,第二次在之后,那第一次Close()已经把结束边界符写入了Buffer,http.NewRequest能读到正确的长度,自然不会挂起,而标准库的multipart.Writer.Close()是幂等实现,内置了closed标识,第二次调用不会做任何操作,完全不影响结果
- 如果你是先调用
- 额外补充:你提供的示例代码没有处理
client.Do返回的响应体,按照标准库要求,无论请求是否成功,都需要调用resp.Body.Close()释放连接,否则可能会出现连接泄漏的问题,未设置客户端超时也可能在网络异常时出现假挂起的情况。
修复方案
把multiPartWriter.Close()固定放在http.NewRequest调用之前即可,不需要多次调用,正确写法如下:
func main() { var requestBody bytes.Buffer multiPartWriter := multipart.NewWriter(&requestBody) // 必须在构造请求之前关闭,保证buffer内容完整,Content-Length计算正确 if err := multiPartWriter.Close(); err != nil { // 错误处理逻辑 panic(err) } req, err := http.NewRequest("POST", "https://api.telegram.org/bot<telegram token>/getme", &requestBody) if err != nil { panic(err) } req.Header.Set("Content-Type", multiPartWriter.FormDataContentType()) // 建议设置超时,避免网络问题导致永久挂起 client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Do(req) if err != nil { panic(err) } // 必须关闭响应体释放连接 defer resp.Body.Close() }
内容的提问来源于stack exchange,提问作者gcoder
相关产品推荐
相关产品推荐

