APICatcher 스크립트 기능 사용 가이드

APICatcher에는 HTTP/HTTPS 요청 및 응답을 가로채고 수정할 수 있는 사용자 지정 스크립트를 작성할 수 있는 JavaScript 기반 스크립트 엔진이 내장되어 있습니다. 스크립트 기능은 강력하고 유연하며 개발 디버깅, API 테스트, 데이터 시뮬레이션 등의 시나리오에 적합합니다.


목차


빠른 시작

  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'
});

// 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 헤더를 추가합니다. 토큰은 로그인 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));
    
    // 분당 최대 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) — 객체 깊은 복사