脚本

RelayCore 包含一个 Deno/V8 运行时,用于使用 JavaScript 或 TypeScript 动态修改请求和响应。

概述

脚本可以在各个阶段拦截和修改流量:

  • onRequest — 在请求被转发之前
  • onResponse — 在响应被接收之后
  • onWebSocket — 用于 WebSocket 消息

基本脚本

import { Flow, FlowAction } from "relay:script";

async function onRequest(flow: Flow): Promise<FlowAction> {
  console.log(`Request: ${flow.method} ${flow.url}`);

  // 添加自定义 header
  flow.request.headers["X-Script-Ran"] = "true";

  // 记录并继续
  return FlowAction.Continue;
}

async function onResponse(flow: Flow): Promise<FlowAction> {
  console.log(`Response: ${flow.status} for ${flow.url}`);

  // 为特定 URL 返回模拟响应
  if (flow.path === "/api/mocked") {
    return FlowAction.Mock({
      status: 200,
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ mocked: true }),
    });
  }

  return FlowAction.Continue;
}

Flow 对象

interface Flow {
  id: string;
  timestamp: Date;
  method: string;
  url: string;
  host: string;
  path: string;
  protocol: string;
  status?: number;

  request: {
    headers: Record<string, string>;
    body?: Uint8Array;
    bodyText?: string;
    bodyJson?: unknown;
  };

  response?: {
    headers: Record<string, string>;
    body?: Uint8Array;
    bodyText?: string;
    bodyJson?: unknown;
  };

  duration_ms: number;
  size: number;
}

FlowAction

enum FlowAction {
  Continue = "continue",      // 正常继续
  Drop = "drop",              // 丢弃流量
  Mock = "mock",              // 返回模拟响应
  Intercept = "intercept",    // 暂停以供检查
  Redirect = "redirect",      // 重定向到 URL
}

高级示例

import { Flow, FlowAction } from "relay:script";

// 用于测试的模拟 API 响应
const mocks = new Map([
  ["/api/users", { users: [{ id: 1, name: "Alice" }] }],
  ["/api/products", { products: [{ id: 1, name: "Widget" }] }],
]);

async function onRequest(flow: Flow): Promise<FlowAction> {
  // 如果缺少则注入 auth token
  if (flow.path.startsWith("/api/") && !flow.request.headers["authorization"]) {
    flow.request.headers["authorization"] = "Bearer test-token-123";
  }
  return FlowAction.Continue;
}

async function onResponse(flow: Flow): Promise<FlowAction> {
  if (flow.response && flow.response.bodyJson) {
    // 记录所有 API 响应
    console.log(`API Response for ${flow.path}:`, flow.response.bodyJson);

    // 向响应注入元数据
    if (Array.isArray(flow.response.bodyJson)) {
      flow.response.bodyJson._meta = {
        mocked: true,
        timestamp: new Date().toISOString(),
      };
    }
  }
  return FlowAction.Continue;
}

加载脚本

# 加载脚本
relay-core-cli scripts load ./path/to/script.ts

# 列出已加载的脚本
relay-core-cli scripts list

# 卸载脚本
relay-core-cli scripts unload my-script

脚本配置

# 在 config.toml 中
[scripting]
enabled = true
deno_dir = "~/.relay-core/deno"
auto_reload = true