脚本 API 参考

RelayCore 内嵌 Deno/V8 运行时,通过 globalThis 全局钩子函数拦截和修改流量。脚本引擎支持 JavaScript 和 TypeScript。

生命周期钩子

脚本通过定义 globalThis 上的命名函数注册钩子。RelayCore 1.0 提供 11 个钩子,覆盖 HTTP、WebSocket 和连接生命周期:

钩子触发时机签名
onRequestHeaders请求头接收后(ctx, flow) => flow | undefined
onRequest请求体就绪后,转发前(ctx, flow, body) => action
onResponseHeaders响应头接收后(ctx, flow) => flow | undefined
onResponse响应体就绪后(ctx, flow, body) => action
onWebSocketMessageWebSocket 帧到达(ctx, flow, message) => message | undefined
onWebSocketStartWebSocket 握手完成(ctx, flow) => void
onWebSocketEndWebSocket 正常关闭(ctx, flow, code, reason) => void
onWebSocketErrorWebSocket 协议/IO 错误(ctx, flow, error) => void
onConnect客户端建立 TCP 连接(ctx, conn) => {drop:true} | undefined
onDisconnect连接关闭(ctx, conn, stats) => void
onError任何钩子抛错时(ctx, flow, error, stage) => void

Flow 对象

每个钩子收到的 flow 参数包含当前流的完整信息:

// Flow 结构(脚本侧视角)
{
  id: string,           // UUID
  start_time: string,    // ISO 8601
  tags: string[],       // 标签列表

  network: {
    client_ip: string,
    server_ip: string,
    protocol: "TCP" | "UDP",
    tls: boolean,
    sni: string | null,
  },

  // 规则桥接(RelayCore 独家)
  matched_rules: string[],       // 命中的规则 ID 列表
  rule_variables: Record,  // SetVariable 设置的变量

  // 应用层协议数据
  layer: {
    type: "Http" | "WebSocket",
    data: {
      // type = "Http" 时有:
      request: {
        method: string,
        url: string,        // 完整 URL
        version: string,    // "HTTP/1.1" | "HTTP/2.0"
        headers: [string, string][],
        body: {
          encoding: string,
          size: number,
          content: string,  // base64 编码
        } | null,
        cookies: [string, string][],
      },
      response: {
        status: number,
        status_text: string,
        version: string,
        headers: [string, string][],
        body: { encoding, size, content } | null,
        cookies: [string, string][],
      } | null,
    }
  }

内置工具库 relay.*

全局对象 relay 提供常用工具函数:

relay.uuid()

const id = relay.uuid();  // "550e8400-e29b-41d4-a716-446655440000"

relay.hash(algorithm, data)

const sha = relay.hash("sha256", "hello");
// 支持: sha1, sha256, sha512, md5
// 返回 hex 字符串

relay.base64.encode / relay.base64.decode

const b64 = relay.base64.encode("hello world");   // "aGVsbG8gd29ybGQ="
const raw = relay.base64.decode(b64);              // Uint8Array

relay.json.parseSafe / relay.json.stringifyPretty

const obj = relay.json.parseSafe('{"a":1}');      // {a:1} 或 undefined
const txt = relay.json.stringifyPretty(obj);          // 2 空格缩进

relay.env(name)

读取白名单内的环境变量,需 CLI 参数 --script-env-allow 显式声明:

// CLI: relay-core-cli run --script-env-allow=BACKEND_HOST
const host = relay.env("BACKEND_HOST");  // "api.example.com"
const miss = relay.env("SECRET");        // undefined(未在白名单)

relay.fetch(url, options?)

发起受控 HTTP 子请求。默认关闭,需 --enable-script-fetch 显式开启。

const resp = await relay.fetch("http://auth.internal/verify", {
  method: "POST",
  headers: { "Authorization": flow.layer.data.request.headers.find(h => h[0]==="authorization")?.[1] },
  body: JSON.stringify({ flow_id: flow.id }),
  timeout_ms: 2000,
});
// resp: { status, headers, body }
if (resp.status !== 200) {
  throw new Error("Auth failed");
}
安全控制说明
默认关闭必须传 --enable-script-fetch
白名单--script-fetch-allow=host1,host2
防递归fetch 自身代理端口会被拒绝
并发上限--script-fetch-max-concurrency=8
超时上限硬上限 30s
审计每次 fetch 进入 Audit Trail

relay.readBody(flow)

const body = await relay.readBody(flow);
// 对于流式 body,返回累积内容

全局状态 sharedState

globalThis.sharedState 是同一引擎实例内所有钩子共享的键值存储:

// 在 onRequest 中存储
globalThis.onRequest = (ctx, flow) => {
  sharedState.set(flow.id, { start: Date.now() });
  return flow;
};

// 在 onResponse 中读取
globalThis.onResponse = (ctx, flow) => {
  const meta = sharedState.get(flow.id);
  if (meta) {
    console.log(`Flow ${flow.id} took ${Date.now() - meta.start}ms`);
    sharedState.delete(flow.id);
  }
};

日志分级 console.*

脚本中 5 级日志分别映射到 Rust 的 tracing

console.log("请求到达");     // tracing::info!    — 运行时可见
console.info("状态正常");     // tracing::info!
console.warn("限速触发");     // tracing::warn!    — 需关注
console.error("鉴权失败");    // tracing::error!   — 必定可见
console.debug("变量值: " + x); // tracing::debug!  — RUST_LOG=debug 才可见

连接级钩子

// 连接过滤 — onConnect 返回 {drop:true} 直接拒绝连接
globalThis.onConnect = (ctx, conn) => {
  // conn: { id, client_ip, client_port }
  if (conn.client_ip.startsWith("192.168.1.")) {
    return { drop: true };
  }
};

// 连接统计
globalThis.onDisconnect = (ctx, conn, stats) => {
  // stats: { duration_ms, flows_count }
  console.log(`Connection ${conn.id}: ${stats.flows_count} flows`);
};

WebSocket 生命周期

globalThis.onWebSocketStart = (ctx, flow) => {
  console.log(`WS opened: ${flow.layer.data.request.url}`);
};

globalThis.onWebSocketEnd = (ctx, flow, code, reason) => {
  console.log(`WS closed: ${code} ${reason}`);
};

globalThis.onWebSocketError = (ctx, flow, error) => {
  console.error(`WS error: ${error}`);
};

规则桥接

RelayCore 独家能力——脚本可以感知规则引擎的执行结果:

globalThis.onResponse = (ctx, flow) => {
  // 读取命中的规则
  if (flow.matched_rules.includes("rule-block-tracking")) {
    flow.layer.data.response.headers.push(["X-Blocked-By", "RelayCore"]);
  }

  // 读取规则引擎 SetVariable 设置的变量
  const count = flow.rule_variables["rate_limit_count"];
  if (count && parseInt(count) > 100) {
    console.warn(`Rate limit exceeded: ${count}`);
  }
};

脚本管理与部署

CLI 命令

# 通过 HTTP API 加载脚本
curl -X POST http://127.0.0.1:8082/api/v1/script \
  -H "Content-Type: application/json" \
  -d '{"script": "globalThis.onRequest = (ctx, flow) => { console.log(flow.layer.data.request.url); }"}'

# MCP 工具(通过 AI Agent)
mcp: relay-core set_script --script script.js

# Bundle 模式:使用 npm 包
relay-core scripts init my-project      # 脚手架
cd my-project && npm install jwt-decode  # 安装依赖
relay-core scripts build                 # esbuild 打包
relay-core scripts dev                   # build + 热上传 + watch

Bundle 模式与 npm 生态

RelayCore 的 V8 isolate 不内置 npm 解析器。要通过 esbuild 将脚本和依赖打包为单文件:

// src/main.ts — 正常 import npm 包
import jwtDecode from "jwt-decode";
import { XMLParser } from "fast-xml-parser";

globalThis.onRequest = (ctx, flow) => {
  const auth = flow.layer.data.request.headers.find(h => h[0] === "authorization");
  if (auth) {
    const payload = jwtDecode(auth[1]);
    console.log("JWT subject:", payload.sub);
  }
};

// relay-core scripts build → dist/bundle.js(单文件 IIFE)
// 直接加载 bundle.js 即可

脚本环境变量

# 启动时允许脚本读取的环境变量
relay-core-cli run --script-env-allow API_TOKEN,DEBUG

可观测性

脚本执行指标自动上报到 Prometheus:

指标说明
script_hook_duration_us{hook}单次钩子执行耗时(直方图)
script_hook_invocations_total{hook}钩子累计调用次数
script_hook_errors_total{hook}钩子累计错误次数
script_fetch_total{target,status}fetch 调用计数
script_env_access_total{key}env 访问计数(不记录值)