全局脚本与断点

「脚本管理器」描述一条请求流经代理时的四个脚本挂载点:可在断点前后改写请求或响应。断点配置中与手动规则、脚本规则、配置组共同决定何时暂停与自动篡改放行。

脚本的四个阶段

  1. ① 请求断点前:例如先解密 Query/Body,让后续过滤器与手动断点看见明文。
  2. ② 转发前:在发往目标服务器之前写回篡改后的明文或重新加密。
  3. ③ 转发后、响应断点前:对下行内容预处理(例如解密)。
  4. ④ 响应断点后、返回客户端前:最后再加工(例如重新加密签名)。

脚本编辑器中为每一阶段单独编写脚本,并提供启用开关、格式化与保存。语法错误或未捕获异常会体现在脚本执行日志中。

在脚本管理器中点击某一阶段即可打开弹窗。标题标注阶段序号与名称(如「② 转发前」),顶部可开关「启用脚本」、右侧可「格式化」代码,底部取消或保存。

DevPeek 全局脚本编辑弹窗:② 转发前阶段示例
脚本管理器:单阶段编辑弹窗(启用、格式化、保存)

各阶段与入口函数

脚本管理器按阶段各保存一份脚本;下表「Hook」为内部阶段名(与存储、日志标签一致),「入口函数」名称须与下表完全一致,否则该段不会被执行。

阶段(界面文案)Hook入口函数形参
① 请求断点前preRequesthandleRequestInrequest
② 转发前requestModifyhandleRequestOutrequest
③ 转发后、响应断点前postForwardhandleResponseInresponse, request
④ 响应断点后、返回客户端前responseModifyhandleResponseOutresponse, request

入口可为 async 或返回 Promise;运行器会等待 Promise 完成后再继续代理链。各阶段仍受单次执行超时等限制约束。

沙箱内可用 API

下列标识符由脚本运行器注入到全局作用域(与 Node.js 默认全局不同);未出现在此表中的名称(如 require、process、import、fs 等)不可用。

  • console:log、warn、error、info、debug、trace、dir、dirxml、table、assert、count、countReset、time、timeEnd、group、groupCollapsed、groupEnd。开启脚本日志且当前请求具备日志收集上下文时,多数输出会同步写入该请求的脚本执行日志。
  • Buffer、Promise、JSON、Math、Date。
  • parseInt、parseFloat。
  • encodeURIComponent、decodeURIComponent、encodeURI、decodeURI。
  • btoa(str)、atob(str):基于 Buffer 的 Base64 辅助函数,语义接近浏览器同名 API。
  • crypto:Node.js 内置 crypto 模块(散列、对称/非对称加解密等,以 Node 文档为准)。
  • axios:可在脚本内发起 HTTP(S) 请求;行为受本机网络、DNS、目标站点策略等影响。
  • 未开放:require、process、module、exports、文件系统、子进程、任意访问 Electron 主进程等。

生效范围与限制

全局脚本在桌面端 DevPeek 的代理进程内执行,每条命中的会话按阶段进入 Node.js vm 沙箱;与「断点脚本规则」(在断点配置里为单条规则编写的 match/handle)不是同一套入口,二者勿混淆。

  • 作用对象:仅处理经本机 DevPeek HTTP(S) 代理转发的流量;对应阶段须在脚本管理器中启用并保存后才会运行。
  • 执行粒度:同一请求在四个阶段可分别执行不同脚本;各阶段读写的是该阶段传入的 request/response 对象。
  • 与管道顺序:① 在请求断点之前;② 在请求断点之后、发往源站之前;③ 在收到源站响应之后、响应断点之前;④ 在响应断点之后、回写给客户端之前。
  • 影响真实链路的改写:通过就地修改 request / response(如 body、headers)决定代理向上游或向下游发送的字节流;这与下面「返回值」控制的详情区展示可以独立存在。
  • multipart 限制:当 Content-Type 为 multipart 时,为避免破坏分段边界,实现上不会用脚本改写后的 request.body 去替换整段上行字节流(脚本仍会执行,日志与展示仍可用)。
  • 沙箱:仅可使用上文「沙箱内可用 API」列出的全局符号;不包含 require、process、文件系统等未注入能力。
  • 超时与失败:单次入口(含 async/await)在实现上有约数秒的执行时限;抛错或超时会导致该阶段记为失败,异常摘要写入脚本执行日志,该阶段对报文的改写不会生效,代理尽量按原始字节继续。
  • 响应阶段(③④)入口的第二个参数 `request` 来自会话里记录的「原始请求」快照(最初写入的 URL/Method/Headers/Body 文本),便于对照读;与经前两阶段改写后的实际上行报文不一定逐字节一致。

返回值与界面展示

各阶段入口(handleRequestIn、handleRequestOut、handleResponseIn、handleResponseOut)可以没有返回值(undefined)。返回值只影响抓包详情里「脚本」一侧的文本如何生成,不会自动替你改写 request.body / response.body;要让线上报文变化,仍需在函数体内修改传入对象。

当脚本执行成功时:若返回 undefined 或 null,详情里「脚本」视图会展示该阶段执行结束后对象上的 body 等(即你在内存里改完的结果)。若返回字符串,则原样作为「脚本」Tab 的展示文本。若返回普通对象或数组,会格式化为带缩进的 JSON 字符串展示。数字、布尔等会转成字符串显示。

若多个阶段都返回了非空展示值,后一阶段会覆盖前一阶段写入的展示字符串:请求侧 ② 覆盖 ①;响应侧 ④ 覆盖 ③。

在请求详情中,当存在「原始数据」快照且脚本已成功执行时,请求体、响应体等分区会出现内层 Tab,用于在「原始数据」与「脚本」之间切换对照;列表行也可能提供与「脚本」相关的列或关键词匹配,便于搜索脚本输出。

示例代码

① 请求断点前:解析 JSON、改写 body,并返回对象供「脚本」Tab 展示

function handleRequestIn(request) {
  console.log('[preRequest]', request.method, request.url)
  if (typeof request.body === 'string' && request.body) {
    try {
      const data = JSON.parse(request.body)
      request.body = JSON.stringify(Object.assign({}, data, { touchedByScript: true }))
      return { topLevelKeys: Object.keys(data) }
    } catch (e) {
      console.warn('body 不是合法 JSON', e && e.message)
    }
  }
}

② 转发前:async 入口与返回值字符串示例

async function handleRequestOut(request) {
  console.log('[requestModify] 即将转发', request.url)
  // 支持 async:可在此 await axios.get(...) 等
  return 'forwarding, header count=' + Object.keys(request.headers || {}).length
}

调试与排错

在脚本中使用 console.log / warn / error 等:当该请求带有 DevPeek 会话上下文时,输出会进入「脚本执行日志」窗口(设置菜单中打开),并按阶段标注,便于与详情里的「脚本」视图对照。

从桌面端启动 DevPeek 的终端或系统控制台查看主进程日志:脚本运行器在捕获未处理异常时也会向主进程 console 打印摘要。

若脚本未启用、内容为空、执行失败或未定义对应入口函数,该阶段相当于跳过;排查时先确认开关与保存,再缩小 URL 条件、用早期 return 二分定位。

断点:手动规则

断点配置中可添加多条手动规则:按 URL(支持通配符 *)、协议、方法过滤,可选地在 Query/Body 中匹配关键词或多组 key=value。每条规则需至少勾选 Req 和/或 Res 以决定在请求阶段、响应阶段或两阶段打断。

勾选自动断点后可填写「篡改」类字段:有篡改内容则自动放行,留空则进入手动断点等待你在 UI 中继续或中止。可同时维护请求头/体、响应头/体的整体替换或与现有头合并(响应头注释中提示受代理时机限制,部分站点仍可能接近源站表现)。

自动断点里填写的「篡改」落到真实链路的规则如下。请求头:多行「Header: value」会先解析为键值表(键名统一为小写,同一键后者覆盖前者);命中且因存在篡改内容而自动放行时,对表中每个键在发往源站前执行 setHeader,相当于**按字段覆盖**这些请求头,未出现在表里的头保持客户端原样。请求体:若填写了请求体篡改文本,则上行 body 会被替换为该字符串的 **UTF-8 整段内容(不是追加到原文末尾)**,并尝试同步 Content-Length;若只配了请求头、未填请求体,则上行 body 仍为原始报文。响应头:与当前响应头对象做**浅合并**,篡改表中的键覆盖同名键。响应体:同样为篡改区文本的 **UTF-8 整段替换** 原响应体。若开启自动断点但篡改区全空,且断点模式为默认的「手动」,则命中后仍会进入手动暂停,由你在界面继续或中止。

在「断点配置」中维护「单条规则」列表作为本地规则库。代理侧**同一时间只会应用其中一条规则**:内部只保存一条当前激活的规则标识,不会把多条规则同时拿来匹配,也不会按多条规则链式叠加执行。切换方式:主窗口菜单「菜单 › 代理 › 启动断点」下的「启动断点」子菜单会列出规则库中的全部条目,当前正在生效的条目前有 ✓ 标记;点选另一项会改为启用该条(原先选中的自动失活);再次点选带 ✓ 的当前项则关闭断点。列表为空时子菜单会提示暂无可选规则。若你曾使用更旧版本里「配置组」式界面,请以当前安装版本的断点设置与菜单为准。

断点:脚本规则

脚本类型规则需实现 match(...);可选返回 handle(...)。请求侧 handle(...) 可返回 含 body 字段的对象 篡改并放行,或 含 pause: true 的对象 进入手动暂停。响应侧可返回 body、完整 status/headers/body,或同样使用 pause。具体占位符请以应用内编辑器说明为准。

脚本规则:可用数据与 handle 语义

断点脚本在独立 vm 中执行:运行器只注入当前阶段的入参对象,以及你在同一段脚本里定义的 match / handle。**不会**像「脚本管理器」里的全局脚本那样注入 axios、crypto、Buffer 等;请在入参上完成判断与字符串级改写,或把需要外呼的逻辑放到全局脚本阶段。

  • 'https'
  • 响应侧(Res 标签页):必须 match(request, response)。response 含 status、headers、body,对应当前下行快照。可选 handle(request, response)。

handle 返回值:undefined、null 或 true 表示不在此步做篡改对象级改写(是否暂停仍由是否包含 pause 及断点模式决定)。返回普通对象时:可含 pause: true 进入手动断点;可同时带 body 字符串作为暂停时编辑器里的初值。不含 pause、且带有 body 和/或 headers 时,在满足「有篡改内容且未暂停」的自动继续分支下,会把 body **整段替换**为返回字符串的 UTF-8 字节,头则按请求 setHeader / 响应浅合并覆盖同名键(仅 headers、不返回 body 时则只改头、体保持原样)。

与手动规则自动篡改一致:返回的 body 始终是**整段替换**而非在原报文末尾自动拼接;若要「在原 HTML 后追加」,需在 handle 内自行把原 response.body 读入并与新片段拼接后再返回。

实现上 match 单次约 2 秒、handle 单次约 5 秒超时(以当前版本为准);超时会记失败并视为未匹配/未篡改。

与脚本管理器的先后:**请求断点**位于 **① preRequest 之后、② requestModify 之前**。断点脚本里的 request.body 来自该时刻管道中已缓冲的上行正文,通常已体现 **①** 中对 `request.body` 的就地改写;**② requestModify** 尚未执行,其结果不会出现在该断点脚本的入参里。**响应断点**位于 **③ postForward 之后、④ responseModify 之前**,`response.body` 一般为 **③** 处理后的下行正文。全局脚本入口的 **return 展示值** 不会作为额外字段注入断点 vm(仅用于抓包详情「脚本」视图);但若 **①** 曾返回非空展示字符串,会话会保存「preRequest 快照」,**响应侧**断点在构造 `match(request, response)` 里的 `request.body` 时会**优先**使用该快照,以便与详情里 return 文本对齐——若你几乎只 return、很少改 `request.body`,此处可能与实际上行字节不一致;需要与线上一致时请主要依赖 **①** 对 `request.body` 的改写。

示例:向 HTML 响应末尾注入片段

function match(request, response) {
  var ct = (response.headers && response.headers['content-type']) || ''
  return String(ct).toLowerCase().indexOf('text/html') >= 0
}

function handle(request, response) {
  var body = response.body == null ? '' : String(response.body)
  return { body: body + '
<!-- DevPeek: injected -->
' }
}

脚本日志窗口

脚本日志集中展示各阶段执行情况,便于和列表中的「脚本」视图交叉验证。清空策略以实际版本为准。

断点组合与脚本会直接影响线上请求:在共享环境或非授权目标上使用前请征得同意,并做好回滚预设。