Proxyman Scripting으로 WebSocket 메시지를 수정하는 방법
6분 읽기
이제 Proxyman에서 WebSocket 메시지를 실시간으로 변경할 수 있습니다 🎉
JavaScript 몇 줄이면 메시지를 수정하거나, 드롭하거나, 새 메시지를 보낼 수 있습니다. WS와 WSS를 모두 지원하며, 클라이언트에서 서버로 가는 메시지와 서버에서 클라이언트로 가는 메시지 양방향에서 동작합니다.
이 글에서는 다음 내용을 다룹니다:
- WebSocket 앱을 테스트할 때의 어려움.
- 새로 추가된 Modify WebSocket Messages 옵션의 기능.
- 직접 실행해 볼 수 있는 로컬 WebSocket 서버로 진행하는 단계별 데모.
- 바로 사용할 수 있는 WebSocket 템플릿과, 메시지가 바뀌지 않을 때 참고할 팁.
Modify WebSocket Messages는 Proxyman for macOS에 새로 추가된 기능입니다. 이 가이드를 따라 하려면 다운로드 페이지에서 최신 버전으로 업데이트하세요.
결과부터 보겠습니다. 서버는 실제 BTC 가격을 보내지만, 앱은 $1을 받습니다. 앱이 비밀번호를 보내면 Proxyman이 해당 메시지를 드롭하고 앱에 경고 메시지로 응답합니다:
1. 문제점
WebSocket은 채팅, 트레이딩, 게임, 실시간 대시보드 앱에서 실시간 데이터를 받는 데 사용됩니다. 이런 앱은 테스트하기가 까다롭습니다:
- 서버를 제어할 수 없습니다. 서버는 실제 데이터를 보냅니다. 가격이 0이거나 오류 메시지처럼 예외적인 상황을 재현하려면 서버 코드를 바꿔야 합니다.
- 재현하기 어려운 경우가 있습니다. 메시지 유실, 이상한 응답, 연결 끊김은 정작 필요할 때 좀처럼 발생하지 않습니다.
- 이전에는 Proxyman이 WebSocket 메시지를 읽기만 할 수 있었습니다. Scripting의
onWebsocket()함수로 메시지를 로그에 남길 수는 있었지만, 메시지를 바꿀 수는 없었습니다.
2. "Modify WebSocket Messages"란 무엇인가요?
Scripting 규칙에 새로 추가된 체크박스입니다. 이 옵션을 켜면 Proxyman은 onWebsocket() 함수가 반환될 때까지 각 WebSocket 메시지를 보류합니다. 함수가 무엇을 반환하느냐에 따라 결과가 달라집니다:
onWebsocket()의 반환 값 | 결과 |
|---|---|
return message | 변경 사항을 적용해 메시지를 전송합니다 |
return null | 메시지를 드롭합니다. 상대방은 이 메시지를 받지 못합니다 |
| 반환 없음 | 원본 메시지를 전송합니다 |
abort() | 양쪽 모두에서 WebSocket 연결을 닫습니다 |
sendToClient(data) / sendToServer(data) | 새 메시지를 전송합니다. String은 텍스트 메시지를, Uint8Array는 바이너리 메시지를 보냅니다 |
message 객체에는 다음 필드가 있습니다:
| 필드 | 값 |
|---|---|
message.from | "client" 또는 "server" |
message.type | "text", "binary", "ping", "pong", 또는 "close" |
message.data | 메시지의 텍스트입니다. 텍스트 메시지를 수정하려면 이 값을 변경하세요 |
message.rawData | 바이너리 메시지의 바이트로, Uint8Array 형식입니다 |
이 옵션은 기본적으로 꺼져 있습니다. 옵션이 꺼져 있으면 onWebsocket()은 이전과 같이 동작합니다. 메시지를 읽기만 하고 지연시키지 않습니다.
3. 단계별 가이드: 실시간 WebSocket 수정하기
영상에 나온 데모를 직접 만들어 보겠습니다. BTC 가격을 보내는 작은 서버와, Proxyman을 거쳐 이 서버와 대화하는 클라이언트를 준비합니다.
3.1 WebSocket 서버 시작하기
새 폴더를 만들고 ws 패키지를 설치합니다:
mkdir websocket-demo && cd websocket-demo
npm init -y
npm install ws
아래 코드를 server.js로 저장합니다. 1초마다 가격을 보내고, 받은 채팅 메시지를 그대로 되돌려 보냅니다:
// server.js: a WebSocket server on ws://localhost:3456
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 3456 });
server.on('connection', (socket) => {
// Send a BTC price every second
const timer = setInterval(() => {
const price = Number((67000 + Math.random() * 1000).toFixed(2));
socket.send(JSON.stringify({ type: 'price', symbol: 'BTC', price: price }));
}, 1000);
// Print what the client sends, then echo it back
socket.on('message', (data) => {
console.log('SERVER gets:', data.toString());
socket.send(JSON.stringify({ type: 'echo', text: JSON.parse(data).text }));
});
socket.on('close', () => clearInterval(timer));
});
console.log('WebSocket server on ws://localhost:3456');
터미널에서 실행합니다:
node server.js
3.2 Proxyman을 거쳐 클라이언트 연결하기
Node.js는 macOS 프록시 설정을 사용하지 않으므로, 클라이언트가 WebSocket 핸드셰이크를 Proxyman으로 직접 보냅니다. 아래 코드를 client.js로 저장합니다:
// client.js: connects to the server through Proxyman
const http = require('http');
const WebSocket = require('ws');
// Proxyman's port. You can see it in the Proxyman toolbar
const PROXYMAN_PORT = 9090;
// Sends the WebSocket handshake to Proxyman, like an app that uses an HTTP proxy
class ProxymanAgent extends http.Agent {
addRequest(request, options) {
request.path = 'http://' + options.host + ':' + options.port + request.path;
super.addRequest(request, { ...options, host: '127.0.0.1', port: PROXYMAN_PORT });
}
}
const socket = new WebSocket('ws://localhost:3456/prices', { agent: new ProxymanAgent() });
socket.on('open', () => {
setTimeout(() => socket.send(JSON.stringify({ type: 'chat', text: 'Hello' })), 2000);
setTimeout(() => socket.send(JSON.stringify({ type: 'chat', text: 'my password is hunter2' })), 4000);
});
socket.on('message', (data) => console.log('CLIENT gets:', data.toString()));
Proxyman을 연 다음, 두 번째 터미널에서 클라이언트를 실행합니다:
node client.js
Proxyman에 ws://localhost:3456/prices가 보입니다. Websocket 탭을 열면 메시지를 확인할 수 있습니다. 아직 아무것도 변경되지 않았습니다. Ctrl+C로 클라이언트를 중지하세요.
3.3 Scripting 규칙 만들기
- Proxyman에서
ws://localhost:3456/prices플로우를 우클릭한 다음 Tools → **Scripting...**을 선택합니다. Proxyman이 URL이 채워진 규칙을 만들어 줍니다. - Request는 체크된 상태로 두고, Modify WebSocket Messages를 체크합니다.
- 스크립트를 아래 코드로 바꾼 다음 Save & Activate(⌘S)를 누릅니다.

function onWebsocket(context, url, request, response, message) {
// Only change text messages. Binary, ping, pong, and close messages pass through
if (message.type !== "text") {
return message;
}
const json = JSON.parse(message.data);
// 1. Server → client: every BTC price becomes $1
if (message.from === "server" && json.type === "price") {
json.price = 1;
json.note = "edited by Proxyman";
message.data = JSON.stringify(json);
return message;
}
// 2. Client → server: drop a message with a password, and warn the client
if (message.from === "client" && json.text.includes("password")) {
sendToClient(JSON.stringify({ type: "warning", text: "Proxyman blocked a password" }));
return null;
}
// 3. Everything else: send it as it is
return message;
}
3.4 다시 실행하고 결과 확인하기
node client.js를 다시 실행하세요. Proxyman은 WebSocket이 연결될 때 이 옵션의 적용 여부를 정하므로, 규칙을 저장한 뒤에는 항상 다시 연결하세요.
이제 클라이언트는 "edited by Proxyman" 노트가 포함된 "price": 1을 받습니다. 비밀번호 메시지는 서버에 도달하지 않습니다. 서버 터미널에는 Hello만 출력되고, 클라이언트는 대신 스크립트가 보낸 경고를 받습니다.

Proxyman의 Websocket 탭에서는 수정된 가격, 스크립트가 보낸 경고, 드롭된 비밀번호 메시지를 확인할 수 있습니다.

4. 추가 예제
양방향으로 텍스트 바꾸기, 예를 들어 호스트 이름을 바꾸는 경우:
const FIND_REGEX = /staging\.example\.com/g;
const REPLACE_WITH = "api.example.com";
function onWebsocket(context, url, request, response, message) {
if (message.type === "text") {
message.data = message.data.replace(FIND_REGEX, REPLACE_WITH);
}
return message;
}
메시지 10개 뒤에 연결 끊김을 시뮬레이션해서 앱이 어떻게 재연결하는지 테스트할 수 있습니다. 함수 밖의 코드는 연결마다 한 번씩 실행되므로, 각 연결이 자신의 메시지 수를 따로 셉니다:
const CLOSE_AFTER_MESSAGES = 10;
let messageCount = 0;
function onWebsocket(context, url, request, response, message) {
messageCount += 1;
if (messageCount === CLOSE_AFTER_MESSAGES) {
console.log("Closing the WebSocket after " + messageCount + " messages");
abort();
}
return message;
}
바이너리 메시지를 변경하려면 새 바이트를 설정합니다:
function onWebsocket(context, url, request, response, message) {
if (message.type === "binary" && message.from === "server") {
message.rawData = new Uint8Array([1, 2, 3]);
}
return message;
}
5. WebSocket 템플릿으로 빠르게 시작하기
스크립트를 처음부터 작성할 필요는 없습니다. 스크립트 편집기에서 Templates를 클릭한 다음 "websocket"을 검색하세요:
- Log WebSocket Messages: 모든 메시지를 Console Log에 출력합니다. 메시지를 읽기만 합니다.
- Modify JSON WebSocket Messages: 서버가 보낸 JSON 메시지의 필드를 변경합니다.
- Replace Text in WebSocket Messages: 양방향으로 텍스트를 찾아 바꿉니다.
- Drop WebSocket Messages: 정규식과 일치하는 메시지를 드롭합니다(예: 분석 이벤트).
- Reply to a WebSocket Message:
sendToClient()로 클라이언트의 메시지에 응답합니다. 서버는 이 메시지를 받지 못합니다. - Simulate a WebSocket Disconnect: 몇 개의 메시지를 받은 뒤 WebSocket 연결을 닫습니다.
Replace를 클릭하면 템플릿을 사용할 수 있습니다. Proxyman은 필요한 체크박스(Modify WebSocket Messages 포함)도 함께 설정합니다.

6. 문제와 해결 방법
메시지가 바뀌지 않아요
- Modify WebSocket Messages가 체크되어 있는지 확인한 다음 Save & Activate(⌘S)를 누르세요.
- WebSocket을 다시 연결하세요. 이 옵션은 메시지마다가 아니라 WebSocket이 연결될 때 읽힙니다.
- Request 또는 Response를 체크된 상태로 두세요. 둘 다 체크되지 않은 규칙은
onWebsocket()을 포함해 아예 실행되지 않습니다. - 스크립트 편집기에서 Console Log를 여세요. 스크립트에서 오류가 발생하면 Proxyman은 원본 메시지를 보내고 오류를 그곳에 출력합니다.
Proxyman에서 WebSocket이 보이지 않아요
wss://의 경우, HTTPS와 마찬가지로 해당 도메인에 대해 SSL Proxying을 활성화하세요.- 많은 앱과 라이브러리는
localhost에 대해 프록시를 건너뜁니다.client.js처럼 트래픽을 Proxyman으로 보내거나, 사용 중인 플랫폼의 WebSocket 문서를 따르세요.
알아두면 좋은 점
- 규칙과 일치하는 WebSocket만 스크립트를 기다립니다. 다른 트래픽은 계속 흐릅니다.
- 16 MB보다 큰 메시지는 스크립트를 거치지 않고 그대로 전송됩니다.
- AI 에이전트를 사용한다면 Proxyman MCP로 같은 규칙을 만들 수 있습니다.
create_scripting_rule에modify_websocket_messages: true를 전달하면 됩니다.
더 읽어보기
