ApiCatcher 스크립트 기능 사용 가이드
ApiCatcher에는 HTTP/HTTPS 요청 및 응답을 가로채고 수정할 수 있는 사용자 지정 스크립트를 작성할 수 있는 JavaScript 기반 스크립트 엔진이 내장되어 있습니다. 스크립트 기능은 강력하고 유연하며 개발 디버깅, API 테스트, 데이터 시뮬레이션 등의 시나리오에 적합합니다.
목차
- 빠른 시작
- 스크립트 기본 구조
- 요청 가로채기 함수 interceptRequest
- 응답 가로채기 함수 interceptResponse
- 내장 API
- 실전 예제
- 변수와 스코프
- 주의 사항
빠른 시작
- ApiCatcher를 열고 "스크립트" 관리 페이지로 들어갑니다.
- "스크립트 추가"를 클릭하면 시스템이 자동으로 템플릿 코드를 채웁니다.
- 스크립트의 일치 조건(Host, Path, Method)을 설정합니다. 일치 방식은 버전에 따라 다릅니다(3.17 이상: 정확 / 와일드카드, 그 이전: 포함 / 접두사). 자세한 내용은 범위.
- 가로채기 논리를 작성합니다.
- 스크립트를 활성화한 후 패킷 캡처를 시작하면 적용됩니다.
스크립트 기본 구조
각 스크립트에는 두 개의 함수를 포함할 수 있습니다.
// 요청 가로채기 (선택 사항)
function interceptRequest(request) {
// 사용자 논리
return { action: 'passthrough' };
}
// 응답 가로채기 (선택 사항)
function interceptResponse(request, response) {
// 사용자 논리
return { action: 'passthrough' };
}
- 둘 중 하나만 정의하거나 둘 다 정의할 수 있습니다.
- 스크립트는 우선순위 및 생성 시간 순으로 실행됩니다.
- 스크립트가
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 함수가 호출됩니다.
매개변수
- request: 원래 요청 정보 (위와 동일)
- response: 응답 정보
| 속성 | 유형 | 설명 |
|---|---|---|
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
스크립트 환경에는 다음 전역 객체가 내장되어 있으며 interceptRequest 및 interceptResponse에서 직접 사용할 수 있습니다.
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'
});
// 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)
매개변수 설명
| 매개변수 | 유형 | 설명 |
|---|---|---|
key | string | 저장할 키 이름 |
value | any | 저장할 값 (자동으로 문자열로 변환됨) |
저장소 설명
- 모든 스크립트는 동일한 저장 공간을 공유합니다.
- 데이터는 앱의 샌드박스(로컬 저장소)에 영구적으로 보존되며 앱을 다시 시작해도 유지됩니다.
- 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
crypto — 해싱 및 인코딩
해시, HMAC, Base64 인코딩, 암호학적으로 안전한 난수를 제공합니다. OAuth, JWT, 커스텀 API 서명 같은 요청 서명에 사용할 수 있습니다.
메서드
// 해시 (소문자 hex)
crypto.md5(s)
crypto.sha1(s)
crypto.sha256(s)
crypto.sha512(s)
// HMAC (소문자 hex)
crypto.hmac(s, key, algorithm) // algorithm 기본값 'sha256'. 'sha1' | 'sha256' | 'sha512'
crypto.hmacSha1(s, key)
crypto.hmacSha256(s, key)
crypto.hmacSha512(s, key)
// Base64
crypto.base64Encode(s) // UTF-8 문자열 → 표준 Base64
crypto.base64Decode(s) // 표준 Base64 → UTF-8 문자열 (잘못된 입력은 빈 문자열)
crypto.base64URLEncode(s) // UTF-8 문자열 → Base64URL (padding 없음)
crypto.base64URLEncodeHex(hex) // hex 다이제스트 → Base64URL (padding 없음. HMAC/해시 결과, JWT 서명 등)
// 안전한 난수
crypto.randomHex(len) // 기본 길이 16
crypto.randomString(len) // 기본 길이 16
crypto.uuid() // 소문자 UUID
메서드 설명
| 메서드 | 반환값 | 설명 |
|---|---|---|
crypto.md5(s) | hex string | MD5 해시 (서명/체크섬 용도) |
crypto.sha1(s) / sha256(s) / sha512(s) | hex string | SHA 해시 |
crypto.hmac(s, key, algorithm) | hex string | HMAC. algorithm 기본값은 sha256 |
crypto.hmacSha1(s, key) / hmacSha256 / hmacSha512 | hex string | 알고리즘이 고정된 HMAC |
crypto.base64Encode(s) / base64Decode(s) | string | 표준 Base64 인코딩/디코딩. 실패 시 빈 문자열 |
crypto.base64URLEncode(s) | string | UTF-8 문자열을 Base64URL로 변환 (padding 없음) |
crypto.base64URLEncodeHex(hex) | string | hex 다이제스트를 Base64URL로 변환 (padding 없음). JWT 서명 등 |
crypto.randomHex(len) / randomString(len) | string | 안전한 난수 문자열, 기본 길이 16 |
crypto.uuid() | string | 소문자 UUID |
예제
function interceptRequest(request) {
var secret = 'your-secret';
var ts = String(Math.floor(Date.now() / 1000));
var nonce = crypto.randomHex(16);
var stringToSign = request.method + '\n' + ts + '\n' + nonce;
request.headers['X-Timestamp'] = ts;
request.headers['X-Nonce'] = nonce;
request.headers['X-Signature'] = crypto.hmacSha256(stringToSign, secret);
return { action: 'modify', request: request };
}
HS256 JWT를 발급할 때 HMAC 결과는 hex입니다. base64URLEncodeHex로 다시 인코딩하세요:
function interceptRequest(request) {
var secret = localStore.read('jwt_secret');
var now = Math.floor(Date.now() / 1000);
var header = crypto.base64URLEncode(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
var payload = crypto.base64URLEncode(JSON.stringify({
iss: localStore.read('jwt_client_id'),
iat: now,
exp: now + 3600
}));
var signingInput = header + '.' + payload;
var jwt = signingInput + '.' + crypto.base64URLEncodeHex(crypto.hmacSha256(signingInput, secret));
request.headers['Authorization'] = 'Bearer ' + jwt;
return { action: 'modify', request: request };
}
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' };
}
변수와 스코프
최상위 변수와 헬퍼 함수
interceptRequest / interceptResponse 밖에서 선언한 변수와 헬퍼 함수는 해당 인터셉트 함수 안에서 바로 사용할 수 있습니다. 상수나 서명 함수 등은 진입 함수 밖에 두는 것이 일반적입니다:
var secret = 'my-key';
function sign(body) {
return crypto.hmacSha256(body, secret);
}
function interceptRequest(request) {
request.headers['X-Sign'] = sign(request.body || '');
return { action: 'modify', request: request };
}
요청 단계와 응답 단계
요청 가로채기와 응답 가로채기는 별도로 실행됩니다. interceptRequest에서 최상위 변수를 수정해도 같은 HTTP의 interceptResponse에는 유지되지 않습니다. 다음 요청이 매칭되면 최상위 변수는 다시 초기화됩니다:
var token = null;
function interceptRequest(request) {
token = request.headers['Authorization'];
return { action: 'passthrough' };
}
function interceptResponse(request, response) {
console.log(token); // ❌ 읽을 수 없음 — 여전히 null
return { action: 'passthrough' };
}
요청과 응답 사이에 데이터를 전달하거나, 여러 요청에 걸쳐 상태를 유지하려면 localStore를 사용합니다:
function interceptRequest(request) {
var auth = request.headers['Authorization'];
if (auth) {
localStore.write(request.host + '_auth', auth);
}
return { action: 'passthrough' };
}
function interceptResponse(request, response) {
var auth = localStore.read(request.host + '_auth');
console.log('auth from request: ' + auth);
return { action: 'passthrough' };
}
구문 제한
- 변수는
var/let/const로 선언하고,cache = {}같은 선언 없는 할당은 피할 것 - 최상위 변수는 요청 간 캐시로 사용할 수 없음
- 진입 함수 이름은
interceptRequest,interceptResponse이어야 하며, 하나만 정의해도 됨 async/await,fetch,import/export,setTimeout등 미지원. HTTP는httpClient사용
주의 사항
-
httpClient는 동기 호출입니다: 스크립트에서 시작된 HTTP 요청은 요청이 완료되거나 시간 초과(15초)될 때까지 스크립트 실행을 차단합니다. 이는 원래 요청의 처리 속도에 영향을 미칩니다. 로그에 자동으로 출력되는 요청 소요 시간 정보에 주의하십시오.
-
localStore 공유 저장소: 모든 스크립트는 동일한 저장 공간을 공유합니다. Host별로 데이터를 분리해야 하는 경우 Key에 직접 접두사를 추가하십시오.
-
스크립트 실행 순서: 여러 스크립트가 우선순위에 따라 순서대로 실행됩니다. 스크립트가
passthrough가 아닌 결과를 반환하면 후속 스크립트는 건너뜁니다. -
원격 스크립트: URL에서 스크립트 로드를 지원합니다. 패킷 캡처를 시작할 때 원격 스크립트 내용을 자동으로 가져옵니다. 가져오기에 실패하면 해당 스크립트는 건너뜁니다.
-
JSON 처리: 요청/응답 body는 모두 문자열입니다. JSON 데이터를 처리할 때
JSON.parse()를 사용하여 구문 분석하고JSON.stringify()를 사용하여 직렬화하십시오. -
오류 처리: 중요한 논리에서는
try-catch를 사용하고 스크립트 충돌로 인한 가로채기 체인 중단을 피하기 위해console.error()를 통해 예외를 기록하는 것이 좋습니다. -
보조 함수: 엔진에는 두 가지 유틸리티 함수가 내장되어 있습니다.
safeJsonParse(str)— 안전한 JSON 구문 분석, 실패 시 null 반환deepClone(obj)— 객체 깊은 복사