10. HTTP Engine#


HTTP Engine 是 Link1 的七层处理模块,用于 HTTP/HTTPS 调试、兼容修补、请求/响应改写、Mock、Capture 和 Replay。普通代理不需要开启它;只有当你明确需要观察或修改 HTTP 内容时再使用。

工作位置#

入站连接
  -> 路由/出站选择
  -> HTTP Engine 判断是否接管 HTTP 流
  -> URL/Header/Body/JSON/JQ/Script/Mock/Route 规则
  -> Capture 记录
  -> 上游或客户端

HTTPS 内容默认是加密的。要改写 HTTPS 请求/响应,需要 MITM:

  1. 生成或导入 CA。
  1. 客户端信任该 CA。
  1. http-engine.mitm.hosts 命中目标域名。
  1. 连接经过 Link1。

最小启用#

http-engine:
  enabled: true
  defaults:
    body-max-size: 1 MiB
    on-error: fail-open

字段影响:

字段含义实际影响
enabled开启 HTTP Engine关闭时所有 HTTP Engine 规则不运行
defaults默认限制和错误策略控制 body/JQ/script 默认行为
mitmHTTPS MITM解密指定 HTTPS 域名
force-http-engine强制进入 HTTP Engine 的 host/pattern对特定流量启用 L7 处理
downstream-h3-proxy下游 HTTP/3 proxy 支持影响客户端到 Link1 的 H3 行为
upstream-h3上游 HTTP/3 策略offhintedaggressive
capture捕获流量记录请求/响应元数据和 body
scriptsQuickJS 脚本源供 script 规则引用
rules规则集合URL/Header/Body/JSON/JQ/Script/Mock/Route

defaults#

http-engine:
  defaults:
    body-max-size: 1 MiB
    jq-timeout: 100ms
    script-timeout: 50ms
    script-memory-limit: 8 MiB
    on-error: fail-open
字段影响
body-max-size默认允许读取/改写的 body 大小
jq-timeoutJQ 默认执行超时
script-timeoutQuickJS 默认执行超时
script-memory-limitQuickJS 默认内存限制
on-errorfail-open 出错放行;fail-closed 出错阻断

MITM#

使用 App 自动管理的 CA#

managed: ca-managed 表示使用 Link1 App 创建和保存的本地 CA;普通用户不需要手写 PEM,只需要在 App 中生成/信任 CA,再在配置中引用这个 CA ID。

http-engine:
  enabled: true
  mitm:
    enabled: true
    ca-cert:
      managed: ca-managed
    ca-key:
      managed: ca-managed
    hosts:
      - service.example.com
      - '*.debug.example.com'
    h2: true
    leaf-cache-max-entries: 1024

使用 PEM 文件#

http-engine:
  enabled: true
  mitm:
    enabled: true
    ca-cert:
      file: ./certs/ca.pem
    ca-key:
      file: ./certs/ca.key
    hosts:
      - service.example.com

使用内联 PEM#

ca-cert:
  inline-pem: |
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----
ca-key:
  inline-pem: |
    -----BEGIN PRIVATE KEY-----
    ...
    -----END PRIVATE KEY-----

字段影响:

字段影响
enabled开启 MITM
ca-cert / ca-keyCA 证书和私钥来源,必须能配对
hosts只对命中的主机 MITM
h2是否支持 HTTP/2 leaf
leaf-cache-max-entries叶子证书缓存数量

安全提示:只 MITM 你需要调试的域名,不要对全网开启。

Capture#

http-engine:
  enabled: true
  capture:
    enabled: true
    max-flows: 1000
    body-preview-bytes: 4 KiB
    store-full-body: true
    full-body-max-bytes: 1 MiB
    spool-dir: ./http-capture

字段影响:

字段影响
enabled开启捕获
max-flows保留最近多少条 flow
body-preview-bytes列表里展示的 body 预览大小
store-full-body是否保存完整 body
full-body-max-bytes完整 body 最大保存大小
spool-dirbody 落盘目录

Link1 App 展示:

这些内容只在 Capture 开启且流量命中 HTTP Engine 时出现。

Match 条件#

所有 HTTP Engine 规则都有 match。字段:

字段含义
view匹配视图/阶段
url / url-regex完整 URL 精确/正则
schemehttphttps
hostHost 精确/模式匹配
path / path-regex路径匹配
query / query-regexQuery 匹配
methodHTTP 方法
content-typeContent-Type
user-agent / user-agent-regexUser-Agent
header / header-regex请求/响应头
cookie / cookie-regexCookie
protocolHTTP 协议版本/类型
entry-point入站入口

示例:

match:
  scheme: [https]
  host: service.example.com
  path-regex: '^/old/'
  method: [GET, POST]
  header:
    X-Client: app

URL rewrite#

http-engine:
  rules:
    url-rewrite:
      - name: rewrite-service
        match:
          host: old.example.com
          path: /old/items
        action:
          type: rewrite-url
          replacement: https://new.example.com/new/items

Action 类型:

type影响
rewrite-url改写请求 URL 后继续转发
redirect直接返回重定向,配合 status/location
reject拒绝请求
reject-200返回 200 空响应
reject-img返回图片占位
reject-dict返回空对象
reject-array返回空数组

Header rewrite#

http-engine:
  rules:
    header-rewrite:
      - name: add-debug-header
        direction: request
        match:
          host: service.example.com
        operations:
          - op: set
            key: X-Debug
            value: '1'
          - op: del
            key: X-Remove-Me

方向:requestresponse

操作:

op影响
set设置为指定值
replace替换现有值
add追加 header 值
del / delete删除 header
replace-regex用正则替换 header 值

Body rewrite#

http-engine:
  rules:
    body-rewrite:
      - name: patch-text
        direction: response
        match:
          host: service.example.com
          content-type: [text/plain]
        require-body: true
        max-size: 512 KiB
        on-error: fail-open
        operations:
          - op: replace
            from: old
            to: new
          - op: replace-regex
            pattern: 'token=[^&]+'
            replacement: 'token=redacted'

字段影响:

字段影响
require-body无 body 时是否视为不匹配/错误
max-size超过大小不处理
on-error出错后放行或阻断
operations顺序执行文本替换

JSON transform#

http-engine:
  rules:
    json-transform:
      - name: normalize-json
        direction: response
        match:
          host: service.example.com
          content-type: [application/json]
        require-body: true
        max-size: 1 MiB
        when:
          - path: $.ok
            eq: true
        ops:
          - op: set
            path: $.source
            value: link1
          - op: set-if-missing
            path: $.items
            value: []
          - op: del
            path: $.debug

规则字段:

字段影响
whenJSON 谓词,全部满足才执行
opsJSON 操作列表

when 支持:patheqneqinnot-inexists

操作支持:

op影响
set设置路径值
set-if-missing路径不存在时设置
del删除路径
append向数组追加
merge合并对象
replace-if-eq当前值等于 equals 时替换
filter-array过滤数组元素
update-array-items更新数组元素

数组过滤条件 where 支持 fieldeqneqall

JQ#

http-engine:
  rules:
    jq:
      - name: jq-response
        direction: response
        match:
          host: service.example.com
          content-type: [application/json]
        require-body: true
        max-size: 1 MiB
        timeout: 100ms
        on-error: fail-open
        expression: '.items |= map(. + {source: $source})'
        variables:
          source: link1

字段影响:

字段影响
expressionJQ 表达式
variables传给 JQ 的变量
timeout单次执行超时
max-sizebody 大小上限
on-error出错策略

QuickJS script#

QuickJS script 适合处理“内置规则表达不出来,但又不值得写独立服务”的 HTTP 改写逻辑,例如:按响应 JSON 动态补字段、把某些请求直接 Mock 掉、根据 Header 临时标记路由。它运行在 Link1 内核里,不是浏览器插件,也不是 Node.js 环境。

脚本源#

先在 http-engine.scripts 中声明脚本源,再在规则里引用它。脚本名必须唯一。

http-engine:
  scripts:
    - name: patcher
      source:
        file: ./http-engine/scripts/patcher.js

脚本也可以内联,适合很短的规则:

http-engine:
  scripts:
    - name: inline-script
      source:
        inline: |
          function onResponse(payload) {
            return {
              headers: {
                "X-Link1": "1"
              }
            };
          }

注意:脚本函数应直接声明为全局函数 function onRequest(...) / function onResponse(...)。不要写 export function,也不要假设有模块系统。

规则引用脚本#

http-engine:
  rules:
    script:
      - name: run-patcher
        direction: response
        match:
          host: service.example.com
        engine: quickjs
        script: patcher
        require-body: true
        max-size: 1 MiB
        timeout: 50ms
        memory-limit: 8 MiB
        on-error: fail-open
        arguments:
          env: prod

字段影响:

字段影响
directionrequest 调用 onRequest(payload)response 调用 onResponse(payload)
engine使用 quickjs;不写时也按 QuickJS 处理。
script引用 http-engine.scripts[].name。引用不存在会在配置编译阶段报错。
require-body要求脚本能读取 body。无 body、body 仍是流式、或超过 max-size 时按 on-error 处理。
max-size脚本最多读取多少 body。超过后,require-body=false 时脚本仍运行但看不到 body;require-body=true 时触发错误策略。
binary-body-mode把可读取的 body 作为 Uint8Array 放入 bodyBytes,适合图片、压缩包、protobuf 等二进制内容。
arguments传给脚本的只读字符串参数,适合把环境名、开关、固定值放进 YAML,而不是写死在 JS 里。
timeout单次脚本执行超时。循环或复杂计算超过时间会中断。
memory-limit单次脚本 VM 的内存上限。body 越大、JSON 越深,需要的内存越多。
on-errorfail-open 跳过本规则继续放行;fail-closed 把错误暴露给连接,适合调试和强策略。

Hook 与 payload 上下文#

direction 决定调用哪个 Hook:

function onRequest(payload) {
  // 只处理请求阶段
}

function onResponse(payload) {
  // 能看到请求与响应
}

payload 是 Link1 为当前 HTTP flow 构造的对象。脚本里修改 payload 本身不会自动生效;应该返回一个“改写结果 envelope”。不要为了“原样返回”而 return payload,否则在响应阶段可能把当前响应当成合成响应重新写一遍。

路径方向类型可写入返回值吗含义
payload.request.urlrequest/responsestring不能直接改,需返回 body/header/route 等 envelope当前请求完整 URL。
payload.request.methodrequest/responsestring不能直接改HTTP 方法,例如 GETPOST
payload.request.headersrequest/responseobject返回 headers 才会生效请求 Header。单值 header 是字符串,多值 header 是字符串数组。
payload.request.bodyrequest/responseany返回 body 才会生效当 body 能读取且能解析成 JSON 时出现。
payload.request.bodyTextrequest/responsestring不能作为输出字段当 body 能读取但不是 JSON 时出现。
payload.request.bodyBytesrequest/responseUint8Array返回 bodyBytes 才会生效binary-body-mode: true 且 body 可读取时出现。
payload.response.statusresponsenumber响应规则可返回 statusHTTP 状态码。
payload.response.headersresponseobject返回 headers 才会生效响应 Header。
payload.response.bodyresponseany返回 body 才会生效响应 body 解析成 JSON 后的值。
payload.response.bodyTextresponsestring不能作为输出字段响应 body 不是 JSON 时的文本。
payload.response.bodyBytesresponseUint8Array返回 bodyBytes 才会生效二进制模式下的响应 body。
payload.argumentsrequest/responseobject只读来自规则 arguments 的字符串键值。

Body 字段不会总是出现:

返回值 envelope#

脚本的返回值决定是否改写当前 flow。

返回值影响
undefined / null不做任何修改,继续处理后续规则。
{headers: {"X-A": "1"}}设置当前请求或响应 Header;方向由规则 direction 决定。
{status: 299}只对响应规则有意义,修改响应状态码。
{body: {...}}把当前请求或响应 body 改成 JSON 序列化后的内容。
{bodyBytes: [1,2,3]}{bodyBytes: Uint8Array}把当前请求或响应 body 改成精确字节;适合文本原样输出或二进制内容。
{response: {status, headers, body/bodyBytes}}在请求阶段直接返回合成响应,不再访问上游;在响应阶段整体替换当前响应。
{route: {outbound: "PROXY", freeze: true}}只在请求阶段可用,把当前 HTTP flow 标记到指定出站。freeze=false 表示只是候选,后续 route 规则仍可覆盖。
{outbound: "PROXY"}route 的简写,等价于冻结到指定出站。

输出 Header 建议写字符串值。输入 Header 可能是字符串数组,但输出数组会被当成普通值格式化,不适合表达多值 Header。

示例:按环境添加 Header#

http-engine:
  scripts:
    - name: add-env-header
      source:
        inline: |
          function onRequest(payload) {
            return {
              headers: {
                "X-Link1-Env": payload.arguments.env || "dev"
              }
            };
          }
  rules:
    script:
      - name: add-env-header
        direction: request
        match:
          host: api.example.com
        script: add-env-header
        arguments:
          env: prod

效果:发往 api.example.com 的请求会被加上 X-Link1-Env: prod。这个例子不需要读取 body,因此不要写 require-body: true

示例:修改 JSON 响应#

http-engine:
  scripts:
    - name: normalize-profile
      source:
        inline: |
          function onResponse(payload) {
            const body = payload.response.body;
            if (!body || typeof body !== "object") return;
            body.source = "link1";
            if (!Array.isArray(body.items)) body.items = [];
            delete body.debug;
            return {
              headers: {
                "Content-Type": "application/json"
              },
              body: body
            };
          }
  rules:
    script:
      - name: normalize-profile
        direction: response
        match:
          host: api.example.com
          path: /profile
          content-type: [application/json]
        script: normalize-profile
        require-body: true
        max-size: 1 MiB
        timeout: 50ms
        on-error: fail-open

效果:只有响应 body 能作为 JSON 读取时才修改。返回 body 后,Link1 会把它序列化成 JSON;如果你要保持原始缩进或非 JSON 文本,请使用 bodyBytes

示例:返回合成响应#

http-engine:
  scripts:
    - name: mock-maintenance
      source:
        inline: |
          function onRequest(payload) {
            if (payload.arguments.maintenance !== "on") return;
            return {
              response: {
                status: 503,
                headers: {
                  "Content-Type": "application/json",
                  "Retry-After": "60"
                },
                body: {
                  ok: false,
                  message: "maintenance"
                }
              }
            };
          }
  rules:
    script:
      - name: maintenance-page
        direction: request
        match:
          host: api.example.com
          path: /v1/order
        script: mock-maintenance
        arguments:
          maintenance: "on"
        on-error: fail-closed

效果:命中后 Link1 直接给客户端返回 503,不再连接 api.example.com。这种规则适合测试和临时降级,不适合长期替代服务端逻辑。

示例:二进制 body#

如果要精确输出文本字节或处理图片、压缩包、protobuf,请打开 binary-body-mode。QuickJS 环境没有 TextEncoder,如果只是调试 ASCII 文本,可以用一个小函数生成字节数组:

http-engine:
  scripts:
    - name: binary-debug
      source:
        inline: |
          function ascii(text) {
            const out = new Uint8Array(text.length);
            for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
            return out;
          }

          function onResponse(payload) {
            const input = payload.response.bodyBytes;
            if (!(input instanceof Uint8Array)) return;
            return {
              headers: {
                "Content-Type": "text/plain"
              },
              bodyBytes: ascii("bytes=" + input.length)
            };
          }
  rules:
    script:
      - name: binary-debug
        direction: response
        match:
          host: download.example.com
        script: binary-debug
        binary-body-mode: true
        require-body: true
        max-size: 64 KiB

效果:响应 body 被替换为 bytes=<长度> 这段原始文本字节。真实处理二进制时,应尽量缩小 matchmax-size,避免把大文件读进脚本。

调试方法#

QuickJS 脚本调试建议按这个顺序做:

  1. 先缩小 match:只匹配一个测试域名、路径和方法,避免影响真实业务。
  1. 先不用 body:先确认 Header 改写生效,再打开 require-body
  1. 调试时用 fail-closed:脚本抛错时,连接会失败,App 日志或连接错误里能看到规则名和错误信息;确认无误后再改回 fail-open
  1. throw new Error(...) 做断言:例如 if (!payload.response.body) throw new Error("missing json body")
  1. 用临时 Header 输出状态:例如返回 headers: {"X-Debug-Status": String(payload.response.status)},再在 Capture 或浏览器开发者工具里查看。
  1. 用合成响应输出调试信息:请求阶段可以临时返回 {response: {status: 200, body: {...}}},确认 payload 中到底有什么。
  1. 打开 Capture 看前后差异:比较原始请求/响应与改写后请求/响应,确认是 match 没命中、body 没读到,还是返回 envelope 写错。

QuickJS 环境不提供 console.log。如果脚本里写 console.log(...),会得到 ReferenceError: 'console' is not defined。也不要依赖 fetch、文件系统、DOM、浏览器对象、Node.js 模块、setTimeoutTextEncoder 或异步事件循环。

运行约束与安全边界#

Mock#

http-engine:
  rules:
    mock:
      - name: mock-health
        match:
          host: service.example.com
          path: /health
        response:
          status: 200
          headers:
            Content-Type: application/json
          body: '{"ok":true}'

响应 body 来源:

字段影响
body直接内联文本
body-file从文件读取
body-base64base64 body
tiny-gif返回 tiny gif

Route 规则#

http-engine:
  rules:
    route:
      - name: mark-service
        match:
          host: service.example.com
        outbound: PROXY

HTTP Engine route 用于在七层处理阶段给当前 HTTP flow 标记业务出口。例如,你可以根据 HTTP path、Header 或 JSON/JQ/QuickJS 的结果,把某个请求标记到 PROXY。它只影响已经进入 HTTP Engine 的 HTTP flow;全局、通用的出站分流仍建议写在顶层 rules,这样 DNS、TUN、非 HTTP 流量和普通连接都能得到一致处理。

Replay#

Replay 用于对捕获的 HTTP flow 做重放测试。典型流程:

  1. 在 App 的 HTTP 捕获列表中选择一条 flow。
  1. 查看原始请求和改写后请求,确认不会泄露敏感内容。
  1. 选择重放目标:使用原始目标,或临时改成测试目标。
  1. 执行重放,观察状态码、响应头、响应 body 和命中的 HTTP Engine 规则。

Replay 适合验证 URL rewrite、Header rewrite、JSON/JQ/QuickJS 改写和 Mock。它可能再次访问真实服务,包含登录态或业务数据的请求不要随意重放。

使用建议#