☰
HeaderEditor 修改响应体(Modify Response Body)实战指南:编码、拦截阶段与自定义函数的原理详解
2026/10/14 5:22:29 网站建设 项目流程
  • 开发工具
  • 网络

【免费下载链接】HeaderEditor

Manage browser's requests, include modify the request headers, response headers, response body, redirect requests, cancel requests

项目地址:https://gitcode.com/gh_mirrors/he/HeaderEditor
点击查看免费下载

本文基于 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'),这正是横幅出现的直接原因。

如果不想看到该提示,文档给出了两种做法:

  1. 在 Options(选项页)中关闭 "Modify Response Body" 开关(对应偏好项modify-body);
  2. 以--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"一节的三个小节一一对应:

  1. Encoding(编码):下拉框,支持过滤搜索,选项来自 encoding.ts 维护的 42 种编码列表;
  2. Request Stage(拦截阶段):Request/Response二选一,仅在非 Firefox环境下显示(ruleType === MODIFY_RECV_BODY && !IS_FIREFOX);
  3. 执行方式:普通模式(直接填写替换用的响应内容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 的格式是 CDPFetch.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)完整流程如下:

  1. 规则加载与开关判断:initRules从规则表加载启用且type === MODIFY_RECV_BODY的规则;最终启用条件为rules.length > 0 && modifyBody && !disableAll(checkEnable),与选项页modify-body开关、disable-all总开关联动;
  2. 按标签页附加调试器:监听tabs.onCreated/tabs.onUpdated触发enableFetch(tabId),先chrome.debugger.attach(协议版本1.3),再Fetch.enable;标签页关闭或debugger.onDetach时清理状态;
  3. 拦截处理:Fetch.requestPaused事件回调中,先用规则匹配器filter(this.rules, { url, method, responseHeaders, resourceType })过滤,resourceType通过resourceTypeMap(Document→main_frame、XHR/Fetch→xmlhttprequest等)映射到声明式请求的资源类型命名;无命中规则时直接Fetch.continueRequest放行;
  4. 拉取响应体:仅当命中规则中存在函数型规则时才调用Fetch.getResponseBody(hasFunc判断,L280-L284),避免不必要的响应体传输;
  5. 逐条规则改写:对每条命中规则——函数型规则执行rule._func(finalBody, detailObj, util);普通规则则直接把rule.body?.value作为新 body;同时收集规则携带的响应头(RULE_REMOVE_MARK标记表示删除该头);
  6. 重新编码并 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

项目地址:https://gitcode.com/gh_mirrors/he/HeaderEditor
点击查看免费下载

相关推荐

上一篇:AzerothCore搭建指南:魔兽世界私服60分钟可玩
下一篇:Granite-3.0-2B-Base-GGUF API接口设计:RESTful服务搭建教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询