Go的http.Client默认自动跟随301/302跳转(最多10次),通过CheckRedirect字段控制逻辑;禁用需返回http.ErrUseLastResponse,自定义须校验Host等并避免return nil。

Go 的 http.Client 默认会自动跟随 301/302 跳转
除非你显式禁用,http.DefaultClient 和任何新构造的 http.Client{} 都会自动处理重定向(最多 10 次),并返回最终响应,而不是跳转响应本身。这在多数场景下是便利的,但也会掩盖真实跳转链、干扰调试,或导致意外访问第三方地址。
关键控制点在 Client.CheckRedirect 字段——它是个函数,决定“遇到跳转时该不该继续”。默认值是 http.DefaultCheckRedirect,内部做了次数限制和简单校验。
- 想完全禁用跳转?把
CheckRedirect设为func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } - 想自定义跳转逻辑?比如只允许同域名跳转,就在这里做
req.URL.Host校验 - 注意:返回
http.ErrUseLastResponse会让 client 直接返回跳转响应(即 status 302 的 resp),而不是继续请求 Location
为什么 CheckRedirect 返回 error 就算“终止”
Go 的 HTTP client 把重定向当作一个「可能失败的流程」,不是分支逻辑。只要 CheckRedirect 返回非 nil error,client 就认为“用户拒绝继续”,立刻停止跳转,并把当前跳转响应(如 302)作为最终 *http.Response 返回。
常见错误现象:CheckRedirect 函数里忘了 return,或者写了 return nil —— 这会导致 panic:panic: net/http: CheckRedirect function returned nil。Go 强制要求必须明确返回 error 或 http.ErrUseLastResponse。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 正确写法只有两种:返回
http.ErrUseLastResponse(停在当前跳转),或返回其他 error(如errors.New("redirect blocked")) - 别用
return nil,也别漏写 return - 如果只是想记录跳转过程,仍需返回
http.ErrUseLastResponse或原样调用http.DefaultCheckRedirect
手动实现跳转跟踪时要注意 req.Header 和 req.Body
当禁用自动跳转后,你得自己解析 resp.Header.Get("Location") 并发起下一次请求。这时容易忽略原始请求的上下文是否被保留:
-
req.Header不会自动继承到新请求——比如带Authorization或User-Agent的请求,跳转后要手动复制 -
req.Body默认是一次性的:如果原请求有 body(如 POST),且你打算在跳转后重发 body,得确保 body 可重读(例如用bytes.NewReader包装) - 307/308 跳转要求方法和 body 不变;301/302 则规范要求降级为 GET 且丢弃 body —— 但 Go 默认不区分,全按 301/302 处理,除非你手动判断状态码再决策
实际调试中,http.Transport 的 Proxy 和跳转可能互相干扰
如果你设置了 http.Transport.Proxy = http.ProxyFromEnvironment(默认行为),而跳转目标域名被代理规则匹配,client 会尝试走代理访问跳转后的地址。这可能导致:本地能直连的地址,跳转后却因走代理失败;或代理本身返回 302 导致嵌套跳转失控。
排查这类问题时,别只盯着 CheckRedirect,先确认跳转 URL 是否真的被代理了:
- 临时设
Transport.Proxy = http.ProxyURL(nil)排除代理干扰 - 用
curl -v对比原始请求和跳转 URL 的行为,看是否一致 - 注意:某些企业网络对
Location中的相对路径(如/new-path)解析不一致,Go 会自动补全为绝对 URL,但中间代理可能不这么干
跳转逻辑看着简单,但一牵扯到认证头、body 重放、代理、相对路径补全,就很容易在第二三次跳转时突然失败——这时候往往不是代码写错了,而是没意识到 client 已经帮你做了太多事,而你又手动绕过了其中一部分。

















