APICatcher 指令碼功能使用指南

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' };
}
  • 你可以只定義其中一個,也可以兩個都定義
  • 指令碼按照優先順序和建立時間順序執行
  • 一旦某個指令碼返回非 passthrough 的結果,後續指令碼將不再執行

請求攔截函數 interceptRequest

當匹配的 HTTP 請求到達時,interceptRequest 函數會被呼叫。

參數:request 物件

屬性型別說明
methodstringHTTP 方法(GET, POST, PUT, DELETE 等)
urlstring完整 URL
hoststring主機名稱
portnumber連接埠號
pathstring請求路徑
schemestring協定(http / https)
headersobject請求標頭(key-value 物件)
bodystring請求主體字串
queryParamsobjectURL 查詢參數(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 函數會被呼叫。

參數

  • request:原始請求資訊(同上)
  • response:回應資訊
屬性型別說明
statusCodenumberHTTP 狀態碼
headersobject回應標頭(key-value 物件)
bodystring回應主體字串

返回值

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)

參數說明

參數型別必填說明
urlstring完整的請求 URL(http:// 或 https://)
bodystring請求主體字串(JSON 請使用 JSON.stringify()
headersobject請求標頭物件

返回值

所有方法返回相同結構的回應物件:

{
    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)

參數說明

參數型別說明
keystring儲存的鍵名
valueany儲存的值(將自動轉換為字串)

儲存說明

  • 所有指令碼共享同一儲存空間
  • 資料持久化在 App 的沙盒中(本地儲存),App 重啟後依然存在
  • 如需按 Host 隔離,請在 key 中加入 Host 資訊,例如:
// 手動按 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) — 深拷貝物件