-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 添加正确关闭HTTP Response.Body的重要性及示例
- Loading branch information
cloaks
committed
Jan 18, 2025
1 parent
ab090e9
commit e4004bb
Showing
1 changed file
with
148 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
--- | ||
date: 2025-01-18 | ||
authors: | ||
- cloaks | ||
categories: | ||
- Golang 编程实践 | ||
tags: | ||
- Golang | ||
comments: true | ||
--- | ||
|
||
# **正确关闭 HTTP Response.Body 的重要性** | ||
|
||
在 Go 语言中,当执行 HTTP 请求时,会返回一个 `http.Response` 对象,其中包含了响应的主体 `Body`。为了避免资源泄露和确保连接复用,需要在适当的时候关闭 `Response.Body`。 | ||
|
||
以下是如何正确关闭 `Response.Body` 的详细说明,以及注意事项。 | ||
|
||
<!-- more --> | ||
|
||
## **为什么需要关闭 Response.Body** | ||
|
||
- **资源释放**:`Response.Body` 是一个 `io.ReadCloser`,不关闭会导致底层资源(如文件描述符、网络连接)泄露。 | ||
- **连接复用**:在启用 `Keep-Alive` 的 HTTP 请求中,关闭 `Response.Body` 能让底层连接被复用以提升性能。 | ||
|
||
如果不关闭,连接将被永久占用,直到垃圾回收器回收该对象,但这可能会造成延迟且资源无法及时释放。 | ||
|
||
## **示例代码分析** | ||
|
||
以下是一个典型的 HTTP 请求处理代码: | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
) | ||
|
||
func main() { | ||
resp, err := http.Get("https://api.ipify.org?format=json") | ||
|
||
// 确保 resp 不为 nil 后再 defer Close | ||
if resp != nil { | ||
defer resp.Body.Close() // OK:无论是否读取 Body,都要关闭 | ||
} | ||
|
||
// 错误处理 | ||
if err != nil { | ||
fmt.Println("HTTP 请求失败:", err) | ||
return | ||
} | ||
|
||
// 读取响应体 | ||
body, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
fmt.Println("读取响应体失败:", err) | ||
return | ||
} | ||
|
||
fmt.Println("响应内容:", string(body)) | ||
} | ||
``` | ||
|
||
## **常见错误** | ||
|
||
### 1. 在 nil 检查前使用 `defer` | ||
|
||
如果 `resp` 为 `nil`,直接执行 `defer resp.Body.Close()` 会导致 panic。 | ||
|
||
```go | ||
defer resp.Body.Close() // 错误:resp 可能为 nil | ||
if err != nil { | ||
fmt.Println(err) | ||
return | ||
} | ||
``` | ||
|
||
**解决方法**:确保 `resp` 不为 nil 后再关闭: | ||
|
||
```go | ||
if resp != nil { | ||
defer resp.Body.Close() | ||
} | ||
``` | ||
|
||
### 2. 忽略剩余数据 | ||
|
||
在 Go 1.5 之前,`Close` 会自动读取并丢弃响应体中的剩余数据以支持连接复用。但从 Go 1.5 开始,这需要手动处理。 | ||
|
||
**解决方法**:在关闭前读取或丢弃剩余数据: | ||
|
||
```go | ||
_, err = io.Copy(ioutil.Discard, resp.Body) // 丢弃剩余数据 | ||
if err != nil { | ||
fmt.Println("丢弃数据失败:", err) | ||
} | ||
``` | ||
|
||
### 3. 部分读取数据 | ||
|
||
例如,使用 `json.NewDecoder` 解码响应时,只读取了部分数据,未处理剩余数据,这可能导致连接无法复用。 | ||
|
||
**解决方法**:确保在关闭前处理剩余数据: | ||
|
||
```go | ||
decoder := json.NewDecoder(resp.Body) | ||
err = decoder.Decode(&data) // 解码数据 | ||
if err != nil { | ||
fmt.Println("解码失败:", err) | ||
} | ||
_, _ = io.Copy(ioutil.Discard, resp.Body) // 丢弃多余数据 | ||
``` | ||
|
||
## **示例:处理非 UTF-8 数据** | ||
|
||
当响应体包含非 UTF-8 数据时,使用 `defer` 关闭前需要小心处理: | ||
|
||
```go | ||
data := "A\xfe\x02\xff\x04" | ||
for _, v := range data { | ||
fmt.Printf("%#x ", v) // 解析 UTF-8:0x41 0xfffd 0x2 0xfffd 0x4 | ||
} | ||
fmt.Println() | ||
|
||
for _, v := range []byte(data) { | ||
fmt.Printf("%#x ", v) // 原始字节:0x41 0xfe 0x2 0xff 0x4 | ||
} | ||
``` | ||
|
||
如果 `resp.Body` 包含非 UTF-8 数据,建议直接读取为字节切片: | ||
|
||
```go | ||
body, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
fmt.Println("读取响应失败:", err) | ||
} | ||
fmt.Println("字节数据:", body) | ||
``` | ||
|
||
## **总结** | ||
|
||
1. **始终关闭 `Response.Body`**:即使不使用响应体,也需要调用 `Close`,可通过 `defer` 实现。 | ||
2. **避免 nil 导致 panic**:确保在 `defer` 调用前检查 `Response` 是否为 nil。 | ||
3. **处理剩余数据**:在关闭 `Body` 前使用 `io.Copy(ioutil.Discard, resp.Body)` 或完整读取数据以确保连接复用。 | ||
4. **处理非 UTF-8 数据**:对于非 UTF-8 响应,优先读取为字节切片。 | ||
|
||
这样可以在不影响性能的前提下,确保代码的健壮性和可维护性。 |