当前位置:首页 > 文章列表 > Golang > Go问答 > Go http.Client CheckRedirect 处理跨域跳转的配置方法

Go http.Client CheckRedirect 处理跨域跳转的配置方法

来源:17golang原创 2026-11-13 09:30:41 0浏览 收藏

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。

Go http.Client CheckRedirect、req.URL、via 与自动跟随、ErrUseLastResponse、url.Error 三种结果的静态关系说明图
图1:CheckRedirect 返回策略说明图,展示请求策略与结果对象的关系,不是运行截图。
回调返回值Client.Do 的结果适合场景
nil继续跟随重定向目标在可信范围内
http.ErrUseLastResponse返回最近一次 3xx 响应,Body 不自动关闭读取 Location 或保留原始状态
其他 error停止并返回 *url.Error拒绝不可信主机或超过业务限制

用目标 URL 判断是否允许跨主机跳转

不要只判断“发生了重定向”就一律拒绝。更稳妥的边界是记录初始主机,只允许同主机,或允许一个明确的目标白名单。req.URL 是即将访问的目标,via 的第一个元素通常能帮助你回看原始请求。下面的示例只展示策略判断;生产代码还应根据业务需要处理端口、scheme 和代理场景。

Go CheckRedirect 对原始 host、目标 host、Location 和白名单进行同主机与跨主机边界判断的说明图
图2:跨主机重定向边界说明图,展示白名单判断与拒绝分支,不是运行证据。
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]?

不能。回调可能在没有历史请求的情况下被调用,访问前先判断长度,否则会触发切片越界。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Cookie 设置成功但请求没有携带的原因Cookie 设置成功但请求没有携带的原因
上一篇
Cookie 设置成功但请求没有携带的原因
http.TimeoutHandler 超时后为何还会出现多次写入
下一篇
http.TimeoutHandler 超时后为何还会出现多次写入
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    1010次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    1014次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1139次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1108次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    954次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码