Go 的 http.Client 可通过设置 CheckRedirect 函数为返回 http.ErrUseLastResponse 来禁用自动重定向,从而手动处理 3xx 响应;需注意 307/308 语义差异、body 重用、header 克隆及循环检测。

Go 的 http.Client 默认会自动跟随重定向(最多 10 次),但很多实际场景下你需要手动控制——比如调试跳转链、获取中间响应头、避免循环重定向,或处理非标准跳转逻辑(如 307/308 语义差异)。直接依赖默认行为容易踩坑。
如何禁用自动重定向并手动处理
关键在于自定义 http.Client 的 CheckRedirect 字段。它是一个函数,当收到 3xx 响应时被调用;若返回非 nil 错误,则中止重定向并把当前响应返回给调用方。
常见做法是设为 nil 或返回 http.ErrUseLastResponse:
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // 停在第一个 3xx 响应
},
}
注意:http.ErrUseLastResponse 是 Go 标准库预定义的错误,不是字符串字面量;用错会导致 panic。
立即学习“go语言免费学习笔记(深入)”;
- 如果想完全禁用(包括不发起后续请求),必须返回任意非 nil 错误,
http.ErrUseLastResponse是最明确的选择 -
via参数包含已发出的请求历史,可用于检测循环(比如检查req.URL.String()是否已在via中出现过) - 不要在
CheckRedirect中修改req.Header后直接返回nil——这等同于继续自动跳转,且可能丢失原始意图
区分 301/302/307/308 的语义与 Go 的默认行为
Go 的默认重定向逻辑对方法的处理并不完全符合 RFC:它把 301 和 302 都转成 GET(即使原始是 POST),而 307 和 308 才保持原方法。但很多旧服务仍用 302 表示“临时重定向 + 保持方法”,这时默认行为就会出错。
解决办法是捕获响应后手动重发请求:
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 307 || resp.StatusCode == 308 {
// 手动重发原 method + body
newReq, _ := http.NewRequest(req.Method, resp.Header.Get("Location"), req.Body)
newReq.Header = req.Header.Clone()
resp, _ = client.Do(newReq)
}
- 301/302:标准要求重定向后改为 GET,但业务中常被滥用;需跟服务端约定清楚
- 307/308:明确要求保持原 method 和 body,适合 API 场景;Go 默认只对 307/308 保持方法,但不会自动重发 body(
req.Body已被读取) - 重发时记得克隆
req.Header,否则 Cookie、Authorization 等可能丢失
如何安全读取重定向链中的所有 Location 头
如果你需要完整跳转路径(比如做 SEO 检查或链路追踪),不能只靠 resp.Request.URL——它只反映最终 URL。必须在每次重定向响应中提取 Location 头。
推荐方式是在 CheckRedirect 中收集:
var redirects []string
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) > 0 {
redirects = append(redirects, via[len(via)-1].URL.String())
}
// 继续跳转
return nil
},
}
-
via是已执行的请求切片,via[0]是初始请求,via[len(via)-1]是上一次跳转发出的请求,其 URL 即本次跳转的源地址 - 真正目标地址在当前
req的Header.Get("Location")里,但该值尚未被解析为绝对 URL;建议用req.URL.ResolveReference构造 - 超过 10 次跳转会触发
net/http: stopped after 10 redirects错误,需提前在CheckRedirect中拦截
重定向逻辑看似简单,但涉及状态码语义、body 处理、header 传递和循环检测多个层面。最容易忽略的是:307/308 下的 req.Body 在第一次 Do 后就已关闭,手动重发前必须重新构造可读的 io.Reader(比如用 bytes.NewReader 缓存原始 body)。










