
在 go 的 net/http 客户端默认行为下,重定向过程会自动关闭并丢弃前序响应体,无法直接访问;但可通过自定义 checkredirect 钩子结合手动请求控制实现对重定向链中每一步响应(包括首次 3xx 响应)的完整捕获。
Go 标准库的 http.Client 在遇到 3xx 重定向状态码(如 301、302、307)时,默认会自动发起后续请求,并立即关闭并丢弃当前响应的 Body——这意味着你无法通过常规方式读取重定向前的响应头、状态码或响应体内容。
其核心逻辑位于 client.go 中:一旦判定需重定向(shouldRedirect(resp.StatusCode) 返回 true),客户端会尝试小量读取(≤2KB)响应体以复用底层 TCP 连接,随后调用 resp.Body.Close() 并丢弃整个响应对象。此时响应已不可用。
✅ 解决方案:禁用自动重定向,手动控制请求流程
你需要将 Client.CheckRedirect 设置为返回 http.ErrUseLastResponse(或自定义错误),从而中断自动重定向机制,使 Client.Do() 在收到首个重定向响应时即刻返回,而非继续跳转:
package main
import (
"fmt"
"io"
"log"
"net/http"
"strings"
)
func main() {
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 阻止自动重定向,保留当前响应
return http.ErrUseLastResponse
},
}
resp, err := client.Get("https://httpbin.org/redirect/2")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Printf("Status: %s\n", resp.Status) // e.g., "302 Found"
fmt.Printf("Location: %s\n", resp.Header.Get("Location")) // 重定向目标地址
// 可安全读取响应体(若服务端返回了 body,如某些 302 响应含 HTML 提示)
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Body (first response): %s\n", strings.TrimSpace(string(body)))
}⚠️ 注意事项与最佳实践
- http.ErrUseLastResponse 是标准信号,表示“使用本次响应,不要重定向”;它不会触发错误 panic,而是让 Do() 正常返回当前 *http.Response。
- 若需完整跟踪重定向链(如获取所有中间响应),应在 CheckRedirect 中不返回错误,而是自行记录 resp(需提前读取并保存关键字段),再通过 req.URL 构造新请求——但注意:此时 resp.Body 已被 Do() 内部关闭,必须在 CheckRedirect 调用前完成读取(这通常需重写 Do 逻辑,或使用第三方库如 gorequest)。
- 对于仅需首次重定向响应的场景,上述 ErrUseLastResponse 方案简洁可靠,无需侵入底层 Transport。
- 始终显式调用 resp.Body.Close(),即使读取失败,避免连接泄漏。
总结:Go 默认不暴露重定向前的响应,但通过主动拦截重定向流程,开发者完全可掌控每个 HTTP 交互环节——这是 Go “显式优于隐式”设计哲学的典型体现。










