脚本 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 |
onWebSocketMessage | WebSocket 帧到达 | (ctx, flow, message) => message | undefined |
onWebSocketStart | WebSocket 握手完成 | (ctx, flow) => void |
onWebSocketEnd | WebSocket 正常关闭 | (ctx, flow, code, reason) => void |
onWebSocketError | WebSocket 协议/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 访问计数(不记录值) |