ApiChatcher

Mobile HTTP/HTTPS packet capture and debugging tool

View My GitHub Profile

APICatcher 脚本功能使用指南

English

APICatcher 内置了基于 JavaScript 的脚本引擎,允许你编写自定义脚本来拦截和修改 HTTP/HTTPS 请求与响应。脚本功能强大且灵活,适合开发调试、接口测试、数据模拟等场景。


目录


快速开始

  1. 打开 APICatcher,进入「脚本」管理页面
  2. 点击「添加脚本」,系统会自动填充模板代码
  3. 设置脚本的 匹配条件(如 Host、Path 等),决定脚本对哪些请求生效
  4. 编写你的拦截逻辑
  5. 启用脚本后,开始抓包即可生效

脚本基本结构

每个脚本可以包含两个函数:

// 请求拦截(可选)
function interceptRequest(request) {
    // 你的逻辑
    return { action: 'passthrough' };
}

// 响应拦截(可选)
function interceptResponse(request, response) {
    // 你的逻辑
    return { action: 'passthrough' };
}

请求拦截函数 interceptRequest

当匹配的 HTTP 请求到达时,interceptRequest 函数会被调用。

参数:request 对象

属性 类型 说明
method string HTTP 方法(GET, POST, PUT, DELETE 等)
url string 完整 URL
host string 主机名
port number 端口号
path string 请求路径
scheme string 协议(http / https)
headers object 请求头(key-value 对象)
body string 请求体字符串
queryParams object URL 查询参数(key-value 对象)

返回值

返回一个对象,包含 action 字段来决定如何处理请求:

1. 放行请求(passthrough)

return { action: 'passthrough' };

不做任何修改,请求正常发送。

2. 修改请求(modify)

request.headers['Authorization'] = 'Bearer my-token';
request.body = JSON.stringify({ modified: true });
return { action: 'modify', request: request };

修改请求内容后继续发送。

3. 模拟响应(mock)

return {
    action: 'mock',
    response: {
        statusCode: 200,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ success: true, data: 'mocked' })
    }
};

不发送真实请求,直接返回模拟的响应。

4. 重定向(redirect)

return {
    action: 'redirect',
    url: 'https://new-api.example.com' + request.path,
    statusCode: 302  // 可选,默认 302
};

将请求重定向到另一个 URL。

5. 丢弃请求(drop)

return { action: 'drop' };

直接丢弃请求,不发送也不响应。


响应拦截函数 interceptResponse

当服务器返回响应后,interceptResponse 函数会被调用。

参数

属性 类型 说明
statusCode number HTTP 状态码
headers object 响应头(key-value 对象)
body string 响应体字符串

返回值

1. 放行响应(passthrough)

return { action: 'passthrough' };

2. 修改响应(modify)

var data = JSON.parse(response.body);
data.injected = true;
response.body = JSON.stringify(data);
return { action: 'modify', response: response };

3. 延迟响应(delay)

return {
    action: 'delay',
    response: response,
    delay: 3000  // 延迟 3000 毫秒
};

4. 丢弃响应(drop)

return { action: 'drop' };

内置 API

脚本环境中预置了以下全局对象,可在 interceptRequestinterceptResponse 中直接使用。

httpClient — HTTP 请求

在脚本中发起 HTTP/HTTPS 请求。调用是 同步 的,脚本会等待请求完成后继续执行。每个请求的耗时会自动记录到日志中,方便排查性能问题。

方法

// GET 请求
httpClient.get(url)
httpClient.get(url, headers)

// POST 请求
httpClient.post(url)
httpClient.post(url, body)
httpClient.post(url, body, headers)

// PUT 请求
httpClient.put(url)
httpClient.put(url, body)
httpClient.put(url, body, headers)

// DELETE 请求
httpClient.delete(url)
httpClient.delete(url, headers)

参数说明

参数 类型 必填 说明
url string 完整的请求 URL(http:// 或 https://)
body string 请求体字符串(JSON 请使用 JSON.stringify()
headers object 请求头对象

返回值

所有方法返回相同结构的响应对象:

{
    statusCode: 200,        // HTTP 状态码(请求失败时为 -1)
    headers: { ... },       // 响应头对象
    body: "...",            // 响应体字符串
    error: ""              // 错误信息(成功时为空字符串)
}

示例

// 简单 GET 请求
var resp = httpClient.get('https://api.example.com/status');
console.log('Status: ' + resp.statusCode);

// 带请求头的 GET 请求
var resp = httpClient.get('https://api.example.com/user', {
    'Authorization': 'Bearer my-token'
});

// POST JSON 数据
var resp = httpClient.post(
    'https://api.example.com/data',
    JSON.stringify({ name: 'test', value: 123 }),
    { 'Content-Type': 'application/json' }
);

// 检查请求是否成功
if (resp.error) {
    console.error('请求失败: ' + resp.error);
} else {
    var data = JSON.parse(resp.body);
    console.log('响应数据: ' + JSON.stringify(data));
}

自动耗时日志

每次 httpClient 调用完成后,引擎会自动输出日志:

[Script] httpClient.get https://api.example.com/status -> 200 (156ms)
[Script] httpClient.post https://api.example.com/data -> 201 (342ms)

你可以在 APICatcher 的日志面板中查看这些信息,帮助定位慢请求。

⚠️ 注意:httpClient 的调用是同步的,会阻塞脚本执行直到 HTTP 请求完成(超时时间 15 秒)。如果你在脚本中发起耗时的 HTTP 请求,可能会导致原始被拦截请求的响应延迟。请关注日志中的耗时数据。


localStore — 本地存储

提供简单的 Key-Value 本地持久化存储。数据存储在 App 的 UserDefaults 中,所有脚本共享同一存储空间。如果需要按 Host 或其他维度隔离数据,请在 key 中自行添加前缀(例如 request.host + '_token')。

方法

// 写入数据
localStore.write(key, value)

// 读取数据
localStore.read(key)     // 返回 string 或 null

// 删除数据
localStore.remove(key)

参数说明

参数 类型 说明
key string 存储的键名
value any 存储的值(将自动转换为字符串)

存储说明

// 手动按 host 隔离
var key = request.host + '_token';
localStore.write(key, 'my-token');

示例

// 基本读写
localStore.write('counter', '1');
var count = localStore.read('counter');   // '1'
console.log('Count: ' + count);

// 存储 JSON 对象
var config = { debug: true, maxRetry: 3 };
localStore.write('config', JSON.stringify(config));

// 读取 JSON 对象
var raw = localStore.read('config');
if (raw) {
    var config = JSON.parse(raw);
    console.log('Debug mode: ' + config.debug);
}

// 删除数据
localStore.remove('counter');
var v = localStore.read('counter');  // null

console — 日志输出

用于在脚本中输出日志,可在 API Catcher 的日志面板中查看。

console.log('普通日志');          // 信息级别
console.warn('警告日志');         // 警告级别
console.error('错误日志');        // 错误级别

支持多个参数,对象会自动 JSON 序列化:

console.log('请求信息:', request.method, request.url);
console.log('响应数据:', { status: response.statusCode, body: response.body });

实战示例

示例 1:自动注入认证 Token

拦截所有 api.myserver.com 的请求,自动添加 Authorization 头。Token 从登录接口获取并缓存到 localStore。

function interceptRequest(request) {
    // 只处理目标服务器
    if (!request.host.includes('api.myserver.com')) {
        return { action: 'passthrough' };
    }
    
    // 尝试从缓存读取 token
    var token = localStore.read('auth_token');
    var tokenExpiry = localStore.read('auth_token_expiry');
    
    // 检查 token 是否过期(使用时间戳,单位秒)
    var now = Math.floor(Date.now() / 1000);
    if (!token || !tokenExpiry || now > parseInt(tokenExpiry)) {
        // Token 不存在或已过期,重新登录获取
        console.log('Token expired, re-authenticating...');
        var loginResp = httpClient.post(
            'https://api.myserver.com/auth/login',
            JSON.stringify({ username: 'testuser', password: 'testpass' }),
            { 'Content-Type': 'application/json' }
        );
        
        if (loginResp.statusCode === 200) {
            var loginData = JSON.parse(loginResp.body);
            token = loginData.token;
            // 缓存 token,设置 1 小时过期
            localStore.write('auth_token', token);
            localStore.write('auth_token_expiry', String(now + 3600));
            console.log('New token cached successfully');
        } else {
            console.error('Login failed: ' + loginResp.statusCode);
            return { action: 'passthrough' };
        }
    }
    
    // 注入 Authorization 头
    request.headers['Authorization'] = 'Bearer ' + token;
    return { action: 'modify', request: request };
}

示例 2:接口数据转发/日志上报

将拦截到的请求和响应信息上报到你的日志分析服务器。

function interceptResponse(request, response) {
    // 只上报特定路径的接口
    if (!request.path.startsWith('/api/v2/')) {
        return { action: 'passthrough' };
    }
    
    // 收集请求和响应信息
    var logData = {
        timestamp: new Date().toISOString(),
        request: {
            method: request.method,
            url: request.url,
            headers: request.headers
        },
        response: {
            statusCode: response.statusCode,
            body: response.body
        }
    };
    
    // 上报到日志服务器(注意:请求是同步的,会阻塞当前响应的返回)
    var reportResp = httpClient.post(
        'https://log.myserver.com/api/collect',
        JSON.stringify(logData),
        { 'Content-Type': 'application/json' }
    );
    
    if (reportResp.error) {
        console.warn('Log report failed: ' + reportResp.error);
    }
    
    // 放行原始响应
    return { action: 'passthrough' };
}

示例 3:A/B 测试 — 动态修改响应

使用计数器实现简单的 A/B 分流,交替返回不同的模拟数据。

function interceptRequest(request) {
    if (request.path !== '/api/recommend') {
        return { action: 'passthrough' };
    }
    
    // 从 localStore 读取交替计数器
    var count = parseInt(localStore.read('ab_counter') || '0');
    count++;
    localStore.write('ab_counter', String(count));
    
    // 奇偶交替返回不同版本
    var version = (count % 2 === 0) ? 'A' : 'B';
    
    if (version === 'A') {
        return {
            action: 'mock',
            response: {
                statusCode: 200,
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    version: 'A',
                    items: [{ id: 1, name: '推荐方案A-1' }, { id: 2, name: '推荐方案A-2' }]
                })
            }
        };
    } else {
        return {
            action: 'mock',
            response: {
                statusCode: 200,
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    version: 'B',
                    items: [{ id: 3, name: '推荐方案B-1' }, { id: 4, name: '推荐方案B-2' }]
                })
            }
        };
    }
}

示例 4:请求限流保护

记录同一接口的请求频率,超过阈值时返回 429 错误。

function interceptRequest(request) {
    if (!request.path.startsWith('/api/')) {
        return { action: 'passthrough' };
    }
    
    var key = 'rate_' + request.path;
    var now = Math.floor(Date.now() / 1000);
    
    // 读取上次请求的时间和计数
    var rawData = localStore.read(key);
    var data = rawData ? JSON.parse(rawData) : { count: 0, windowStart: now };
    
    // 如果时间窗口超过 60 秒,重置计数器
    if (now - data.windowStart > 60) {
        data = { count: 0, windowStart: now };
    }
    
    data.count++;
    localStore.write(key, JSON.stringify(data));
    
    // 每分钟最多 10 次
    if (data.count > 10) {
        console.warn('Rate limit exceeded for ' + request.path);
        return {
            action: 'mock',
            response: {
                statusCode: 429,
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    error: 'Too Many Requests',
                    retryAfter: 60 - (now - data.windowStart)
                })
            }
        };
    }
    
    return { action: 'passthrough' };
}

注意事项

  1. httpClient 是同步调用:脚本中发起的 HTTP 请求会阻塞脚本执行,直到请求完成或超时(15 秒)。这会影响原始请求的处理速度。请关注日志中自动输出的请求耗时信息。

  2. localStore 共享存储:所有脚本共享同一存储空间。如果需要按 Host 隔离数据,请在 Key 中自行加入前缀。

  3. 脚本执行顺序:多个脚本按优先级排序执行。一旦某个脚本返回非 passthrough 的结果,后续脚本将跳过。

  4. 远程脚本:支持从 URL 加载脚本。启动抓包时会自动拉取远程脚本内容。如果拉取失败,该脚本会被跳过。

  5. JSON 处理:请求/响应的 body 都是字符串。需要处理 JSON 数据时,使用 JSON.parse() 解析、JSON.stringify() 序列化。

  6. 错误处理:建议在关键逻辑中使用 try-catch,并通过 console.error() 记录异常,避免脚本崩溃导致拦截链中断。

  7. 辅助函数:引擎预置了两个工具函数:

    • safeJsonParse(str) — 安全的 JSON 解析,失败返回 null
    • deepClone(obj) — 深拷贝对象