第 11 章:网络请求(Networking)
第 11 章:网络请求(Networking)
网络请求的拦截和修改是广告拦截器、隐私保护工具、API 调试器等扩展的核心能力。Manifest V3 用声明式的 declarativeNetRequest 替代了 MV2 的 webRequest,本章将全面讲解两种方式以及相关的网络 API。
11.1 MV3 网络请求模型
webRequest vs declarativeNetRequest
| 特性 | webRequest (MV2) | declarativeNetRequest (MV3) |
|---|
| 工作方式 | 命令式,JS 回调 | 声明式,预定义规则 |
| 能力 | 读取/修改/阻断/重定向 | 阻断/重定向/修改头 |
| 远程代码 | 允许动态规则 | 静态规则 + 动态规则(有限) |
| 性能 | 可能阻塞请求 | 由浏览器原生处理,更高效 |
| 隐私 | 扩展可读取所有请求数据 | 扩展无法读取请求内容 |
| 状态 | 仅 observe 权限仍在 | MV3 推荐方案 |
11.2 declarativeNetRequest
11.2.1 权限声明
{
"permissions": [
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess"
],
"host_permissions": [
"*://*.example.com/*"
]
}
| 权限 | 说明 |
|---|
declarativeNetRequest | 使用规则匹配,但不能指定 host 以外的 URL |
declarativeNetRequestWithHostAccess | 需配合 host_permissions 使用 |
declarativeNetRequestFeedback | 允许监听规则匹配事件(限 Dev Channel) |
11.2.2 静态规则
在 manifest.json 中声明规则文件:
{
"declarative_net_request": {
"rule_resources": [
{
"id": "block_ads",
"enabled": true,
"path": "rules/block-ads.json"
},
{
"id": "redirect_tracker",
"enabled": false,
"path": "rules/redirect-trackers.json"
}
]
}
}
规则文件格式(rules/block-ads.json):
[
{
"id": 1,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "||ads.example.com",
"resourceTypes": ["script", "image", "xmlhttprequest", "sub_frame"]
}
},
{
"id": 2,
"priority": 1,
"action": {
"type": "redirect",
"redirect": {
"url": "https://example.com/tracker-pixel.gif"
}
},
"condition": {
"urlFilter": "||tracker.example.com/pixel",
"resourceTypes": ["image"]
}
},
{
"id": 3,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Referer",
"operation": "remove"
}
]
},
"condition": {
"urlFilter": "||api.example.com",
"resourceTypes": ["xmlhttprequest"]
}
},
{
"id": 4,
"priority": 2,
"action": {
"type": "allow"
},
"condition": {
"urlFilter": "||safe-ads.example.com",
"resourceTypes": ["script"]
}
}
]
11.2.3 规则动作类型
| 动作类型 | 说明 | 用途 |
|---|
block | 阻断请求 | 广告拦截 |
redirect | 重定向请求 | 替换资源、去跟踪参数 |
allow | 允许请求(覆盖更高优先级的阻断) | 白名单 |
allowAllRequests | 允许所有子请求 | 信任特定页面 |
upgradeScheme | 升级 HTTP 到 HTTPS | 安全升级 |
modifyHeaders | 修改请求/响应头 | 移除跟踪头 |
11.2.4 URL 过滤语法
基本模式: ||example.com/path
| 模式 | 含义 | 匹配 |
|---|
example.com | 包含 | any.example.com/path |
|example.com | 开头匹配 | example.com/path |
example.com| | 结尾匹配 | sub.example.com |
|example.com| | 精确匹配 | 仅 example.com |
| ` | | example.com` |
11.2.5 规则条件字段
{
"condition": {
"urlFilter": "||example.com",
"regexFilter": "https://api\\.example\\.com/v[0-9]+/.*",
"resourceTypes": ["script", "image", "stylesheet"],
"excludedResourceTypes": ["main_frame"],
"domains": ["example.com"],
"excludedDomains": ["safe.example.com"],
"initiatorDomains": ["myapp.com"],
"tabIds": [1, 2, 3],
"excludedTabIds": [4],
"isUrlFilterCaseSensitive": false,
"requestMethods": ["get", "post"],
"requestHeaders": [
{
"header": "Content-Type",
"values": ["application/json"]
}
]
}
}
| 资源类型 | 说明 |
|---|
main_frame | 主页面 |
sub_frame | iframe |
stylesheet | CSS 文件 |
script | JavaScript 文件 |
image | 图片 |
font | 字体 |
xmlhttprequest | XMLHttpRequest / fetch |
websocket | WebSocket 连接 |
media | 音视频资源 |
other | 其他资源 |
11.3 动态规则
// 添加动态规则
async function addBlockingRule() {
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: [
{
id: 1001,
priority: 1,
action: { type: 'block' },
condition: {
urlFilter: '||new-tracker.com',
resourceTypes: ['script', 'xmlhttprequest']
}
}
],
removeRuleIds: [1001] // 先移除同 ID 的旧规则
});
}
// 删除动态规则
async function removeBlockingRule() {
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [1001]
});
}
// 获取当前规则
async function listRules() {
const rules = await chrome.declarativeNetRequest.getDynamicRules();
console.log('动态规则:', rules);
const sessionRules = await chrome.declarativeNetRequest.getSessionRules();
console.log('会话规则:', sessionRules);
}
// 更新静态规则集状态
async function toggleRuleset(rulesetId, enabled) {
const rulesets = await chrome.declarativeNetRequest.getEnabledRulesets();
await chrome.declarativeNetRequest.updateEnabledRulesets({
enableRulesetIds: enabled ? [rulesetId] : [],
disableRulesetIds: enabled ? [] : [rulesetId]
});
}
会话规则
// 会话规则仅在当前浏览器会话中有效,不会持久化
await chrome.declarativeNetRequest.updateSessionRules({
addRules: [{
id: 5001,
priority: 1,
action: { type: 'block' },
condition: {
urlFilter: '||temp-block.com',
resourceTypes: ['script']
}
}],
removeRuleIds: []
});
规则数量限制
| 类型 | 最大数量 | 说明 |
|---|
| 静态规则(每扩展) | 30,000 | 在 manifest 中声明 |
| 动态规则 | 30,000 | 通过 API 添加 |
| 会话规则 | 5,000 | 仅当前会话 |
| 所有扩展静态规则总量 | 330,000 | Chrome 全局限制 |
11.4 修改请求头和响应头
[
{
"id": 100,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{ "header": "X-Custom-Header", "operation": "set", "value": "my-value" },
{ "header": "Referer", "operation": "remove" }
],
"responseHeaders": [
{ "header": "X-Frame-Options", "operation": "remove" },
{ "header": "Access-Control-Allow-Origin", "operation": "set", "value": "*" }
]
},
"condition": {
"urlFilter": "||api.example.com",
"resourceTypes": ["xmlhttprequest"]
}
}
]
| 操作 | 说明 |
|---|
set | 设置或覆盖头的值 |
append | 在现有值后追加 |
remove | 移除该头 |
11.5 webNavigation 与网络事件
// 监听导航事件
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
console.log(`即将导航: ${details.url} (标签页: ${details.tabId})`);
});
chrome.webNavigation.onCompleted.addListener((details) => {
if (details.frameId === 0) { // 主框架
console.log(`页面加载完成: ${details.url}`);
}
});
// 监听子框架加载
chrome.webNavigation.onDOMContentLoaded.addListener((details) => {
if (details.frameId !== 0) {
console.log(`iframe 加载: ${details.url}`);
}
});
// 检测 SPA 导航
chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
console.log(`SPA URL 变化: ${details.url}`);
});
11.6 Fetch API 使用
Service Worker 中使用 fetch API 发起网络请求:
// 基本请求
async function fetchJSON(url, options = {}) {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
// 带重试的请求
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await fetchJSON(url, options);
} catch (error) {
lastError = error;
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
throw lastError;
}
// 并发请求
async function fetchMultiple(urls) {
const results = await Promise.allSettled(
urls.map(url => fetchJSON(url))
);
return results.map((result, i) => ({
url: urls[i],
success: result.status === 'fulfilled',
data: result.status === 'fulfilled' ? result.value : null,
error: result.status === 'rejected' ? result.reason.message : null
}));
}
// 取消请求(AbortController)
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error;
}
}
11.7 业务场景
场景一:API 请求代理
// Service Worker 中代理 API 请求,自动添加认证头
async function authenticatedFetch(url, options = {}) {
const { apiToken } = await chrome.storage.local.get('apiToken');
if (!apiToken) {
throw new Error('未登录');
}
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${apiToken}`,
'X-Extension-Version': chrome.runtime.getManifest().version
}
});
}
// 监听 Content Script 的 API 请求
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'API_REQUEST') {
authenticatedFetch(message.url, message.options)
.then(res => res.json())
.then(data => sendResponse({ success: true, data }))
.catch(err => sendResponse({ success: false, error: err.message }));
return true;
}
});
场景二:自定义广告拦截
// 动态添加拦截规则
async function addCustomBlockRule(domain) {
const existingRules = await chrome.declarativeNetRequest.getDynamicRules();
const maxId = Math.max(0, ...existingRules.map(r => r.id));
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: [{
id: maxId + 1,
priority: 1,
action: { type: 'block' },
condition: {
urlFilter: `||${domain}`,
resourceTypes: [
'script', 'image', 'sub_frame',
'xmlhttprequest', 'media'
]
}
}],
removeRuleIds: []
});
}
// 从规则文件批量导入
async function importRulesFromURL(url) {
const response = await fetch(url);
const rules = await response.json();
const maxId = Math.max(
0,
...rules.map(r => r.id)
);
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: rules.map((rule, i) => ({
...rule,
id: maxId + i + 1
})),
removeRuleIds: []
});
}
11.8 注意事项
| 问题 | 说明 | 解决方案 |
|---|
| 规则不生效 | 优先级冲突 | 检查 priority 值,高优先级规则先匹配 |
| 无法读取请求内容 | MV3 限制 | 仅能阻断/重定向,不能读取响应体 |
| 动态规则上限 | 最多 30,000 条 | 合并规则、使用 urlFilter 通配 |
| 跨域请求失败 | CORS 限制 | 需要 host_permissions 或服务端配置 |
webRequest 只读 | MV3 中 webRequest 仅能观察 | 使用 declarativeNetRequest 进行修改 |
11.9 扩展阅读