Proxyman Scripting으로 WebSocket 메시지를 수정하는 방법

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 규칙 만들기

  1. Proxyman에서 ws://localhost:3456/prices 플로우를 우클릭한 다음 Tools → **Scripting...**을 선택합니다. Proxyman이 URL이 채워진 규칙을 만들어 줍니다.
  2. Request는 체크된 상태로 두고, Modify WebSocket Messages를 체크합니다.
  3. 스크립트를 아래 코드로 바꾼 다음 Save & Activate(⌘S)를 누릅니다.
Modify WebSocket Messages가 체크되고 onWebsocket() 스크립트가 있는 Proxyman 스크립트 편집기
Modify WebSocket Messages가 체크되고 onWebsocket() 스크립트가 있는 Proxyman 스크립트 편집기
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 탭
왼쪽: 클라이언트와 서버가 실제로 주고받는 데이터. 오른쪽: 수정된 가격이 표시된 Proxyman의 Websocket 탭

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

ws://localhost:3456/prices 플로우와 수정된 WebSocket 메시지가 표시된 Proxyman 메인 창
ws://localhost:3456/prices 플로우와 수정된 WebSocket 메시지가 표시된 Proxyman 메인 창

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 포함)도 함께 설정합니다.

WebSocket 템플릿으로 필터링된 Proxyman의 Script Templates 시트
WebSocket 템플릿으로 필터링된 Proxyman의 Script Templates 시트

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를 전달하면 됩니다.

더 읽어보기

Noah Tran
Noah Tran

서버를 건드리지 않고 WebSocket 메시지 바꾸기

Scripting 규칙에서 Modify WebSocket Messages를 켜고, 앱이 처리해야 하는 모든 메시지를 테스트할 수 있습니다.

macOS용 Proxyman 받기