This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hzh/DEV/跨域问题调试/跨域 HTTP 请求示例.md
T

8.5 KiB
Raw Blame History

tags, create time
tags create time
CORS
跨域
HTTP
网络安全
前端调试
2026-04-29 10:30

跨域 HTTP 请求示例 — 网易邮箱 Countly 分析请求

概述

本文档记录一次真实的浏览器 CORS(Cross-Origin Resource Sharing) 请求样本:来自 mail.163.com 向同域名下的 countly.mail.163.com 发起的 POST 事件追踪请求。通过分析完整的 HTTP 请求头和响应头,理解浏览器的 CORS 机制、服务端如何正确配置跨域策略,以及安全相关的首部字段含义。

[!question] 思考 countly.mail.163.com 和 mail.163.com 算"跨域"吗?严格来说它们属于不同子域,浏览器会将其视为不同 origin,因此仍然触发 CORS 流程。但如果服务端未配置 CORS 头,请求会被浏览器拦截——尽管这次请求实际成功返回了 200 OK。

正文

请求概览

这是一个典型的前端埋点上报请求,使用 Countly 分析 SDK 在用户发送邮件等行为时收集事件数据。

项目 值
URL https://countly.mail.163.com/stats/i
方法 POST
状态码 200 OK
Content-Type application/x-www-form-urlencoded
响应内容长度 20 bytes(通常为一个透明像素 GIF)

关键请求头解析

Origin & Referer — 跨域判定依据

[!info] 核心概念 浏览器判断是否跨域的依据是 origin = scheme + host + port。即使同源(如都是 .163.com),只要 subdomain 不同就会触发预检或直接带 Origin 的请求。

origin: https://mail.163.com
referer: https://mail.163.com/
  • origin:由浏览器自动附加,不可被 JS 手动设置。服务端通过此字段决定要不要允许跨域。
  • referer:告诉服务端页面来源,常用于统计和鉴权。

Sec-Fetch-* 系列 — 请求意图标识

[!tip] W3C Fetch Metadata Chrome 82+ 默认发送 Sec-Fetch-* 首部,帮助服务器理解请求性质,可以用来防御 CSRF 等攻击。

sec-fetch-mode: cors        ← 表明这是一个跨域请求
sec-fetch-site: same-site   ← 同站(同一一级域名 .163.com)
sec-fetch-dest: empty       ← 无特定资源类型(非图片/脚本等)

Sec-Fetch-Site 取值速查:

值 含义
same-origin 同源请求
same-site 同站(共享 eTLD+1)
cross-site 真正的跨站
none 无 referer 的特殊情况

[!info] none 的常见触发场景 sec-fetch-site: none 等价于浏览器无法判断请求来源站点,通常出现在以下情况:

  • <a target="_blank"> 打开的新标签页中发起的请求
  • 书签页面(bookmarks:)作为来源时
  • 页面设置了 Referrer-Policy: no-referrer 且请求跨域
  • Service Worker / Background Sync 等后台任务发起的请求
  • HTTP 页面跳转到 HTTPS 资源(部分浏览器出于安全考虑丢弃 referer)

这些头部的派生链路:Sec-Fetch-* ← document.referrer ← 用户导航行为。referer 缺失则整组头部失效。

安全相关头部

[!warning] 引用站点策略 Referrer-Policy: strict-origin-when-cross-origin 意味着:

  • 同源请求:发送完整 referer
  • 跨域请求:只发送 origin(不包含路径和参数)
  • HTTPS → HTTP:不发送 referer(防止泄露)

CORS 响应头解析

服务端的响应中包含了关键的 CORS 控制头:

access-control-allow-credentials: true
access-control-allow-methods: GET,POST
access-control-allow-origin: https://mail.163.com
响应头 作用
Access-Control-Allow-Origin 指定允许访问的源,此处精确匹配 https://mail.163.com,而非通配符 *
Access-Control-Allow-Methods 允许的 HTTP 方法
Access-Control-Allow-Credentials 允许携带 Cookie/认证凭据

[!important] Credentials + 通配符陷阱 当 Access-Control-Allow-Credentials: true 时,不能将 Access-Control-Allow-Origin 设置为 *。这是 CORS 规范中的硬性约束,违反会导致浏览器直接报错。此处的写法是正确的:精确指定允许的 origin。

CORS 请求全流程图解

场景一:简单请求(无需预检)— 当前记录的情况

Content-Type 为 application/x-www-form-urlencoded、multipart/form-data、text/plain 之一时,只发 1 次 POST:

sequenceDiagram
    participant B as 浏览器
    participant S as countly.mail.163.com
    participant M as mail.163.com(当前页面)

    Note over B,M: JS 发起事件追踪
    B->>S: POST /stats/i<br/>Origin: https://mail.163.com<br/>Content-Type: application/x-www-form-urlencoded
    S-->>B: 200 OK<br/>Access-Control-Allow-Origin: https://mail.163.com<br/>Access-Control-Allow-Credentials: true
    Note over B: ✅ 校验通过,响应交由 JS 处理

场景二:需要预检的请求 — 常见情况

当 Content-Type 为 application/json、application/octet-stream 等非简单类型时,浏览器先发 OPTIONS 预检,通过后才发实际请求:共 2 次。

sequenceDiagram
    participant B as 浏览器
    participant S as countly.mail.163.com
    participant M as mail.163.com(当前页面)

    Note over B,M: JS 发起带自定义 header 的跨域 POST
    B->>S: OPTIONS /stats/i<br/>Origin: https://mail.163.com<br/>Access-Control-Request-Method: POST<br/>Access-Control-Request-Headers: content-type
    activate S
    S-->>B: 204 No Content<br/>Access-Control-Allow-Methods: POST, OPTIONS<br/>Access-Control-Allow-Headers: content-type<br/>Access-Control-Max-Age: 86400
    deactivate S
    Note over B: ⏱ 预检通过,按 Max-Age 缓存此结果
    B->>S: POST /stats/i<br/>Origin: https://mail.163.com<br/>Content-Type: application/octet-stream
    activate S
    S-->>B: 200 OK<br/>Access-Control-Allow-Origin: https://mail.163.com<br/>Access-Control-Allow-Credentials: true
    deactivate S
    Note over B: ✅ 校验通过,响应交由 JS 处理

[!warning] 预检失败的处理 如果 OPTIONS 返回非 2xx 或响应中缺少必需的 CORS 头(如 Allow-Methods、Allow-Headers),浏览器会直接放弃发送实际请求并抛出 CORS 错误。这意味着客户端甚至不会看到服务端的原始响应。

代码示例

以下是用原生 fetch API 复现此类埋点请求的代码:

// 发送跨域埋点事件(与 Countly SDK 行为类似)
async function trackEvent(payload: Record<string, string>): Promise<void> {
  // 构造表单数据(服务端期望 application/x-www-form-urlencoded)
  const body = new URLSearchParams(payload).toString();

  await fetch('https://countly.mail.163.com/stats/i', {
    method: 'POST',
    credentials: 'include',  // 携带 Cookie(对应 Allow-Credentials: true)
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body,
    // keepalive: true 用于 pagehide 时保证请求发出(可选优化)
  });
}

// 调用示例
trackEvent({
  event_key: 'email_sent',
  count: '1',
  duration: '120',
});

[!note] 解释 上述代码中 credentials: 'include' 是关键:它让浏览器在跨域请求中携带 Cookie,这要求服务端必须明确返回 Access-Control-Allow-Credentials: true 且 Access-Control-Allow-Origin 必须是具体域名(不能是 *)。

其他请求头一览

其余头部为常规 HTTP 标准字段,供参考:

:authority          countly.mail.163.com     # H2 pseudo-header,目标主机
:method             POST                     # HTTP/2
:path               /stats/i                 # 请求路径
:scheme             https                    # 协议

accept              */*                      # 接受任意内容类型
accept-encoding     gzip, deflate, br, zstd  # 支持的压缩算法
accept-language     zh-CN,zh;q=0.9,...       # 语言偏好
content-length      2184                     # 请求体大小
priority            u=1, i                   # HTTP/3 优先级
user-agent          Mozilla/5.0 ... Edg/147  # 浏览器标识

sec-ch-ua           "Microsoft Edge";v="147" # UA 品牌信息
sec-ch-ua-mobile    ?0                       # 非移动端
sec-ch-ua-platform  "Windows"                # 操作系统

[!info] 关于 HTTP/2 此请求使用了 HTTP/2(存在 :authority、:method 等伪头),HTTP/2 通过多路复用避免了传统 HTTP/1.1 的队头阻塞问题,对同时加载多个资源的现代 Web 应用性能提升明显。

关联笔记