- 开发工具
- 网络
【免费下载链接】HeaderEditor
Manage browser's requests, include modify the request headers, response headers, response body, redirect requests, cancel requests
本文基于 HeaderEditor 仓库中的官方文档 modify-body.md 与对应源码实现,系统讲解"修改响应体"(modifyReceiveBody)这一核心功能:如何在 Chrome 与 Firefox 下配置响应体替换规则,encoding、body.stage、自定义函数三大配置项的实际行为,以及底层分别基于 CDPFetch拦截与filterResponseData流过滤的实现机制。读完本文,你可以正确配置响应体改写规则(包括非 UTF-8 页面)、理解 Chrome 调试横幅的来源,并掌握自定义函数中val/detail参数的结构差异。
功能概览与使用须知
HeaderEditor 的"修改响应体"功能允许你拦截 HTTP 请求的响应,并替换其响应体(Body)内容,典型场景是替换页面中的文案、改写返回的 JS 脚本、修改接口返回的 JSON 文本等。
使用前需要注意文档明确列出的影响:
- 性能与资源占用:由于需要读取并重新编码响应内容,访问速度与资源消耗会受到一定影响;
- 内容下载:部分内容下载(如经过该链路代理的二进制资源)可能受到影响。
Chrome 下会出现"正在调试此浏览器"横幅
在 Chrome 上启用该功能后,浏览器地址栏下方会出现"Header Editor" started debugging this browser的提示。这是因为该功能在 Chrome 侧基于chrome.debuggerAPI(DevTools Protocol)实现,扩展一旦attach到标签页,浏览器就会展示调试横幅。从源码 chrome-response-modifier.ts 可以看到,扩展对每个标签页调用debuggerAPI.attach({ tabId }, '1.3'),这正是横幅出现的直接原因。
如果不想看到该提示,文档给出了两种做法:
- 在 Options(选项页)中关闭 "Modify Response Body" 开关(对应偏好项
modify-body); - 以
--silent-debugger-extension-api参数启动 Chrome,即可隐藏该横幅。
功能开关与自动启用
modify-body偏好的默认值为false,定义在 constant.ts。但有一个便利设计:当你在规则编辑器中保存一条modifyReceiveBody规则时,扩展会自动把modify-body置为true并弹出"已自动开启"修改响应体",如不需要请手动关闭"的提示。相关逻辑位于 edit/index.tsx,提示文案对应国际化 keyauto_enable_modify_body(见 messages.json)。
在选项页中,该开关的定义见 base-prefs.tsx:
'modify-body': { langKey: 'modify_body', type: 'switch', disabled: IS_FIREFOX && !IS_SUPPORT_STREAM_FILTER, },即 Firefox 下如果当前构建不支持filterResponseData流过滤,开关会被禁用——这决定了该功能在 Firefox 侧的可用性前提。
规则配置界面
modifyReceiveBody类型规则的执行配置项由 execution.tsx 渲染,与文档"Configuration"一节的三个小节一一对应:
- Encoding(编码):下拉框,支持过滤搜索,选项来自 encoding.ts 维护的 42 种编码列表;
- Request Stage(拦截阶段):
Request/Response二选一,仅在非 Firefox环境下显示(ruleType === MODIFY_RECV_BODY && !IS_FIREFOX); - 执行方式:普通模式(直接填写替换用的响应内容
body.value)或自定义函数模式(isFunction: true,代码编辑器中编写val/detail逻辑);
此外还有一个文档未展开、但源码中明确支持的配置点:修改响应体规则可以携带响应头修改("Modify body can also modify headers",见 execution.tsx)。在 Firefox 侧 web-request-handler.ts 会把 body 规则的 headers 一并应用,Chrome 侧则在Fetch.fulfillRequest前合并进responseHeaders(见 chrome-response-modifier.ts)。这一点在 Request 阶段拦截时尤其有用——因为拿不到服务端响应头,你可能需要手动补上Content-Type等头。
配置项一:Encoding(编码)
行为约定
文档对该项的约定如下:
- HeaderEditor 默认使用UTF-8解码传输内容。若目标网站不是 UTF-8 编码,需要手动指定编码;
- 该编码仅用于"解码"入站内容。修改后的响应始终按 UTF-8 重新编码输出;
- 如果不确定页面编码,可打开控制台(F12)→ Network 面板 → 刷新页面,查看 Response Headers 中
Content-Type里的charset。
可用编码列表
规则编辑表单提供的编码清单完整收录在 encoding.ts,共 42 项,涵盖:
- 中文:
utf-8、gbk、gb18030、big5、hz-gb-2312; - 日文:
euc-jp、iso-2022-jp、shift-jis; - 韩文:
euc-kr、iso-2022-kr; - Windows 系:
windows-874、windows-1250~windows-1258; - ISO 8859 系列:
iso-8859-2~iso-8859-16(不含iso-8859-1/9); - 其他:
koi8-r、koi8-u、macintosh、x-mac-cyrillic、utf-16be、utf-16le、x-user-defined、replacement。
该列表与浏览器 Encoding API 支持的编码集合一致(编码全集可查阅 MDN 的 Encoding API 文档)。
源码中的解码实现
解码与编码的核心工具集中在 text-coder.ts:
textDecode(encoding, buffer):基于 Web 标准TextDecoder,并对每种编码缓存 Decoder 实例(textDecoder: Map<string, TextDecoder>),解码失败时记录错误并返回undefined而非抛错,防止单条规则失败拖垮整个响应链路(见 text-coder.ts);textEncode(text):TextEncoder封装,输出恒为 UTF-8,对应文档"修改后响应总是 UTF-8 编码"的约定;safeBtoa/safeAtob/uint8ArrayToBase64:为 Chrome 的 CDP 通道(Fetch.getResponseBody返回 base64、Fetch.fulfillRequest接收 base64)提供编解码,并做了性能优化——纯 ASCII 内容走快路径直接btoa/atob,含高字节时才分块(4096)转换;safeAtob只对utf-8、latin1、windows-1252等"ASCII 段与 Unicode 一一对应"的编码启用快路径,shift-jis、iso-2022-jp等编码不会误走快路径(见 text-coder.ts)。
Firefox 侧调用方式:web-request-handler.ts 中textDecode(encoding, buffers),encoding取规则值、缺省utf-8;Chrome 侧则是safeAtob(rule.encoding || 'utf-8', resp.body)(见 chrome-response-modifier.ts)。
配置项二:Request Stage(拦截阶段)
文档指出,在 Chrome 上你可以选择在请求的哪个阶段拦截:
| 阶段 | 行为 | 可访问信息 | 性能 |
|---|---|---|---|
Request | 请求根本不会真正发送到服务器,由扩展直接伪造(fulfill)响应 | 无法访问服务端响应头与响应体;多数情况下需要手动配置Content-Type | 更好,无需等待服务器往返 |
Response | 等待服务器真实响应后再改写 | 自定义函数中可以读取服务端响应头与响应体 | 需等待完整响应 |
而Firefox 上该配置不存在,始终为 Response 阶段(响应流到达后再经filterResponseData改写)。
源码中的阶段逻辑
UI 层的阶段下拉框仅在非 Firefox 下渲染(execution.tsx),存储字段为body.stage,类型定义在 types.ts:stage?: 'Request' | 'Response'。
Chrome 侧的阶段生效逻辑见 chrome-response-modifier.ts 的checkStage:
const hasResponse = this.rules.some(r => r.body?.stage === 'Response'); const newStage = hasResponse ? 'Response' : 'Request'; if (newStage !== this.stage) { this.stage = newStage; // 对已附加的标签页重新 disable/enable Fetch }即:只要启用规则中存在任意一条stage: 'Response',整个扩展就运行在 Response 阶段,否则运行在 Request 阶段;阶段变化会触发对所有已 attach 标签页的Fetch.disable+Fetch.enable重建。Fetch.enable的完整参数见 chrome-response-modifier.ts:
await debuggerAPI.sendCommand({ tabId }, 'Fetch.enable', { patterns: [ { urlPattern: 'http://*', requestStage: this.stage }, { urlPattern: 'https://*', requestStage: this.stage }, ], });这里requestStage直接透传为 CDPFetch.enable的合法取值(Request/Response),与文档描述完全对应。
配置项三:Custom Function(自定义函数)
函数签名与返回值
文档约定:自定义函数接收两个参数——val为已按encoding解码后的响应文本,detail为当前请求的 detail 对象;函数返回修改后的文本。例如把页面上所有 "baidu" 替换为 "Google":
return val.replace(/baidu/g, 'Google');从源码结构看,实际调用为rule._func(finalBody, detailObj, util)(见 chrome-response-modifier.ts 与 web-request-handler.ts),第三个入参util是扩展注入的内置工具对象,定义在 function-util.ts,包含:
util._:lodash 常用函数子集(clone、first、uniq、pick、get/set等);util.atob/util.btoa:带编码感知的安全 base64 编解码(safeAtob/safeBtoa);util.textDecode/util.textEncode、util.uint8ArrayToBase64。
返回值方面,文档只提到"返回修改后的文本",而源码进一步确认还支持返回Uint8Array二进制:
if (typeof result === 'string' && result !== finalBody) { finalBody = result; hasChanged = true; } if (typeof result === 'object' && result instanceof Uint8Array) { finalBody = result; hasChanged = true; }(见 web-request-handler.ts)这意味着自定义函数可以输出非文本内容(如图片、zip 片段),扩展会按二进制直接写回响应流。
detail 对象:Firefox 与 Chrome 的差异
文档强调detail.browser可取得浏览器类型(chrome或firefox),两个浏览器的 detail 结构不同:
- Firefox:detail 与其他自定义函数(见 custom-function 文档)一致。从 web-request-handler.ts 的
CustomFunctionDetail接口可看到其字段:id(requestId)、url、tab、method、frame、parentFrame、proxy、type(资源类型)、time、originUrl、documentUrl、incognito、cookieStoreId、requestHeaders、responseHeaders、statusCode、statusLine、browser,以及响应体修改专属的rawResponse(完整响应的Uint8Array原始字节,见 web-request-handler.ts)。 - Chrome:detail 的格式是 CDP
Fetch.requestPaused事件的params对象(含requestId、request、responseHeaders、resourceType等,可查阅 CDP Fetch 域文档),并额外附加了rawResponse(Fetch.getResponseBody返回的 body 字符串,仅当存在函数型规则时才拉取)与browser: 'chrome',构造代码见 chrome-response-modifier.ts:
const detailObj = { ...params, // Fetch.requestPaused 事件参数 rawResponse: resp?.body, browser: 'chrome', };因此跨浏览器编写函数时,建议只依赖detail.browser、detail.rawResponse这类两端共有语义的字段,其余字段按浏览器分支处理。
一条经过 e2e 测试验证的函数示例
仓库的端到端测试 response.spec.mjs 中有一条真实的自定义函数用例:拦截mock-js.php的响应,用正则把单引号包裹的内容改为双引号包裹并拼接前后缀:
code: `return val.replace(/'(.*?)'/, '"${key1}$1${key1}$1"');`,测试随后断言页面中脚本实际执行到的值等于改写后的${key1}${key2}${key1}${key2},验证了"函数返回值确实成为最终响应体"这条链路。
实现机制剖析
Chrome:CDP Fetch 域拦截
ChromeResponseModifier(chrome-response-modifier.ts)完整流程如下:
- 规则加载与开关判断:
initRules从规则表加载启用且type === MODIFY_RECV_BODY的规则;最终启用条件为rules.length > 0 && modifyBody && !disableAll(checkEnable),与选项页modify-body开关、disable-all总开关联动; - 按标签页附加调试器:监听
tabs.onCreated/tabs.onUpdated触发enableFetch(tabId),先chrome.debugger.attach(协议版本1.3),再Fetch.enable;标签页关闭或debugger.onDetach时清理状态; - 拦截处理:
Fetch.requestPaused事件回调中,先用规则匹配器filter(this.rules, { url, method, responseHeaders, resourceType })过滤,resourceType通过resourceTypeMap(Document→main_frame、XHR/Fetch→xmlhttprequest等)映射到声明式请求的资源类型命名;无命中规则时直接Fetch.continueRequest放行; - 拉取响应体:仅当命中规则中存在函数型规则时才调用
Fetch.getResponseBody(hasFunc判断,L280-L284),避免不必要的响应体传输; - 逐条规则改写:对每条命中规则——函数型规则执行
rule._func(finalBody, detailObj, util);普通规则则直接把rule.body?.value作为新 body;同时收集规则携带的响应头(RULE_REMOVE_MARK标记表示删除该头); - 重新编码并 fulfill:字符串结果经
safeBtoa、Uint8Array结果经uint8ArrayToBase64转为 base64,最终以responseCode: 200调用Fetch.fulfillRequest伪造响应(L341-L346)。
Request 与 Response 阶段的差异在这里体现得很直接:Request阶段暂停时根本没有服务端响应,responseHeaders为空、Fetch.getResponseBody无意义,所以改写内容只能来自规则本身(普通值或函数对空val的生成逻辑),响应头需要靠规则手动补全;Response阶段暂停时 CDP 已携带真实responseHeaders,函数中可读到它们。
Firefox:filterResponseData 响应流过滤
Firefox 侧(WebExtension v2)不经过 debugger,而是在onHeadersReceived事件中调用browser.webRequest.filterResponseData(requestId)取得响应流过滤器。核心实现是 web-request-handler.ts 的modifyReceivedBody,要点:
- 前置门槛:
IS_SUPPORT_STREAM_FILTER(ENABLE_WEB_REQUEST && typeof browser.webRequest.filterResponseData === 'function',定义见 browser.ts)不满足直接跳过; - 8MB 上限:
handleReceived中若响应带Content-Length且 ≥MAX_BODY_SIZE(8 * 1024 * 1024)则不做修改(L319-L328);流式缓冲阶段一旦累计超过 8MB,也立即切为透传模式——把已缓冲的分片原样写出,后续数据零拷贝直接write,保证大文件不被截断也不额外吃内存(L543-L564); - 两种执行路径:无函数型规则时走"simple execute",
onstop后直接把最后一条规则的body.value编码写出并close();存在函数型规则时,先缓冲全部分片,onstop后合并为单个Uint8Array,按encoding解码得到finalBody,依次执行各函数规则(detail.rawResponse指向合并后的原始字节),有变化则写回改写结果,否则写回原字节;onerror时会把已缓冲数据 flush 后关闭,避免响应体丢失; - 编码往返:入站
textDecode(encoding, buffers),出站textEncode(UTF-8),与 Chrome 侧同一套约定。
已知问题与限制
- Chrome 新标签页首次访问不生效(文档 Known Issues 原文:"On Chrome, response modifications do not take effect when accessing a site for the first time in a new tab")。从源码结构看,
enableFetch是在tabs.onCreated/onUpdated回调中异步attach+Fetch.enable的,而pendingTabIds只用于去重、并不会挂起请求;可以推断标签页创建后发出的首个请求可能先于Fetch.enable完成而直接放行,因此需要刷新或再次访问才会被拦截; - Firefox 下 8MB 响应体上限:超过
MAX_BODY_SIZE的响应不做改写(直接透传),无Content-Length的流式响应则依赖分片累计判断; - 性能与下载影响:文档提示该功能会影响访问速度与资源占用,部分内容下载可能受影响,建议只对必要 URL 配置规则,并在不需要时通过 Options 页关闭
modify-body开关。
小结
HeaderEditor 的"修改响应体"是一个横跨两种浏览器架构的功能:Chrome 侧依赖chrome.debugger+ CDPFetch域,通过requestStage参数实现 Request/Response 两阶段拦截,并以Fetch.fulfillRequest伪造响应;Firefox 侧依赖webRequest.filterResponseData的响应流过滤,配合 8MB 缓冲上限与透传保护。配置时把握三个要点即可:非 UTF-8 页面务必在encoding中指定正确编码(它只影响解码,输出恒为 UTF-8);Chrome 下按"是否需要读取真实响应"选择body.stage;自定义函数只依赖val、detail.browser、detail.rawResponse等两端共有语义,并注意函数返回Uint8Array可以输出二进制内容。配合仓库中 tests/e2e/response.spec.mjs 的端到端用例,可以完整验证从普通替换到函数改写的整条链路。
- 开发工具
- 网络
【免费下载链接】HeaderEditor
Manage browser's requests, include modify the request headers, response headers, response body, redirect requests, cancel requests
相关推荐
Litmus pod-http-modify-body 实验详解:通过代理改写 HTTP 响应 Body 的混沌故障注入
Litmus pod http modify body 实验详解:通过代理改写 HTTP 响应 Body 的混沌故障注入 本文围绕 Litmus 混沌实验清单中
云原生运维可观测性Higress custom-response 插件完全指南:自定义 HTTP 应答状态码、响应头与 Body
Higress custom response 插件完全指南:自定义 HTTP 应答状态码、响应头与 Body 本篇技术指南以 Higress 开源仓库中 cu
API网关后端云原生LLM 网关人工智能MCP 服务QuickRecorder 安装教程:macOS 免费录屏,不装虚拟声卡录系统声音
QuickRecorder 安装教程:macOS 免费录屏,不装虚拟声卡录系统声音 QuickRecorder 是一款免费的开源 macOS 录屏工具,基于苹果
桌面应用音视频屏幕录制
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考