
go 标准库的 http.client 默认会在重定向时自动丢弃并关闭前序响应体,无法直接访问;但可通过自定义 checkredirect 配合手动请求链路控制,或使用 transport.roundtrip 绕过重定向逻辑来捕获原始响应。
在 Go 的 HTTP 客户端设计中,重定向(如 301/302)由 http.Client 自动处理,默认行为是:收到重定向响应后,立即读取并丢弃响应体(为复用底层 TCP 连接),然后关闭 resp.Body,最后发起下一次请求。这意味着——原始响应体在重定向发生后即不可访问。
核心原因在于源码逻辑(client.go#L384–L399):
if shouldRedirect(resp.StatusCode) {
const maxBodySlurpSize = 2 << 10
if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize {
io.CopyN(io.Discard, resp.Body, maxBodySlurpSize) // 强制消费小响应体
}
resp.Body.Close() // ⚠️ 关闭后无法再读取
// ... 构造新请求,continue 循环
}因此,标准 client.Do(req) 无法返回中间重定向响应。但有以下两种实用方案可实现目标:
✅ 方案一:禁用自动重定向,手动控制请求链(推荐)
设置 Client.CheckRedirect 返回 http.ErrUseLastResponse,强制终止自动重定向,并在每次响应后自行判断是否需继续请求:
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // 停止重定向,返回当前响应
},
}
req, _ := http.NewRequest("GET", "https://httpbin.org/redirect-to?url=/headers", nil)
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// 此时 resp 是第一个重定向响应(如 302)
fmt.Printf("Status: %s\n", resp.Status) // Status: 302 FOUND
fmt.Printf("Location: %s\n", resp.Header.Get("Location")) // Location: /headers
// 若需继续跳转,可手动解析 Location 并发起新请求
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
nextURL := resp.Header.Get("Location")
if nextURL != "" {
nextReq, _ := http.NewRequest("GET", nextURL, nil)
nextResp, _ := client.Do(nextReq)
defer nextResp.Body.Close()
// 处理最终响应...
}
}✅ 优势:完全可控、无需修改 Transport、兼容性好
⚠️ 注意:需自行处理重定向逻辑(如相对 URL 解析、循环检测、认证透传等)
✅ 方案二:使用 Transport.RoundTrip 绕过 Client 层
RoundTrip 不执行重定向逻辑,仅完成单次请求-响应,适合需要逐跳分析的场景:
tr := &http.Transport{}
req, _ := http.NewRequest("GET", "https://httpbin.org/redirect-to?url=/headers", nil)
resp, err := tr.RoundTrip(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// 直接获取首次响应(302),无需担心被自动消费
body, _ := io.ReadAll(resp.Body)
fmt.Printf("First response body (truncated): %s\n", string(body[:min(len(body), 200)]))✅ 优势:最轻量、零重定向干预
⚠️ 注意:不继承 Client 的超时、重试、CookieJar 等高级特性,需自行补充
? 总结与建议
- 不要依赖 CheckRedirect 获取响应体:该回调仅接收 *http.Request,不暴露 *http.Response;
- 优先选用方案一:通过 http.ErrUseLastResponse + 手动跳转,兼顾可控性与完整性;
- 若仅需调试或审计首跳响应,方案二更简洁;
- 生产环境中务必添加重定向次数限制(避免无限跳转)、URL 安全校验(防止开放重定向漏洞)及超时控制。
掌握这一机制,不仅能解决“获取重定向前响应”的具体需求,更能深入理解 Go HTTP 客户端的设计权衡与扩展边界。










