
go 中调用 http.client.do 后 status=200 且无错误,但 resp.body 读取为空,通常并非请求构造问题,而是因误用 bufio.scanner.text() 导致响应流被提前耗尽或截断;正确方式应使用 io.readall 安全读取全部字节。
go 中调用 http.client.do 后 status=200 且无错误,但 resp.body 读取为空,通常并非请求构造问题,而是因误用 bufio.scanner.text() 导致响应流被提前耗尽或截断;正确方式应使用 io.readall 安全读取全部字节。
在 Go 中发起 HTTP 请求时,http.Client.Do 返回的 *http.Response 对象本身是可靠的——只要 err == nil 且 resp.StatusCode == 200,基本可确认服务端已成功返回数据。真正导致“响应体为空”的常见陷阱,往往隐藏在响应体读取逻辑中,而非请求头(如 Authorization)或请求方法(如 GET)本身。
你提供的代码结构完全正确:
✅ 正确使用 http.NewRequest 构造带自定义 Header 的请求;
✅ 正确复用 http.Client(无需每次新建);
✅ 正确调用 defer resp.Body.Close() 防止资源泄漏。
问题根源在于 readBody 函数的实现。若其中使用了如下模式:
func readBody(r io.Reader) (string, error) {
scanner := bufio.NewScanner(r)
var result strings.Builder
for scanner.Scan() {
result.WriteString(scanner.Text()) // ❌ 危险!Text() 不返回换行符,且 Scanner 默认每行限 64KB
}
return result.String(), scanner.Err()
}这会引发两个关键问题:
-
bufio.Scanner默认以行(\n)为单位扫描,而 JSON 或其他 API 响应可能不含换行符,导致Scan()立即返回false,循环不执行,结果为空字符串; -
Scanner内部有默认缓冲区大小(64KB),若响应体超长或含二进制/特殊字符,Text()可能失败并静默终止(scanner.Err()才反映错误,但常被忽略)。
✅ 正确做法:始终使用 io.ReadAll(Go 1.16+)或 ioutil.ReadAll(旧版)完整读取响应流:
import "io"
func readBody(r io.ReadCloser) ([]byte, error) {
defer r.Close() // 注意:此处 close 由 readBody 负责,原调用处需移除 defer resp.Body.Close()
return io.ReadAll(r) // ✅ 安全、完整、无假设
}
// 使用示例(整合优化版):
func fetchOrderItems() ([]byte, error) {
req, err := http.NewRequest("GET", "https://api.molt.in/v1/orders/11111111/items", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "secretToken") // 推荐 Set 替代 Add,避免重复头
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close() // 保持 defer 在最外层
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
// 可选:检查空响应
if len(body) == 0 {
return nil, errors.New("empty response body")
}
return body, nil
}⚠️ 注意事项:
- 不要混用
bufio.Scanner和io.ReadAll—— 一旦任一方式读取过resp.Body,流即被消耗,再次读取将返回空; -
req.Header.Add可多次调用添加同名 Header,若需唯一值(如Authorization),优先用req.Header.Set; - 生产环境建议添加超时控制:
&http.Client{Timeout: 30 * time.Second}; - 若需解析 JSON,直接传
bytes.NewReader(body)给json.Unmarshal,避免二次序列化开销。
总结:HTTP 请求成功 ≠ 响应体可读。排查“空响应”问题时,请优先审查 Body 的读取方式——io.ReadAll 是最安全、最通用的起点;仅在明确需要流式处理(如大文件下载)时,才谨慎选用 bufio.Scanner 或 io.Copy,并务必处理其边界条件与错误。

















