优雅 DIY 响应数据(XHR / fetch 拦截器)
前端轮子系列:
- 前端部署后-判断页面是否为最新
- 如何优雅diy响应数据
- 如何优雅进行webview调试
- 如何实现测试环境的自动登录
一个面向前端联调/测试的 Chrome 扩展:在 页面中的xhr\fetch请求,命中规则时直接返回自定义响应,从而"让业务代码以为请求成功了"。😂
你能得到什么
- 按规则拦截 XHR:请求 URL 正则 + 方法(ANY/GET/POST...)匹配后返回自定义文本/JSON
- DevTools 面板可视化管理:新增 / 编辑 / 删除 / 搜索 / 开关
- 实时生效 :规则与开关存储在
chrome.storage.local,变更后同步到页面
一、快速开始(安装与使用)
1) 安装(开发者模式)
- 打开 Chrome 扩展管理页:
chrome://extensions/ - 右上角开启 开发者模式
- 点击 加载已解压的扩展程序 ,选择
Interceptors/目录

2) 使用(DevTools 面板)
- 打开要调试的网站
- 打开 DevTools(F12)
- 切到面板 "XHR拦截器"
- 打开右上角 拦截开关
- 新增规则(pattern / method / response),保存即可生效

二、实现思路
1. 构建插件目录
如果是第一次开发浏览器插件看过来 官方的 hello world
- manifest.json 文件 => 定义插件的权限、内容脚本、web_accessible_resources 等
- devtools.html 文件 => 定义插件的 DevTools 入口页面
- devtools.js 文件 => 创建 DevTools 面板
- panel.html 文件 => 定义插件的面板页面
- panel.js 文件 => 定义插件的面板脚本
- scripts/background.js 文件 => 定义插件的后台脚本 / 内容注入逻辑
- scripts/page-inject.js 文件 => 定义插件的页面上下文拦截逻辑
2. 建立插件与页面的通信
因为插件和页面是不同的域,所以需要建立插件与页面的通信
- 在页面中注入
page-inject.js脚本- 负责拦截、重写页面中的 XMLHttpRequest 请求
- 通过 监听 message 事件 接收插件的规则、自定义返回内容
- 匹配到规则后,构建响应数据并返回数据(阻止请求的发送)
- 在插件中注入
background.js脚本 => 负责 插件与页面通信 => 通过 postMessage 发送配置到页面- 通过 chrome.storage.onChanged 监听 storage 变化
- 当 storage 变化时,发送配置到页面
三、具体实现(上菜)
1. 构建项目
sh
mkdir interceptor && cd interceptor
- manifest.json 文件 => 定义插件的权限、内容脚本、web_accessible_resources 等 声明:版本、名字、描述、权限、主机权限、脚本、资源访问权限
json
{
"manifest_version": 3,
"name": "XHR Interceptor",
"description": "通过可配置的规则拦截并替换 XHR/fetch 请求结果。",
"version": "1.0.0",
"permissions": ["storage"],
"host_permissions": ["<all_urls>"],
"devtools_page": "devtools.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["scripts/background.js"],
"run_at": "document_start"
}
],
"web_accessible_resources": [
{
"resources": ["scripts/page-inject.js"],
"matches": ["<all_urls>"]
}
]
}
- devtools.html 文件 => 定义插件的 DevTools 入口页面
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
</head>
<body>
<!-- 引入 devtools.js 文件 => 创建面板 -->
<script src="devtools.js"></script>
</body>
</html>
- devtools.js 文件 => 创建 DevTools 面板 panels面板
js
chrome.devtools.panels.create(
"XHR拦截器",
null,
"panel.html",
(panel) => {
if (panel) console.log("[XHR Interceptor] DevTools panel created.");
}
);
- panel.html 文件 => 定义插件的面板页面
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>XHR Interceptor</title>
<!-- 样式文件引入 -->
<link rel="stylesheet" href="panel.css" />
</head>
<body>
<!-- 插件面板样式构建 -->
<!-- 引入 panel.js 文件 => 定义面板交互逻辑 -->
<script src="panel.js" type="module"></script>
</body>
</html>
2. 注入js脚本到页面
- background.js 中 注入 page-inject.js 脚本到页面
js
const PAGE_SCRIPT = chrome.runtime.getURL("scripts/page-inject.js");
function injectPageScript() {
const script = document.createElement("script");
script.src = PAGE_SCRIPT;
script.type = "text/javascript";
script.addEventListener("load", () => script.remove());
script.addEventListener("error", () => script.remove());
document.documentElement.appendChild(script);
}
- background.js 中 监听 storage 变化
js
const MESSAGE_SOURCE = "xhr-interceptor-extension";
chrome.storage.onChanged.addListener((changes, namespace) => {
// 当 storage 变化时,发送配置到页面
postConfigToPage({});
});
function postConfigToPage(config) {
// 直接使用 window.postMessage:content script 与页面共享同一个 window 消息通道,
// 且不会触发页面的 CSP(避免内联脚本被阻止)。
window.postMessage(
{
source: MESSAGE_SOURCE,
type: "config:update",
payload: config || buildConfig(),
},
"*"
);
}
3. 重写 XHR、fetch 方法
js
// 保存原始方法
const ORIGINAL = {
open: XMLHttpRequest.prototype.open,
send: XMLHttpRequest.prototype.send,
fetch: window.fetch ? window.fetch.bind(window) : null,
};
function hookXMLHttpRequest() {
XMLHttpRequest.prototype.open = function patchedOpen(method, url) {
// 做一些标记、额外操作
return ORIGINAL.open.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function patchedSend(body) {
// 检查 开关是否打开、规则是否匹配
// 如果匹配,构建响应数据并返回
// 如果不匹配,继续发送请求
return ORIGINAL.send.apply(this, arguments);
};
}
function hookFetch() {
if (!ORIGINAL.fetch) return;
window.fetch = function patchedFetch(input, init) {
// 解析 method + url,然后复用 pickMatchingRule 进行匹配
const meta = extractFetchMeta(input, init);
const rule = pickMatchingRule(meta.method, meta.url);
if (!enabled || !rule) return ORIGINAL.fetch(input, init);
const res = buildMockFetchResponse(rule);
return Promise.resolve(res);
};
}
4. 规则列表匹配
js
let rules = []; // 保存规则列表
function pickMatchingRule(method, url) {
if (!url) return null;
const upperMethod = (method || "GET").toUpperCase();
for (const rule of rules) {
if (!rule.regex) continue;
if (rule.method !== "ANY" && rule.method !== upperMethod) continue;
rule.regex.lastIndex = 0;
if (rule.regex.test(url)) {
return rule;
}
}
return null;
}
5. 监听消息 & 同步面板数据、规则到页面
接收插件的规则、自定义返回内容(从 background.js 发送的消息)
js
const script = document.currentScript;
const messageSource =
(script && script.dataset && script.dataset.source) ||
"xhr-interceptor-extension";
// 监听消息通道
function setupMessageChannel() {
window.addEventListener("message", (event) => {
if (event.source !== window) return;
if (!event.data || event.data.source !== messageSource) return;
if (event.data.type === "config:update") {
// 判断为background.js发送的消息
// 更新插件面板的数据(开关的状态、规则的数据)
}
});
}
6. 面板存储规则到storage & 处理ui交互
- staorage 的读取、设置、移除
js
// 读取
const stored = await chrome.storage.local.get({ rules: [] })
// 设置
await chrome.storage.local.set({ rules });
// 移除 可以传 字符串 | 数组
await chrome.storage.local.remove("rules");
// 清空
await chrome.storage.local.clear();
- 面板的ui交互 正常的ui交互,正常的原生开发就行
源码
结语
如果本文对你有收获,麻烦动动发财的小手,点点关注、点点赞!!!👻👻👻
因为收藏===会了
如果有不对、更好的方式实现、可以优化的地方欢迎在评论区指出,谢谢👾👾👾