APICatcher スクリプト機能使用ガイド

APICatcherには、HTTP/HTTPSの要求と応答を傍受および変更するためのカスタムスクリプトを記述できるJavaScriptベースのスクリプトエンジンが組み込まれています。スクリプト機能は強力かつ柔軟で、開発のデバッグ、APIテスト、データのモックなどのシナリオに適しています。


目次


クイックスタート

  1. APICatcherを開き、「スクリプト」管理ページに入ります。
  2. 「スクリプトの追加」をクリックすると、システムが自動的にテンプレートコードを入力します。
  3. スクリプトの マッチングルール(Host、Pathなど)を設定して、スクリプトがどのリクエストに有効になるかを決定します。
  4. 傍受ロジックを記述します。
  5. スクリプトを有効にした後、パケットキャプチャを開始すると有効になります。

スクリプトの基本構造

各スクリプトには2つの関数を含めることができます:

// リクエスト傍受(オプション)
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'
});

// JSONデータのPOST
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型のローカル永続ストレージを提供します。データはアプリのUserDefaultsに保存され、すべてのスクリプトが同じストレージスペースを共有します。Hostやその他の基準でデータを分離する必要がある場合は、キーに自分でプレフィックスを追加してください(例:request.host + '_token')。

メソッド

// データの書き込み
localStore.write(key, value)

// データの読み取り
localStore.read(key)     // string または null を返します

// データの削除
localStore.remove(key)

パラメータの説明

パラメータ説明
keystring保存するキー名
valueany保存する値(自動的に文字列に変換されます)

ストレージの説明

  • すべてのスクリプトは同じストレージスペースを共有します。
  • データはアプリのサンドボックス(ローカルストレージ)に永続化され、アプリの再起動後も保持されます。
  • Hostで分離する必要がある場合は、以下のようにキーに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:認証トークンの自動注入

api.myserver.comへのすべてのリクエストを傍受し、自動的にAuthorizationヘッダーを追加します。TokenはログインAPIから取得され、localStoreにキャッシュされます。

function interceptRequest(request) {
    // ターゲットサーバーのみ処理
    if (!request.host.includes('api.myserver.com')) {
        return { action: 'passthrough' };
    }
    
    // キャッシュからトークンの読み取りを試行
    var token = localStore.read('auth_token');
    var tokenExpiry = localStore.read('auth_token_expiry');
    
    // トークンが期限切れかどうかを確認(タイムスタンプ使用、単位は秒)
    var now = Math.floor(Date.now() / 1000);
    if (!token || !tokenExpiry || now > parseInt(tokenExpiry)) {
        // トークンが存在しないか期限切れのため、再ログインして取得
        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;
            // トークンをキャッシュし、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:APIデータの転送/ログレポート

傍受したリクエストとレスポンスの情報を、あなたのログ分析サーバーにレポートします。

function interceptResponse(request, response) {
    // 特定のパスのAPIのみレポート
    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:リクエストのレート制限保護

同じAPIの要求頻度を記録し、しきい値を超えた場合に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));
    
    // 1分間に最大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. 補助関数:エンジンには2つのユーティリティ関数があらかじめ用意されています:

    • safeJsonParse(str) — 安全なJSON解析、失敗時はnullを返します
    • deepClone(obj) — オブジェクトのディープコピー