# Scripting

> Proxyman Scripting 도구로 JavaScript를 작성해 HTTP 요청과 응답을 수정하고, Mock API를 만들고, onWebsocket으로 WebSocket 메시지를 로깅할 수 있습니다.

## 1. 무엇인가요? {#1-whats-it}

Proxyman에는 Scripting 도구가 있습니다. JavaScript 코드를 작성해 요청과 응답을 유연하게 변경할 수 있습니다.

## 2. 장점 {#2-benefits}

* JS 코드로 Map Local / Map Remote / Breakpoint를 구현할 수 있습니다. **100배 빠릅니다**
* Domain, Host, Scheme, Port, Path, HTTP Method, HTTP Headers, Query, Body(Encoded-Form, JSON, plain-text) 등 **요청 내용**을 변경할 수 있습니다.
* `onWebsocket`으로 WebSocket 메시지를 로깅하고 확인할 수 있습니다(**macOS, Windows, Linux용 Proxyman 26.1.0 이상**).
* `onWebsocket`으로 [WebSocket 메시지를 편집, 드롭 또는 전송](https://proxyman.com/docs/ko/scripting/modify-websocket-messages)할 수 있습니다(**macOS용 Proxyman 26.2.0 이상, Windows/Linux용 Proxyman 26.1.0 이상**).
* HTTP Status Code, HTTP Headers, Body(JSON, Encoded-Form, plain-text, binary 등) 등 **응답 내용**을 변경할 수 있습니다.
* Hashing, Encode/Decode, JSON-Text transformer, Beautify 등 일반적인 작업에 쓸 수 있는 내장 애드온과 라이브러리가 많이 있습니다.
* **직접 만든 JS 애드온이나 라이브러리를 작성할 수 있습니다**
* Charles Proxy의 Rewrite GUI 도구를 대체하도록 설계되었습니다.
* [Shared State 또는 Environment Variables](https://proxyman.com/docs/ko/scripting/environment-variables)를 사용해 현재 세션의 스크립트 간에 상태를 공유할 수 있습니다.

![POST 규칙이 있고 Request와 Response가 체크되어 있으며 Run as Mock API가 설정된 Proxyman Script Editor, onRequest 및 onResponse 코드 포함](https://proxyman.com/docs/assets/proxyman-script-editor.jpg)

_Scripting 도구_

- [스니펫 코드](https://proxyman.com/docs/ko/scripting/snippet-code)

## 3. 사용 방법 {#3-how-to-use-it}

다음 방법으로 Scripting 도구를 열 수 있습니다:

* **Scripting** 메뉴 → **Script List…** (⌥⌘I)
* 플로우를 우클릭 → **Tools** → **Scripting…**

[스니펫 코드](https://proxyman.com/docs/ko/scripting/snippet-code) 페이지에서 Scripting 도구용 JS 스니펫 모음을 확인하세요.

## 4. GraphQL 요청에서 Scripting 사용하기 {#4-scripting-with-graphql-requests}

Proxyman 2.27.0 이상부터 Scripting 도구는 QueryName으로 GraphQL 요청을 매칭할 수 있습니다. 다음 GraphQL 문서를 참고하세요.

- [GraphQL 요청 디버깅](https://proxyman.com/docs/ko/advanced-features/graphql)

## 4.1 WebSocket에서 Scripting 사용하기 {#41-scripting-with-websocket}

macOS용 Proxyman 6.2.0부터는 `onRequest`와 `onResponse`를 사용해 WebSocket 요청 URL과 핸드셰이크 헤더를 수정할 수 있습니다. 자세한 내용은 [WebSocket URL 및 헤더 변경](https://proxyman.com/docs/ko/scripting/snippet-code#change-websocket-url-requestsresponse-header)을 참고하세요.

### onWebsocket으로 메시지 로깅 {#log-messages-with-onwebsocket}

> **경고:** `onWebsocket`은 **macOS, Windows, Linux용 Proxyman 26.1.0 이상**에서 사용할 수 있습니다. iOS에서는 사용할 수 없습니다.

`onWebsocket(context, url, request, response, message)`를 사용하면 클라이언트와 서버에서 캡처한 메시지를 확인하고 로깅할 수 있습니다:

```javascript
function onWebsocket(context, url, request, response, message) {
  console.log(`[${message.from}] ${message.type}: ${message.data}`);
}
```

기본적으로 이 훅은 **읽기 전용**입니다. Proxyman은 스크립트를 실행하기 전에 메시지를 먼저 전달합니다. 인자를 변경하거나 값을 반환해도 트래픽이나 캡처된 메시지는 바뀌지 않습니다. return 문은 필요하지 않습니다.

메시지를 편집, 드롭 또는 전송하려면 규칙에서 **Modify WebSocket Messages**를 체크하세요(macOS용 Proxyman 26.2.0 이상, Windows/Linux용 Proxyman 26.1.0 이상). 자세한 내용은 [Modify WebSocket Messages](https://proxyman.com/docs/ko/scripting/modify-websocket-messages)를 참고하세요.

**사용 방법**

1. WebSocket 연결을 캡처합니다. `wss://` 연결이라면 Proxyman이 메시지를 읽을 수 있도록 해당 호스트의 SSL Proxying을 켜세요.
2. 캡처한 플로우를 우클릭하고 **Tools → Scripting**을 선택합니다. 생성된 스크립트에는 `onResponse` 아래에 `onWebsocket`이 포함됩니다.
3. 로깅 코드를 추가한 다음 **Save & Activate**를 클릭합니다.
4. WebSocket을 다시 연결해 메시지를 보내고, 스크립트의 **Console Log**를 확인합니다.

규칙은 연결이 열릴 때 활성화되어 있어야 합니다. 규칙을 만든 후이거나, 연결이 열렸을 때는 없었던 `onWebsocket`을 스크립트에 추가한 후에는 WebSocket을 다시 연결하세요. 일반 새 스크립트 템플릿에는 이 훅이 포함되어 있지 않으므로 직접 추가해야 합니다.

**인자**

다섯 인자는 반드시 이 순서대로 유지하세요. `context`와 `url`은 플로우와 매칭 규칙을 나타냅니다. `request`와 `response`에는 `onRequest`, `onResponse`, 또는 Breakpoint에서 적용한 변경 사항까지 포함된 최종 핸드셰이크가 들어 있습니다. `message`에는 다음 정보가 포함됩니다:

| 필드 | 값 |
| --- | --- |
| `message.type` | `"text"`, `"binary"`, `"ping"`, `"pong"`, 또는 `"close"`입니다. |
| `message.from` | `"client"` 또는 `"server"`입니다. |
| `message.data` | 항상 문자열입니다. 바이너리 데이터는 가능한 경우 UTF-8 텍스트로 표시되며, 그렇지 않으면 `Binary (length=N)`으로 표시됩니다. 선택 사항인 WebSocket 탭 디코더는 적용되지 않습니다. |
| `message.rawData` | 복사된 `Uint8Array`이며, 바이너리 메시지에만 사용할 수 있습니다. 명시적으로 로깅해야 합니다. `message`를 열거할 때는 포함되지 않습니다. |
| `message.closeCode` | 숫자이며, close 메시지에만 사용할 수 있습니다(예: `1000`). |

**참고 사항**

* 분할된 텍스트 및 바이너리 프레임은 훅이 실행되기 전에 재조립됩니다. 연속 프레임마다 콜백이 따로 호출되지 않고, 완전한 메시지 하나를 받습니다.
* Ping/Pong 페이로드와 close 사유는 노출되지 않습니다.
* 동기 훅과 `async` 훅을 모두 지원하며, `console.log`, `require`, `sharedState`, `$http` 같은 헬퍼도 사용할 수 있습니다.
* 기존 훅의 수정 사항과 규칙/폴더/도구의 활성화 상태는 메시지가 도착할 때 최대 1초에 한 번씩 확인됩니다. 이미 대기열에 들어간 호출은 완료될 수 있습니다.
* Console Log에는 최근 로그 항목 200개만 보관됩니다. 스크립트가 메시지를 따라가지 못하면 트래픽은 계속 처리되는 동안 경고와 함께 훅 호출이 건너뛰어질 수 있습니다. Console Log는 디버깅용이며, 전체 트래픽을 보관하는 용도가 아닙니다.

텍스트, JSON, 바이너리, ping/pong, close 예제는 [WebSocket 메시지 스니펫](https://proxyman.com/docs/ko/scripting/snippet-code#log-websocket-messages-with-onwebsocket)을 참고하세요.

## 5. 예제 {#5-examples}

다음 가이드는 JS 코드로 요청 도메인을 프로덕션에서 localhost로 바꾸고 응답 본문을 변경하는 방법을 보여 줍니다.

1. 스크립트를 만들기 전에 이 도메인의 SSL Proxying을 켰는지 확인하세요.
2. Scripting 도구를 열고 새 Script Entry(⌘N)를 만듭니다. 요청을 우클릭하고 **Tools → Scripting…**을 선택해도 됩니다. 그러면 Proxyman이 스크립트를 대신 만들어 줍니다.
3. 이름을 입력하고 Matching Rule을 정의합니다.
4. 예: **Name**=Localhost 엔드포인트 테스트, **URL**=https://proxyman.io
5. **Run Script on**에서 **Request**와 **Response** 체크박스를 켭니다.
6. `onRequest` 함수에 JS 코드를 작성하기 시작합니다.

**onRequest(context, url, request) {}**

```javascript
// Import UUID addons
const { uuidv4 } = require("@addons/UUID.js");

function onRequest(context, url, request) {
  // print log
  console.log(request);

  // Change Production domain -> Localhost
  request.method = "GET";
  request.scheme = "http";
  request.host = "localhost";
  request.port = 8000;
  
  // Add new header
  request.headers["X-New-Header"] = "Hello From Scripting feature";
  request.headers["UUID"] = uuidv4(); // generate random UUIDv4
  delete request.headers["Key-Need-Delete"];

  // Update or Add a new Query
  request.queries["name"] = "Proxyman";

  // Update Body
  var body = request.body;
  body["name"] = "Proxyman";
  request.body = body;
  
  // Or Map the body with a local file (Proxyman 2.25.0+)
  // request.bodyFilePath = "~/Desktop/mockdata.json";
  
  // Done
  return request;
}
```

## onRequest() 객체 형식 {#onrequest-object-format}

`context`, `url`, `request` 객체는 다음과 같이 정의됩니다:

```javascript
// context (readonly)
{
    "scriptName": "<String> Your Script Name",
    "matchingRule": "<String> Your Matching Rule",
    "matchingMethod": "<String> Method",
    "isEnableOnRequest": "Bool",
    "isEnableOnResponse": "Bool",
    "filePath": "<String> Script path",
    "flow": { // Available from 2.16.0+
        "serverPort": "443",
        "serverIpAddress": "104.18.230.83",
        "clientIpAddress": "192.168.0.102",
        "remoteDeviceName": "iPhone XR",
        "remoteDeviceIP": "192.168.0.102",
        "id": "51",
        "clientPath": null,
        "clientPort": "51494",
        "clientName": null,
        "mapRemoteOriginalURL": "<String> Original URL before Map Remote modification (nullable)"
    },
}

// url (readonly)
url: String // => Present the full URL

// request
{
    "method": "<String> HTTP Method. Accept string method. Ex: GET, POST, ...",
    "scheme": "<String> Accept http or https",
    "host": "<String> Host of the request. Ex: api.proxyman.io, localhost, ...",
    "path": "<String>: Path of the URL. Ex: /v1/data",
    "port": "<Int> Accept int port number. Ex: 443, 8080, ..",
    "queries": "<[String: Any]> A JS Object (Dictionary) contains key values of the query",
    "headers": "<[String: Any]> A JS Object (Dictionary) contains key values of the header",
    "body": "Depend on the Content-Type header. It might be a dictionary for JSON and form, Plain Text or Uint8Array",
    "bodyFilePath": "<String><Optional> Set a body with a local file. See example in Snippet Code Page"
    "rawBody": "<Readonly>: A raw body String or Uint8Array",
    "preserveHostHeader": "<Bool> Preserve the Host",
    "isURLEncoding": "<Bool> Determine if Proxyman will perform URLEncoding when constructing the final URL. Default is True",
    "upstream": "<Object><Optional> Upstream TLS ALPN and HTTP/2 settings. Proxyman v26.0.0 or later for macOS."
}

```

> **노트:** `rawBody`를 제외한 요청 객체의 모든 값을 변경할 수 있습니다.

`onRequest`에서 `request.upstream`을 설정하세요. 예제와 지원되는 필드는 [Upstream ALPN 및 HTTP/2 설정](https://proxyman.com/docs/ko/scripting/snippet-code#upstream-alpn-and-http2-settings)을 참고하세요.

> **노트:** Content-Type 헤더가 잘못되어 **body** 형식이 올바르지 않다면 **rawBody**를 사용해 문자열을 직접 파싱하세요.

`request.body`의 타입은 Content-Type 헤더에 따라 달라집니다.

| Content-Type 헤더 | request.body |
| --- | --- |
| application/json 또는 JSON 계열 | JavaScript Object |
| application/x-www-form-urlencoded | JavaScript Object |
| plain-text 또는 텍스트 기반 Content-Type. 예: application/js, text/css, text/html 등 | String |
| 그 밖의 경우: 바이너리 데이터(application/zip, application/octet-stream) | Uint8Array |

자주 사용하는 JS 코드는 스니펫 코드 페이지에서 확인하세요:

- [스니펫 코드](https://proxyman.com/docs/ko/scripting/snippet-code)

7\. `onResponse` 함수에 코드 작성을 시작합니다

```javascript
function onResponse(context, url, request, response) {
  console.log(response);

  // Update or Add a new header
  response.headers["Content-Type"] = "application/json";

  // Update status Code
  response.statusCode = 500;

  // Update Body
  var body = response.body;
  body["new-key"] = "Proxyman";
  response.body = body;

  // Or Map the body with a local file (Proxyman 2.25.0+)
  // response.bodyFilePath = "~/Desktop/mockdata.json";
  
  // Done
  return response;
}
```

## onResponse() 객체 형식 {#onresponse-objects-format}

`context`, `url`, `request`, `response` 객체는 다음과 같이 정의됩니다:

```javascript
// context (readonly): Same with onRequest

// url (readonly): Same with onRequest

// request (readonly): Same with onRequest

// response
{
    "statusCode": "<Int> Status Code. Ex: 200, 400, 404,...",
    "httpVersion": "<String><Readonly> The HTTP Version",
    "statusPhrase": "<String><Readonly> HTTP Status Phrase. Ex Not Found, OK, ...",
    "headers": "<[String: Any]> A JS Object (Dictionary) contains key values of the header",
    "body": "Depend on the Content-Type header. It might be a dictionary for JSON and form, PlainText or Uint8Array",
    "rawBody": "<Readonly>: A raw body String or Uint8Array",
    "bodyFilePath": "<String><Optional> Set a body with a local file. See example in Snippet Code Page"
}
```

> **노트:** 응답 객체에서 `statusCode`, `headers`, `body`를 변경할 수 있습니다.

> **노트:** Content-Type 헤더가 잘못되어 **body** 형식이 올바르지 않다면 **rawBody**를 사용해 문자열을 직접 파싱하세요.

`response.body`의 타입은 Content-Type 헤더에 따라 달라집니다.

| Content-Type 헤더 | response.body |
| --- | --- |
| application/json 또는 JSON 계열 | JavaScript Object |
| application/x-www-form-urlencoded | JavaScript Object |
| plain-text 또는 텍스트 기반 Content-Type. 예: application/js, text/css, text/html 등 | String |
| 그 밖의 경우(application/zip, application/octet-stream) | Uint8Array |

> **노트:** `onRequest`와 `onResponse` 함수에서 `request`와 `response` 객체를 반드시 반환해야 합니다.

## 6. 내장 애드온 및 라이브러리 {#6-built-in-addons-and-libraries}

Proxyman은 Hashing, Encode/Decode 등 일반적인 작업을 위한 애드온과 라이브러리를 많이 제공합니다.

- [애드온](https://proxyman.com/docs/ko/scripting/addons)

- [내장 JS 라이브러리](https://proxyman.com/docs/ko/scripting/built-in-js-libraries)

- [스니펫 코드](https://proxyman.com/docs/ko/scripting/snippet-code)

## 7. JS 오류 디버깅 {#7-debugging-js-error}

문법 오류나 잘못된 코드 때문에 JavaScript 오류가 발생할 수 있습니다. 콘솔의 오류 메시지를 읽거나 `console.log()`를 사용해 디버깅할 수 있습니다.

## 8. Scripting을 Mock API로 사용하기 {#8-use-scripting-as-a-mock-api}

Proxyman 2.32.0부터 Scripting 도구를 Mock API로 사용할 수 있습니다. 요청은 서버에 전달되지 않으며, 응답 본문은 직접 정의합니다. 동작 방식은 Map Local과 같습니다.

실제 REST API가 아직 준비되지 않았을 때 유용합니다. 로컬에서 API를 정의하고 테스트할 수 있습니다.

Mock API를 활성화하려면:

1. Scripting 도구를 열고 스크립트를 선택합니다.
2. Script Editor에서 **Request**와 **Response** 체크박스 옆에 있는 **Run as Mock API** 체크박스를 켭니다(위의 Script Editor 스크린샷 참고).

그런 다음 평소처럼 Response를 정의하면 됩니다:

```javascript
function onResponse(context, url, request, response) {

  // Init new body
  var body = {};
  body["new-key"] = "Proxyman";
  response.body = body;

  // Or map from a file
  // response.bodyFilePath = "~/Desktop/myfile.json"
  
  // Done
  return response;
}
```

## 9. 참고 사항 {#9-notes}

* `onRequest`와 `onResponse` 함수에서 `request`와 `response` 객체를 반드시 반환해야 합니다.
* **Proxyman 4.16.0 이상**: Proxyman은 이제 바이너리 데이터를 **Uint8Array**로 변환합니다.
* Proxyman 4.15.0 이하: JavaScript에는 Data 타입이 없으므로, 바이너리 본문은 JavaScript에서 **Base64 인코딩 문자열**로 변환됩니다. Uint8Array, blob 또는 ArrayBuffer를 본문으로 전달하려면 먼저 **Base64 인코딩 문자열**로 변환하세요.

## 더 알아보기 {#learn-more}

* [GraphQL 요청에 로컬 응답을 매핑하는 방법](https://proxyman.com/posts/2021-01-24-How-To-Map_Response_For_GraphQL_Requests)
* [CORS 정책을 우회하는 방법](https://proxyman.com/posts/2020-08-31-How-to-by-pass-CORS-policy)
