Mobile HTTP/HTTPS packet capture and debugging tool
APICatcher 内置了基于 JavaScript 的脚本引擎,允许你编写自定义脚本来拦截和修改 HTTP/HTTPS 请求与响应。脚本功能强大且灵活,适合开发调试、接口测试、数据模拟等场景。
每个脚本可以包含两个函数:
// 请求拦截(可选)
function interceptRequest(request) {
// 你的逻辑
return { action: 'passthrough' };
}
// 响应拦截(可选)
function interceptResponse(request, response) {
// 你的逻辑
return { action: 'passthrough' };
}
passthrough 的结果,后续脚本将不再执行当匹配的 HTTP 请求到达时,interceptRequest 函数会被调用。
| 属性 | 类型 | 说明 |
|---|---|---|
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 字段来决定如何处理请求:
return { action: 'passthrough' };
不做任何修改,请求正常发送。
request.headers['Authorization'] = 'Bearer my-token';
request.body = JSON.stringify({ modified: true });
return { action: 'modify', request: request };
修改请求内容后继续发送。
return {
action: 'mock',
response: {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ success: true, data: 'mocked' })
}
};
不发送真实请求,直接返回模拟的响应。
return {
action: 'redirect',
url: 'https://new-api.example.com' + request.path,
statusCode: 302 // 可选,默认 302
};
将请求重定向到另一个 URL。
return { action: 'drop' };
直接丢弃请求,不发送也不响应。
当服务器返回响应后,interceptResponse 函数会被调用。
| 属性 | 类型 | 说明 |
|---|---|---|
statusCode |
number | HTTP 状态码 |
headers |
object | 响应头(key-value 对象) |
body |
string | 响应体字符串 |
return { action: 'passthrough' };
var data = JSON.parse(response.body);
data.injected = true;
response.body = JSON.stringify(data);
return { action: 'modify', response: response };
return {
action: 'delay',
response: response,
delay: 3000 // 延迟 3000 毫秒
};
return { action: 'drop' };
脚本环境中预置了以下全局对象,可在 interceptRequest 和 interceptResponse 中直接使用。
在脚本中发起 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 请求,可能会导致原始被拦截请求的响应延迟。请关注日志中的耗时数据。
提供简单的 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
用于在脚本中输出日志,可在 API Catcher 的日志面板中查看。
console.log('普通日志'); // 信息级别
console.warn('警告日志'); // 警告级别
console.error('错误日志'); // 错误级别
支持多个参数,对象会自动 JSON 序列化:
console.log('请求信息:', request.method, request.url);
console.log('响应数据:', { status: response.statusCode, body: response.body });
拦截所有 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 };
}
将拦截到的请求和响应信息上报到你的日志分析服务器。
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' };
}
使用计数器实现简单的 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' }]
})
}
};
}
}
记录同一接口的请求频率,超过阈值时返回 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' };
}
httpClient 是同步调用:脚本中发起的 HTTP 请求会阻塞脚本执行,直到请求完成或超时(15 秒)。这会影响原始请求的处理速度。请关注日志中自动输出的请求耗时信息。
localStore 共享存储:所有脚本共享同一存储空间。如果需要按 Host 隔离数据,请在 Key 中自行加入前缀。
脚本执行顺序:多个脚本按优先级排序执行。一旦某个脚本返回非 passthrough 的结果,后续脚本将跳过。
远程脚本:支持从 URL 加载脚本。启动抓包时会自动拉取远程脚本内容。如果拉取失败,该脚本会被跳过。
JSON 处理:请求/响应的 body 都是字符串。需要处理 JSON 数据时,使用 JSON.parse() 解析、JSON.stringify() 序列化。
错误处理:建议在关键逻辑中使用 try-catch,并通过 console.error() 记录异常,避免脚本崩溃导致拦截链中断。
辅助函数:引擎预置了两个工具函数:
safeJsonParse(str) — 安全的 JSON 解析,失败返回 nulldeepClone(obj) — 深拷贝对象