最近在调试一个第三方登录功能时,我遇到了一个奇怪的现象:明明看到页面跳转了,用户也成功登录了,但我在 Network 面板里怎么都找不到那个关键的 auth code。习惯性地勾选了 Fetch/XHR 过滤器,翻遍了所有请求,就是没有🤔。
直到我取消过滤,切换到 Doc 类型,才在 URL 参数里看到了 ?code=abc123&state=xyz。
这次经历让我开始思考:Network 面板里的 Doc 到底是什么?为什么有些认证信息会出现在这里而不是 Fetch/XHR 里?这背后有什么机制?
问题的起源
一次真实的调试经历
场景是这样的:我在接入 GitHub OAuth 登录,流程看起来很顺利——用户点击"使用 GitHub 登录",跳转到 GitHub 授权页面,授权后跳回我的应用。但问题来了,我需要在回调中获取 GitHub 返回的 authorization code,然后用这个 code 去换取 access token。
按照惯例,我打开 Chrome DevTools,勾选 Network 面板的 Fetch/XHR 过滤器,准备查看 API 请求。结果——什么都没有。没有看到任何包含 code 参数的请求。
困惑了好一会儿,我才想起取消过滤,查看所有类型的请求。这时我注意到有个 Type 为 document 的请求,点开一看,Request URL 是:
https://myapp.com/callback?code=gho_xxxxxxxxxxxx&state=random_string
原来 code 一直在这里!只是它不是通过 Fetch/XHR 发送的,而是作为页面跳转(重定向)的一部分。
这背后的几个疑问
这次经历让我产生了几个问题:
- Network 面板里的 Doc 是什么?和 Fetch/XHR 有什么区别?
- 为什么 OAuth 的认证信息会出现在 Doc 请求里?
- 什么时候我应该去查看 Doc 类型的请求?
- Network 面板里其他的过滤类型都代表什么?
接下来,让我们一个个搞清楚。
认识 Network 面板中的请求分类
什么是 Doc 请求?
Doc(Document)请求,指的是 HTML 文档请求,也就是浏览器加载的页面本身。
这个定义听起来有点抽象,我们用代码来看什么情况下会产生 Doc 请求:
// 环境: 浏览器
// 场景: 各种触发 Doc 请求的方式
// 1. 直接在地址栏输入 URL
// https://example.com
// → Type: document
// 2. 点击链接跳转
const link = document.createElement('a');
link.href = '/dashboard';
link.click();
// → Type:document
// 3. JavaScript 页面跳转
window.location.href = '/dashboard';
// → Type:document
// 4. 表单提交(非 AJAX)
const form = document.createElement('form');
form.action = '/login';
form.method = 'POST';
form.submit();
// → Type:document
// 5. 服务端 HTTP 重定向
// Server Response:
// HTTP/1.1 302 Found
// Location:/callback?code=xxx
// → 浏览器自动发起新的 document 请求
相对的,下面这些操作不会产生 Doc 请求:
// 环境: 浏览器
// 场景: 这些是 Fetch/XHR 请求,不是 Doc
// 使用 fetch API
fetch('/api/user')
.then(res => res.json())
// → Type: fetch
// 使用 XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.send();
// → Type: xhr
// 使用 axios 等库
axios.get('/api/posts');
// → Type: xhr
Doc 请求的关键特征:
- ✅ 会触发页面导航(浏览器地址栏的 URL 会改变)
- ✅ 浏览器会解析返回的 HTML 并渲染页面
- ✅ 可能伴随着 Cookie 的设置和自动携带
- ✅ 会刷新整个页面(除非是 iframe)
Network 面板的过滤类型全解
为了更清楚地理解 Doc 在整个 Network 面板中的位置,我整理了一个对比表:
| 类型 | 含义 | 何时查看 | 典型例子 |
|---|---|---|---|
| All | 所有请求 | 排查复杂问题,需要看完整链路 | - |
| Doc | HTML 文档 | 页面跳转、重定向、认证回调 | 登录重定向、OAuth 回调 |
| Fetch/XHR | AJAX 请求 | API 调用、异步数据加载 | fetch('/api/user') |
| JS | JavaScript 文件 | 脚本加载问题、404 错误 | <script src="app.js"> |
| CSS | 样式表 | 样式未生效、加载失败 | <link href="style.css"> |
| Img | 图片 | 图片显示异常、加载慢 | <img src="photo.jpg"> |
| Media | 音视频 | 媒体播放问题 | <video src="movie.mp4"> |
| Font | 字体文件 | 字体显示异常、图标不显示 | @font-face 引用的字体 |
| WS | WebSocket | 实时通信连接问题 | new WebSocket(url) |
一个简单的记忆方法:
- Doc = 页面本身(会导致页面刷新或跳转)
- Fetch/XHR = 页面背后的数据请求(页面不刷新)
- 其他类型 = 页面加载需要的各种资源
什么时候需要查看 Doc?
根据我的经验,以下场景必须查看 Doc 类型的请求:
1. 调试页面跳转流程
用户登录 → 重定向到首页 → 重定向到之前访问的页面
每一次重定向都是一个 Doc 请求,需要追踪完整链路。
2. 排查认证问题
- OAuth/SAML 等第三方登录的回调
- Cookie 是否正确设置(查看 Response Headers 的 Set-Cookie)
- URL 参数中的临时 token 或 code
3. 追踪 HTTP 重定向链路
- 服务端返回 301/302/303/307/308 状态码
- 需要查看 Location 头部,了解重定向目标
- 排查重定向循环问题
4. 分析页面加载性能
- 首次加载时间(TTFB、DOM 解析时间)
- 服务端渲染(SSR)的响应时间
调试技巧:
在 Chrome DevTools 的 Network 面板中:
-
✅ 勾选 "Preserve log"(保留日志)
- 页面跳转后不会清空请求记录
- 可以看到完整的重定向链路
-
✅ 取消过滤或选择 "All"
- 看到所有类型的请求
- 不会漏掉关键信息
-
✅ 关注 Status 列的 3xx 状态码
- 302 Found, 301 Moved Permanently 等
- 这些都是重定向请求
为什么需要 Doc 分类?
浏览器的两种数据获取方式
理解 Doc 和 Fetch/XHR 的区别,关键在于理解浏览器获取数据的两种不同方式。
方式 1:页面导航(Doc 请求)
这是浏览器的原生机制,从 Web 诞生之初就存在:
<!-- 环境: 浏览器 -->
<!-- 场景: 传统的页面导航 -->
<!-- 点击链接 -->
<a href="/products">查看产品</a>
<!-- 表单提交 -->
<form action="/login" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">登录</button>
</form>
特点:
- ✅ 浏览器自动处理 Cookie、缓存、重定向
- ✅ 不需要写 JavaScript 代码
- ✅ 天然支持跨域(不受 CORS 限制)
- ❌ 会刷新整个页面,用户体验较差
方式 2:异步请求(Fetch/XHR 请求)
这是 AJAX 时代引入的技术,由 JavaScript 控制:
// 环境: 浏览器
// 场景: 现代单页应用的数据获取
// 使用 fetch API
async function login(username, password) {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
return data.token;
}
// 后续请求手动携带 token
async function getUserInfo(token) {
const response = await fetch('/api/user', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.json();
}
特点:
- ✅ 无需刷新页面,用户体验好
- ✅ 完全由 JavaScript 控制,灵活性高
- ❌ 需要手动处理认证信息(token)
- ❌ 受 CORS 限制,跨域需要服务端配合
为什么要区分这两种方式?
因为它们的调试方法、安全模型和适用场景都不同:
| 特性 | Doc(页面导航) | Fetch/XHR(异步请求) |
|---|---|---|
| 触发方式 | 链接点击、表单提交、重定向 | JavaScript 代码 |
| 页面刷新 | 是 | 否 |
| Cookie 携带 | 自动 | 需配置 credentials |
| 跨域限制 | 无(但有其他安全机制) | 严格的 CORS 检查 |
| 调试位置 | Network → Doc | Network → Fetch/XHR |
| 适用场景 | 传统 Web、SSO、OAuth | 单页应用、REST API |
真实案例:OAuth 认证为什么用 Doc?
现在让我们回到文章开头的问题:为什么 OAuth 的 auth code 会出现在 Doc 请求里?
让我用一个完整的 OAuth 流程来说明:
sequenceDiagram
participant User as 用户浏览器
participant App as 你的应用
participant GitHub as GitHub
User->>App: 1. 点击"Login with GitHub"
Note over User,App: Fetch/XHR: GET /api/auth/github
App->>User: 2. 返回授权 URL
User->>GitHub: 3. 重定向到 GitHub
Note over User,GitHub: Doc 请求: 页面跳转
GitHub->>User: 4. 返回登录页面 HTML
User->>GitHub: 5. 用户输入账号密码
GitHub->>User: 6. 302 重定向回你的应用
Note over User,App: Doc 请求: HTTP 重定向
User->>App: 7. GET /callback?code=xxx&state=yyy
Note over User,App: ⭐ code 在这个 Doc 请求的 URL 里
App->>GitHub: 8. 用 code 换 token(服务端)
Note over App,GitHub: 这个请求不在浏览器里
GitHub->>App: 9. 返回 access_token
关键时刻分解:
步骤 3:跳转到 GitHub(Doc 请求)
// 环境: 浏览器
// 场景: 用户点击登录按钮后
// 前端构造 GitHub 授权 URL
const authUrl = 'https://github.com/login/oauth/authorize?' +
'client_id=your_client_id&' +
'redirect_uri=https://yourapp.com/callback&' +
'state=random_string';
// 页面跳转(产生 Doc 请求)
window.location.href = authUrl;
// Network 面板会看到:
// Type: document
// URL: https://github.com/login/oauth/authorize?...
// Status: 200
步骤 6-7:GitHub 重定向回你的应用(Doc 请求)
# GitHub 服务器的响应
HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code=gho_xxxx&state=random_string
# 浏览器自动发起新的请求
GET /callback?code=gho_xxxx&state=random_string HTTP/1.1
Host: yourapp.com
# Network 面板会看到:
# Type: document
# URL: https://yourapp.com/callback?code=gho_xxxx&state=random_string
# Status: 200
这就是你在 Doc 里找到 auth code 的原因!
为什么 OAuth 必须用重定向(Doc)?
可能你会想:为什么不用 Fetch/XHR 来实现 OAuth?这样就不用刷新页面了。
让我解释一下为什么这样做不通:
原因 1:安全性——用户密码不能经过第三方应用
问题情境: 用户(你)想让你的应用(比如 Notion)访问你的 Google Drive
❌ 错误做法:
Notion 让你在 Notion 页面输入 Google 密码
→ Notion 拿到了你的 Google 密码
→ 风险:Notion 可以随意访问你的 Google 账户
✅ 正确做法(OAuth):
Notion 把你重定向到 Google 登录页面
→ 你直接在 Google 页面输入密码
→ Notion 永远拿不到你的密码
→ Google 只给 Notion 一个有限权限的 token
重定向保证了用户直接在资源提供方(Google)的页面输入密码,密码不会经过第三方应用。
原因 2:跨域限制——CORS 会阻止 AJAX 请求
// 环境: 浏览器
// 场景: 如果尝试用 AJAX 请求 GitHub 授权页面
// ❌ 这样做不行
fetch('https://github.com/login/oauth/authorize?...')
.then(res => res.text())
.then(html => {
// 想法:拿到 GitHub 登录页面的 HTML,显示在我的页面里
});
// 会遇到的问题:
// 1. CORS 错误:GitHub 不允许你的域名跨域请求
// 2. 即使能拿到 HTML,用户在你的页面输入密码也不安全
// 3. 无法获取 GitHub 的 Cookie,登录状态无法维护
原因 3:浏览器的自动行为——重定向无需编写代码
# HTTP 重定向是浏览器的标准功能
# 服务器只需要返回一个响应头:
HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code=xxx
# 浏览器会自动:
# 1. 提取 Location 头的 URL
# 2. 发起新的请求(Doc 请求)
# 3. 更新地址栏 URL
# 4. 渲染新页面
# 不需要写任何 JavaScript!
Cookie 认证为什么也在 Doc 里?
除了 OAuth,传统的 Cookie 认证也主要依赖 Doc 请求。让我们看一个例子:
// 环境: 浏览器
// 场景: 传统的表单登录
// HTML 表单
/*
<form action="/login" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">登录</button>
</form>
*/
// 用户提交表单时发生的事情:
// 1. 浏览器发送 Doc 请求
// POST /login HTTP/1.1
// Content-Type: application/x-www-form-urlencoded
//
// username=alice&password=secret123
// 2. 服务器验证成功,返回重定向 + Set-Cookie
// HTTP/1.1 302 Found
// Set-Cookie: session_id=abc123xyz; HttpOnly; Secure; SameSite=Strict
// Location: /dashboard
// 3. 浏览器自动:
// - 保存 Cookie
// - 发起新的 Doc 请求到 /dashboard
// - 自动携带 Cookie
// GET /dashboard HTTP/1.1
// Cookie: session_id=abc123xyz
为什么是 Doc 请求?
- Set-Cookie 在响应头中 → 只有 Doc 请求会自动处理 Set-Cookie
- Cookie 自动携带 → Doc 请求会自动带上同域的 Cookie
- 表单提交 → 传统 HTML form 产生的就是 Doc 请求
对比现代方式(Token 认证) :
// 环境: 浏览器
// 场景: 现代单页应用的 Token 认证
// 1. 用户登录(Fetch/XHR 请求)
async function login(username, password) {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
// { access_token: "eyJhbG...", refresh_token: "..." }
// 手动存储 token
localStorage.setItem('access_token', data.access_token);
}
// 2. 后续请求手动携带 token(Fetch/XHR 请求)
async function getUserInfo() {
const token = localStorage.getItem('access_token');
const response = await fetch('/api/user', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.json();
}
两种方式对比:
| 特性 | Cookie + Doc | Token + Fetch/XHR |
|---|---|---|
| 请求类型 | Doc(页面跳转) | Fetch/XHR(异步) |
| 认证信息位置 | Cookie(Response Headers) | JSON 响应体 |
| 携带方式 | 浏览器自动 | 手动添加到 Headers |
| 用户体验 | 页面刷新 | 无刷新,流畅 |
| 调试位置 | Doc 请求 | Fetch/XHR 请求 |
| 典型应用 | 传统 Web 应用、企业内部系统 | 单页应用、移动 App API |
实战调试技巧
案例:找不到 OAuth 的 auth code
让我用实际的调试步骤演示一遍:
问题场景:
- 接入 GitHub OAuth 登录
- 用户点击登录,跳转到 GitHub,授权后跳回应用
- 需要获取 URL 中的
code参数
错误的调试方法:
- 打开 DevTools → Network 面板
- 勾选 Fetch/XHR 过滤器
- 刷新页面,点击登录
- 找不到包含 code 参数的请求 ❌
正确的调试步骤:
✅ Step 1: 取消所有过滤,选择 "All" → 看到所有类型的请求
✅ Step 2: 勾选 "Preserve log"(保留日志) → 防止页面跳转后记录被清空
✅ Step 3: 点击登录,完成授权流程
✅ Step 4: 在 Network 面板中,找 Type 为 "document" 的请求 → 按时间顺序,找最后几个 Doc 请求
✅ Step 5: 点击 Doc 请求,查看详情 → Request URL: yourapp.com/callback?co… → code 就在这里!
✅ Step 6: 查看 Headers 标签
→ Request Headers 可以看到 Cookie、Referer
→ Response Headers 可以看到 Set-Cookie
在 Console 中提取 code:
// 环境: 浏览器 Console
// 场景: 在回调页面提取 URL 参数
// 方法 1: 使用 URLSearchParams
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
console.log('Auth code:', code);
console.log('State:', state);
// 方法 2: 手动解析(不推荐,但可以理解原理)
const queryString = window.location.search; // "?code=xxx&state=yyy"
const pairs = queryString.substring(1).split('&');
const result = {};
pairs.forEach(pair => {
const [key, value] = pair.split('=');
result[key] = decodeURIComponent(value);
});
console.log(result);
// { code: "gho_xxxx", state: "yyyy" }
调试清单
当你需要排查认证或重定向问题时,参考这个清单:
查看 Doc 请求时,重点关注:
| 位置 | 关键信息 | 用途 |
|---|---|---|
| Request URL | URL 参数 ?code=xxx&state=yyy | OAuth code、查询参数 |
| Status | 3xx 状态码(301/302/303) | 识别重定向链路 |
| Request Headers | Cookie 字段 | 检查认证信息是否携带 |
| Response Headers | Set-Cookie 字段 | 检查 Cookie 是否正确设置 |
| Response Headers | Location 字段 | 查看重定向目标地址 |
常见问题排查:
-
❓ 为什么看不到某些请求?
- → 检查是否勾选了 "Preserve log"
- → 取消类型过滤,选择 "All"
-
❓ 为什么 Cookie 没有携带?
- → 检查 Cookie 的 Domain 是否匹配
- → 检查 Cookie 的 Path 是否正确
- → 检查 SameSite 属性(Strict/Lax/None)
-
❓ 为什么一直重定向循环?
- → 启用 "Preserve log",查看完整的重定向链
- → 找出循环的起点和终点
- → 检查服务端的重定向逻辑
-
❓ 为什么 OAuth code 参数没有?
- → 检查 state 参数是否匹配(CSRF 防护)
- → 查看 GitHub 的错误提示(可能在 URL 参数里)
- → 确认 redirect_uri 配置是否正确
Chrome DevTools 实用技巧
1. 复制请求为 cURL
# 在 Network 面板中:
# 1. 右键请求
# 2. Copy → Copy as cURL
# 得到类似这样的命令:
curl 'https://yourapp.com/callback?code=gho_xxxx&state=yyyy' \
-H 'Accept: text/html' \
-H 'Cookie: session_id=abc123' \
-H 'Referer: https://github.com/login'
# 可以在终端中重放这个请求
2. 保存网络日志(HAR 文件)
右键 Network 面板 → Save all as HAR with content
用途:
- 保存完整的请求/响应记录
- 可以导入到其他工具分析
- 分享给同事协助调试
⚠️ 注意:HAR 文件包含敏感信息(Cookie、Token),不要随意分享
3. 过滤和搜索
在 Network 面板的 Filter 输入框中:
# 按域名过滤
domain:github.com
# 按状态码过滤
status-code:302
# 按请求方法过滤
method:POST
# 按资源大小过滤
larger-than:1M
# 组合使用
domain:api.example.com status-code:200
小结
通过这次对 Network 面板 Doc 类型的探索,我理清了几个关键点:
核心要点
1. Doc 是什么?
- HTML 文档请求,即浏览器加载的页面本身
- 所有触发页面导航的操作(链接点击、表单提交、重定向)都会产生 Doc 请求
- 会刷新页面,会自动处理 Cookie 和重定向
2. 什么时候需要看 Doc?
- 调试页面跳转和重定向流程
- 排查认证问题(OAuth 回调、Cookie 设置)
- 追踪 URL 参数中的临时信息(code、state、token)
- 分析完整的请求链路(配合 Preserve log)
3. 为什么需要 Doc 分类?
- 浏览器有两种数据获取方式:页面导航(Doc)和异步请求(Fetch/XHR)
- OAuth/SSO 等认证流程必须用重定向(安全性 + 跨域限制)
- Cookie 认证依赖浏览器的自动行为(自动携带、自动处理 Set-Cookie)
- 不同的机制需要不同的调试方法
4. Network 分类速查
- Doc → 页面跳转、重定向、认证回调
- Fetch/XHR → API 调用、异步数据加载
- JS/CSS/Img → 页面资源加载
- WS → WebSocket 实时通信
一个类比帮助记忆
把 Network 面板想象成一个物流追踪系统:
- Doc = 你本人去取件(需要走到快递点,拿到包裹后回家)
- Fetch/XHR = 快递送上门(你在家里,东西直接送到)
- JS/CSS/Img = 包裹里的物品(笔记本、衣服、配件等)
当你需要亲自去某个地方完成某件事(比如银行签字、OAuth 授权),就是 Doc 请求。当你只需要获取数据(API 调用),就是 Fetch/XHR 请求。
一个调试口诀
看不到数据?先别慌
取消过滤查 All 类型
重定向认证看 Doc
API 数据看 XHR
保留日志 Preserve log
完整链路不会漏
参考资料
- MDN - HTTP redirections - HTTP 重定向机制详解
- Chrome DevTools Network Reference - Chrome 官方文档
- OAuth 2.0 Simplified - OAuth 授权码模式
- HTTP cookies - Cookie 工作原理
- Same-origin policy - 同源策略与 CORS