Go http.Client CheckRedirect 处理跨域跳转的配置方法
来源:17golang原创
时间:2026-11-13 09:30:41 445浏览 收藏
Go 的 http.Client 默认会处理常见的 3xx 重定向,但遇到登录跳转、短链或跨主机地址时,直接放行并不总是合适。这里的“跨域”特指重定向目标的主机或 scheme 发生变化,不是浏览器里的 CORS。实用做法是给 Client 配置 CheckRedirect:从 req.URL 判断当前目标,利用 via 了解跳转历史,再决定继续、保留这次 3xx 响应,还是返回策略错误。
官方地址:https://pkg.go.dev/net/http#Client.CheckRedirect
- 只想阻止自动跟随并读取 Location 时,返回
http.ErrUseLastResponse,得到最后一次响应且不把它当成普通错误。 - 不可信目标应在
CheckRedirect中比较原始 host、目标 host 和白名单;拒绝策略可返回自定义错误。 - 普通错误会被
http.Client.Do包装成*url.Error,策略拒绝和网络失败要在调用方分开记录。
先区分三种重定向结果
CheckRedirect 的回调参数是即将发出的 req 和已经走过的请求切片 via。返回 nil 表示允许 Client 继续;返回普通错误表示停止并把错误交给调用方;返回特殊值 http.ErrUseLastResponse 时,Client 返回最近一次响应,响应体保持打开,适合拿到原始 3xx 状态和 Location。

| 回调返回值 | Client.Do 的结果 | 适合场景 |
|---|---|---|
nil | 继续跟随重定向 | 目标在可信范围内 |
http.ErrUseLastResponse | 返回最近一次 3xx 响应,Body 不自动关闭 | 读取 Location 或保留原始状态 |
| 其他 error | 停止并返回 *url.Error | 拒绝不可信主机或超过业务限制 |
用目标 URL 判断是否允许跨主机跳转
不要只判断“发生了重定向”就一律拒绝。更稳妥的边界是记录初始主机,只允许同主机,或允许一个明确的目标白名单。req.URL 是即将访问的目标,via 的第一个元素通常能帮助你回看原始请求。下面的示例只展示策略判断;生产代码还应根据业务需要处理端口、scheme 和代理场景。

package main
import (
"fmt"
"net/http"
"net/url"
)
func newRedirectClient() *http.Client {
// 白名单只放业务确实需要跳转到的主机,避免把任意 Location 当成可信目标。
allowedHosts := map[string]bool{
"login.example.com": true,
"api.example.com": true,
}
return &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) == 0 {
return nil // 没有历史请求时,无法比较原始目标,先交给默认策略。
}
originalHost := via[0].URL.Hostname()
targetHost := req.URL.Hostname()
if targetHost == originalHost || allowedHosts[targetHost] {
return nil // 同主机或白名单目标可以继续跟随。
}
return fmt.Errorf("拒绝跨主机重定向: %s -> %s", originalHost, targetHost)
},
}
}
func doRequest(client *http.Client, rawURL string) error {
resp, err := client.Get(rawURL)
if err != nil {
// Client 会把 CheckRedirect 的普通错误包装成 *url.Error,保留原因链便于区分策略拒绝。
var urlErr *url.Error
if ok := errorAs(err, &urlErr); ok {
return fmt.Errorf("请求被重定向策略终止: %w", urlErr)
}
return err
}
defer resp.Body.Close() // 成功拿到最终响应后,调用方负责关闭响应体。
return nil
}
// errorAs 只为让示例保持短小;真实代码可直接使用 errors.As。
func errorAs(err error, target **url.Error) bool {
for err != nil {
if value, ok := err.(*url.Error); ok {
*target = value
return true
}
type unwrapper interface{ Unwrap() error }
next, ok := err.(unwrapper)
if !ok {
return false
}
err = next.Unwrap()
}
return false
}
示例里的 errorAs 是为了展示错误链判断,真实项目建议使用标准库 errors.As。另外,Go 客户端对不可信目标的敏感请求头有自己的转发规则;这和是否允许业务跳转是两层策略,不应只靠“跨域”这个词做判断。
需要读取 3xx 时保留最后一次响应
如果需求是查看跳转地址,而不是把拒绝当异常,可以返回 http.ErrUseLastResponse。官方语义是把最近一次响应交给调用方,同时不替你关闭它的 Body,因此读取或丢弃完内容后必须关闭。
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 返回特殊错误会保留最近一次 3xx 响应,而不是生成普通 url.Error。
if len(via) > 0 && req.URL.Hostname() != via[0].URL.Hostname() {
return http.ErrUseLastResponse
}
return nil // 同主机跳转继续交给 Client 处理。
},
}
resp, err := client.Get("https://example.com/start")
if err != nil {
return err // 网络错误或其他重定向策略错误仍从这里返回。
}
defer resp.Body.Close() // ErrUseLastResponse 下也必须负责关闭 Body。
fmt.Println(resp.StatusCode, resp.Header.Get("Location")) // 读取原始 3xx 和目标地址。
这个分支适合做重定向分析、记录 Location 或让上层决定下一步。若返回自定义错误,调用方通常拿不到一个可继续读取的最近响应,记录日志时应保留 url.Error 的 Err 链。
按任务选择配置边界
实际排查时可以先按下面的清单选策略,再写回调,避免把所有 3xx 都当成同一种情况:
| 任务 | 判断重点 | 回调策略 |
|---|---|---|
| 正常登录跳转 | 目标 host 在业务白名单 | 返回 nil |
| 记录短链最终去向 | 需要保留当前 Location | 返回 ErrUseLastResponse |
| 禁止跳到外部站点 | 目标 host 不在允许集合 | 返回带原因的普通 error |
| 限制循环跳转 | 检查 len(via) 和业务上限 | 超限返回普通 error |
常见问题
Go 客户端的跨域跳转等同于浏览器 CORS 吗?
不是。这里讨论的是 HTTP 客户端是否跟随新的 host 或 scheme;CORS 是浏览器对脚本发起跨源请求的访问控制模型。
为什么返回 ErrUseLastResponse 后没有 error?
这是标准库专门保留最后一次响应的分支,调用方应检查响应状态和 Location,并在读取后关闭 Body。
via 为空时能不能直接取 via[0]?
不能。回调可能在没有历史请求的情况下被调用,访问前先判断长度,否则会触发切片越界。
-
Golang · Go问答 | 59分钟前 | net/http · Go问答 · 接口排查 · cookie Go Cookiejar http.Client Set-Cookie AddCookie116 收藏
-
282 收藏
-
199 收藏
-
451 收藏
-
Golang · Go问答 | 7小时前 | 性能排查 · HTTP客户端 · Go问答 · Go net/http 连接复用 Transport keep-alive http.Client Response.Body289 收藏
-
441 收藏
-
410 收藏
-
375 收藏
-
101 收藏
-
282 收藏
-
255 收藏
-
138 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习