WebSocket은 일반 HTTP 요청이 아닙니다. 초기 "핸드셰이크" 후 연결은 열려 있으며 데이터는 양방향으로 지속적으로 흐릅니다. 이러한 이유로 표준 HTTP 프록시는 종종 WS 연결을 중단시키거나 전혀 처리하지 못합니다. 이 기사에서는 WebSocket 트래픽을 올바르게 프록시하는 방법, 어떤 프록시가 적합한지, 어떻게 설정하는지, 그리고 자주 발생하는 오류를 살펴보겠습니다.
WebSocket이 작동하는 방식과 프록시가 복잡한 이유
문제를 이해하려면 메커니즘을 파악해야 합니다. WebSocket 연결은 일반 HTTP 요청으로 시작됩니다. 클라이언트는 Upgrade: websocket 및 Connection: Upgrade 헤더를 보냅니다. 서버는 101 Switching Protocols 코드로 응답하며, 이 시점부터 연결은 더 이상 HTTP가 아닙니다. 이는 지속적인 양방향 채널로 변환되어 데이터가 프레임으로 전송됩니다.
요청과 응답만 전달할 수 있는 표준 HTTP/1.1 프록시는 응답 101 이후 연결을 어떻게 처리해야 할지 모르는 문제가 발생합니다. 많은 프록시 서버는 이 시점에서 연결을 닫거나 502 Bad Gateway 오류를 반환합니다. 다른 프록시는 연결을 유지하지만 WebSocket 프레임을 올바르게 전달하지 못해 데이터 손실이나 왜곡이 발생합니다.
WebSocket과 일반 HTTP의 주요 차이점은 프록시 관점에서 다음과 같습니다:
| 매개변수 | HTTP | WebSocket |
|---|---|---|
| 연결 유형 | 요청 → 응답 → 종료 | 지속적, 양방향 |
| 수명 | 초 (하나의 요청) | 분, 시간, 일 |
| 데이터 발신자 | 클라이언트만 | 클라이언트 및 서버 |
| 핸드셰이크 후 프로토콜 | HTTP | 자체 프로토콜 (RFC 6455) |
| 포트 | 80, 443 | 80 (WS), 443 (WSS) |
핸드셰이크 후 프로토콜 변경으로 인해 대부분의 간단한 프록시 솔루션이 WebSocket을 처리하지 못합니다. 명시적으로 터널링을 지원하는 프록시를 사용하거나 더 낮은 수준에서 작동하며 트래픽 내용을 분석하지 않는 SOCKS5 프로토콜을 사용해야 합니다.
WebSocket을 지원하는 프록시 유형
모든 프록시가 WebSocket에 유용한 것은 아닙니다. 각 유형을 살펴보겠습니다:
| 프록시 유형 | WS 지원 | 메커니즘 | 복잡성 |
|---|---|---|---|
| HTTP 프록시 | ⚠️ 부분 지원 | CONNECT 터널링을 통해 | 중간 |
| HTTPS 프록시 | ✅ 예 | CONNECT + TLS | 중간 |
| SOCKS4 | ⚠️ 제한적 | 인증 없는 TCP 터널 | 낮음 |
| SOCKS5 | ✅ 완전 지원 | 투명한 TCP/UDP | 낮음 |
| 투명 프록시 | ❌ 아니오 | HTTP만 | — |
결론: WebSocket 작업에 최적의 선택은 SOCKS5입니다. 이 프로토콜은 전송 계층에서 작동하며 TCP 연결을 단순히 터널링하고 트래픽 내용을 분석하지 않습니다. HTTP, WebSocket, SSH 또는 다른 것이든 상관없이 처리합니다. HTTP 프록시도 WS와 함께 작동할 수 있지만, CONNECT 메서드를 통해서만 작동하며, 여기에는 아래에서 다룰 세부 사항이 있습니다.
CONNECT 메서드: HTTP 프록시가 WebSocket을 터널링하는 방법
HTTP 메서드 CONNECT는 HTTP 프록시가 대상 서버까지 "맹목적인" 터널을 생성할 수 있게 해주는 특별한 메커니즘입니다. 프록시는 터널 내부의 트래픽을 분석하지 않고 단순히 바이트를 전달합니다. 이것이 HTTPS가 HTTP 프록시를 통해 작동하는 방식이며, WebSocket을 프록시하는 방법입니다.
과정은 다음과 같습니다:
- 클라이언트가 프록시에 요청을 보냅니다:
CONNECT example.com:443 HTTP/1.1 - 프록시는
example.com:443와 TCP 연결을 설정합니다 - 프록시는 클라이언트에게 응답합니다:
200 Connection Established - 이 시점부터 프록시는 바이트를 양방향으로 전달합니다 — 분석하지 않고
- 클라이언트는 터널을 통해 서버와 직접 TLS 핸드셰이크를 수행합니다
- 그 후 — TLS 위에 WebSocket 핸드셰이크를 수행합니다
주요 제한 사항: CONNECT 메서드는 일반적으로 443 포트에 대해서만 허용됩니다. WebSocket 서버가 비표준 포트(예: 8080 또는 9000)에서 작동하는 경우, 프록스는 연결을 거부할 수 있습니다. 이 경우 SOCKS5가 더 바람직합니다 — 포트 제한이 없습니다.
또한 일부 기업 HTTP 프록시(예: 기본 구성의 Squid)는 특정 포트에 대해 CONNECT 메서드를 명시적으로 차단하거나 인증을 요구할 수 있습니다. 상업용 프록시 제공업체와 함께 작업하는 경우, 대부분은 제한 없이 CONNECT를 지원합니다.
# curl을 통한 CONNECT 요청 예제 (확인용)
curl -v -x http://proxy_host:proxy_port \
--proxytunnel \
https://echo.websocket.org
# 프록시가 CONNECT를 지원하면 다음과 같은 메시지가 표시됩니다:
# * CONNECT tunnel established, response 200
SOCKS5와 WebSocket: 왜 이것이 최선의 선택인지
SOCKS5는 TCP/UDP 연결 수준에서 작동하는 프록시 프로토콜입니다. HTTP 프록시와 달리 SOCKS5는 내부에 있는 응용 프로토콜에 대해 아무것도 알지 못합니다. 단순히 클라이언트와 대상 서버 간의 터널을 생성할 뿐입니다. 이는 여러 가지 이유로 WebSocket에 이상적입니다:
- 프로토콜에 대한 제한 없음: SOCKS5는 WS, WSS, SSH, FTP 등을 포함한 모든 TCP 트래픽을 터널링합니다.
- 포트에 대한 제한 없음: 443이나 80뿐만 아니라 모든 포트에서 작동합니다.
- 프로토콜 변경 시 연결 끊김 없음: 프록시는 HTTP에서 WebSocket으로의 전환을 "보지" 않습니다.
- 인증 지원: SOCKS5는 로그인/비밀번호를 지원하여 상업용 프록시에 유용합니다.
- UDP 지원: 애플리케이션이 WebRTC 또는 WS와 함께 UDP를 사용하는 경우 SOCKS5가 처리할 수 있습니다.
거의 모든 최신 WebSocket 라이브러리는 SOCKS5를 직접 또는 추가 패키지를 통해 지원합니다. 아래에서는 Python 및 Node.js에 대한 구체적인 예제를 살펴보겠습니다.
💡 SOCKS5를 언제 선택하고 HTTP CONNECT를 언제 선택해야 할까요?
SOCKS5를 사용하세요: 비표준 포트, UDP 지원이 필요하거나 최소한의 설정을 원할 경우.
HTTP CONNECT를 사용하세요: 프록시 제공업체가 SOCKS5를 지원하지 않거나 기업 프록시를 통해 작업하는 경우.
코드 예제: Python에서 프록시를 통한 WebSocket
Python을 위한 몇 가지 시나리오를 살펴보겠습니다. Python에서 가장 인기 있는 WebSocket 라이브러리는 websockets와 websocket-client입니다.
옵션 1: HTTP 프록시를 통한 websocket-client
import websocket
# HTTP 프록시 설정
proxy_host = "proxy.example.com"
proxy_port = 8080
proxy_user = "username"
proxy_pass = "password"
ws = websocket.WebSocket()
ws.connect(
"wss://echo.websocket.org",
http_proxy_host=proxy_host,
http_proxy_port=proxy_port,
http_proxy_auth=(proxy_user, proxy_pass),
proxy_type="http" # 또는 "socks5"
)
ws.send("안녕하세요, WebSocket!")
result = ws.recv()
print(f"수신됨: {result}")
ws.close()
옵션 2: SOCKS5를 통한 websocket-client
import websocket
# SOCKS5를 위해 패키지 필요: pip install PySocks
ws = websocket.WebSocket()
ws.connect(
"wss://echo.websocket.org",
http_proxy_host="socks5_proxy.example.com",
http_proxy_port=1080,
http_proxy_auth=("username", "password"),
proxy_type="socks5"
)
ws.send("테스트 메시지")
print(ws.recv())
ws.close()
옵션 3: SOCKS5를 통한 websockets (asyncio) 라이브러리
websockets (asyncio) 라이브러리는 프록시 지원이 내장되어 있지 않으므로, 터널을 생성하기 위해 python-socks를 사용합니다:
# pip install websockets python-socks[asyncio]
import asyncio
import websockets
from python_socks.async_.asyncio import Proxy
async def connect_via_socks5():
proxy = Proxy.from_url("socks5://username:[email protected]:1080")
# 프록시를 통해 TCP 연결 생성
sock = await proxy.connect(
dest_host="echo.websocket.org",
dest_port=443
)
# websockets에 소켓 전달
async with websockets.connect(
"wss://echo.websocket.org",
sock=sock
) as ws:
await ws.send("SOCKS5를 통해 안녕하세요!")
response = await ws.recv()
print(f"응답: {response}")
asyncio.run(connect_via_socks5())
옵션 4: PySocks를 통한 전역 패치
Python 애플리케이션의 모든 트래픽을 SOCKS5를 통해 라우팅하려면 각 호출을 변경하지 않고 socks.setdefaultproxy()를 사용하세요:
# pip install PySocks
import socks
import socket
import websocket
# 소켓을 전역적으로 패치
socks.set_default_proxy(
socks.SOCKS5,
"proxy.example.com",
1080,
username="user",
password="pass"
)
socket.socket = socks.socksocket
# 이제 모든 연결이 SOCKS5를 통해 이루어집니다
ws = websocket.WebSocket()
ws.connect("wss://echo.websocket.org")
ws.send("전역 SOCKS5 프록시!")
print(ws.recv())
ws.close()
코드 예제: Node.js에서 프록시를 통한 WebSocket
Node.js 생태계에서 WebSocket을 위한 가장 인기 있는 라이브러리는 ws입니다. HTTP/SOCKS5를 통한 프록시를 위해 https-proxy-agent 또는 socks-proxy-agent 패키지를 사용합니다.
옵션 1: HTTP CONNECT 프록시를 통한 WSS
// npm install ws https-proxy-agent
const WebSocket = require('ws');
const { HttpsProxyAgent } = require('https-proxy-agent');
const proxyUrl = 'http://username:[email protected]:8080';
const agent = new HttpsProxyAgent(proxyUrl);
const ws = new WebSocket('wss://echo.websocket.org', { agent });
ws.on('open', () => {
console.log('HTTP 프록시를 통해 연결이 설정되었습니다');
ws.send('Node.js에서 안녕하세요!');
});
ws.on('message', (data) => {
console.log(`수신됨: ${data}`);
ws.close();
});
ws.on('error', (err) => {
console.error('오류:', err.message);
});
옵션 2: SOCKS5를 통한 WSS
// npm install ws socks-proxy-agent
const WebSocket = require('ws');
const { SocksProxyAgent } = require('socks-proxy-agent');
const proxyUrl = 'socks5://username:[email protected]:1080';
const agent = new SocksProxyAgent(proxyUrl);
const ws = new WebSocket('wss://echo.websocket.org', { agent });
ws.on('open', () => {
console.log('SOCKS5를 통한 연결이 설정되었습니다');
ws.send(JSON.stringify({ type: 'ping', data: 'test' }));
});
ws.on('message', (data) => {
console.log('응답:', data.toString());
});
ws.on('close', (code, reason) => {
console.log(`닫힘: ${code} - ${reason}`);
});
옵션 3: 수동으로 HTTP 프록시를 통한 WS (TLS 없음)
HTTP 프록시를 통한 비암호화 WS(포트 80)의 경우, 표준 에이전트가 HTTPS와만 작동하는 경우가 많기 때문에 CONNECT 요청을 수동으로 보내야 합니다:
const net = require('net');
const WebSocket = require('ws');
function createTunnel(proxyHost, proxyPort, targetHost, targetPort) {
return new Promise((resolve, reject) => {
const socket = net.connect(proxyPort, proxyHost, () => {
const connectReq =
`CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\n` +
`Host: ${targetHost}:${targetPort}\r\n` +
`Proxy-Authorization: Basic ${Buffer.from('user:pass').toString('base64')}\r\n` +
`\r\n`;
socket.write(connectReq);
});
socket.once('data', (data) => {
if (data.toString().includes('200')) {
resolve(socket);
} else {
reject(new Error(`프록시가 CONNECT를 거부했습니다: ${data.toString()}`));
}
});
socket.on('error', reject);
});
}
async function main() {
const socket = await createTunnel(
'proxy.example.com', 8080,
'echo.websocket.org', 80
);
const ws = new WebSocket('ws://echo.websocket.org', { socket });
ws.on('open', () => {
ws.send('수동 CONNECT 터널!');
});
ws.on('message', (data) => {
console.log('수신됨:', data.toString());
ws.close();
});
}
main().catch(console.error);
WSS (WebSocket Secure): TLS를 통한 프록시의 특징
WSS는 TLS 위의 WebSocket입니다 (HTTP의 HTTPS와 유사). SOCKS5 또는 HTTP CONNECT를 통한 WSS 프록시에서 중요한 점은: TLS 암호화는 클라이언트와 최종 서버 간에 설정되며, 클라이언트와 프록시 간에는 설정되지 않습니다. 이는 다음을 의미합니다:
- 프록시 서버는 WSS 트래픽의 내용을 보지 않습니다 — 오직 IP 주소와 포트만 볼 수 있습니다.
- 서버 인증서는 클라이언트가 직접 확인합니다.
- 프록시는 자체 CA 인증서를 설치하지 않고는 데이터를 "변조"하거나 "가로챌" 수 없습니다.
이는 보안 측면에서 좋은 소식입니다. 그러나 설정 시 실용적인 세부 사항도 있습니다:
프록시를 통한 인증서 확인
기업 프록시(SSL 검사를 수행하는 프록시)를 사용할 때 인증서 확인 오류가 발생할 수 있습니다. 이 경우 프록시는 서버 인증서를 자신의 것으로 바꿉니다. 이러한 환경에서 작업하려면 프록시의 CA 인증서를 신뢰할 수 있는 목록에 추가해야 합니다:
# Python: 기업 프록시의 CA 인증서를 전달합니다
import ssl
import websocket
ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations("/path/to/corporate-ca.crt")
ws = websocket.WebSocket(sslopt={"context": ssl_context})
ws.connect(
"wss://internal.example.com",
http_proxy_host="corp-proxy.company.com",
http_proxy_port=8080,
proxy_type="http"
)
# 테스트 환경에서는 (생산 환경에서 사용하지 마세요!) 인증서 확인을 비활성화할 수 있습니다:
ws_test = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE})
ws_test.connect("wss://test.example.com", http_proxy_host="proxy", http_proxy_port=8080)
⚠️ 중요
TLS 인증서 확인 비활성화 (CERT_NONE)는 테스트 환경에서만 허용됩니다. 생산 환경에서는 MITM(중간자 공격)에 대한 취약점을 초래합니다.
프록시를 통한 SNI (서버 이름 표시)
SOCKS5를 사용할 때 클라이언트는 DNS를 직접 해결하거나 프록시에 위임할 수 있습니다. socks5h 모드는 DNS 요청이 프록시 서버 측에서 수행됨을 의미합니다. 이는 WSS에 중요합니다. TLS 핸드셰이크에서 SNI 헤더는 호스트 이름과 일치해야 합니다:
# socks5 — DNS는 로컬(클라이언트)에서 해결됩니다.
# socks5h — DNS는 프록시 서버에서 해결됩니다 (익명성을 위해 권장).
from python_socks.async_.asyncio import Proxy
# 프록시를 통한 DNS (권장):
proxy = Proxy.from_url("socks5h://user:[email protected]:1080")
# 로컬 DNS:
proxy = Proxy.from_url("socks5://user:[email protected]:1080")
일반적인 오류 및 해결 방법
WebSocket을 프록시할 때 가장 빈번한 문제와 해결책을 모았습니다:
오류 1: 407 프록시 인증 필요
프록시가 인증을 요구하지만, 자격 증명을 전달하지 않았거나 잘못 전달했습니다.
# ❌ 잘못됨 — 인증이 없음
ws.connect("wss://example.com", http_proxy_host="proxy.example.com", http_proxy_port=8080)
# ✅ 올바름 — 로그인과 비밀번호를 전달합니다
ws.connect(
"wss://example.com",
http_proxy_host="proxy.example.com",
http_proxy_port=8080,
http_proxy_auth=("username", "password")
)
오류 2: Connection reset by peer / 502 Bad Gateway
프록시가 WebSocket 또는 CONNECT 메서드를 지원하지 않습니다. 해결책: SOCKS5로 전환하거나 제공업체가 WebSocket 트래픽을 지원하는지 확인하세요.
오류 3: 연결이 30-60초 후에 끊김
많은 프록시 서버가 "비활성" TCP 연결을 타임아웃으로 종료합니다. WebSocket 연결은 데이터 교환이 없으면 비활성으로 보일 수 있습니다. 해결책 — ping/pong을 활성화하세요:
# Python — 매 20초마다 keepalive ping을 활성화합니다
import websocket
import threading
def run():
ws = websocket.WebSocketApp(
"wss://echo.websocket.org",
on_message=lambda ws, msg: print(msg),
on_error=lambda ws, err: print(f"오류: {err}"),
on_close=lambda ws, c, m: print("닫힘")
)
ws.run_forever(
ping_interval=20, # 매 20초마다 ping 전송
ping_timeout=10, # pong을 최대 10초 기다림
http_proxy_host="proxy.example.com",
http_proxy_port=8080,
proxy_type="socks5"
)
thread = threading.Thread(target=run)
thread.start()
오류 4: SSL: CERTIFICATE_VERIFY_FAILED
주로 SSL 검사를 수행하는 기업 프록시를 사용할 때 발생합니다. 해결책: 프록시의 CA 인증서를 신뢰할 수 있는 목록에 추가하거나 HTTP 프록시 대신 SOCKS5를 사용하세요 — SOCKS5는 SSL 검사를 수행하지 않습니다.
오류 5: 핸드셰이크 상태 403 Forbidden
대상 서버가 연결을 차단합니다. 원인: 프록시 IP가 블랙리스트에 올라가거나 필요한 헤더(Origin, User-Agent)가 없거나 서버가 데이터 센터의 트래픽을 차단합니다. 해결책: 주거용 프록시를 사용하여 실제 가정 사용자의 IP를 사용하세요 — 차단하기가 훨씬 어렵습니다.
오류 6: [Errno 111] Connection refused
프록시 서버가 사용할 수 없습니다: 잘못된 호스트/포트이거나 프록시가 실행되지 않습니다. WebSocket을 테스트하기 전에 간단한 HTTP 요청을 통해 연결 데이터와 프록시의 가용성을 확인하세요.
WebSocket 작업에 적합한 프록시 유형 선택하기
프록시 유형 선택은 특정 작업에 따라 다릅니다. 다음은 실용적인 가이드입니다:
| 작업 | 추천 유형 | 이유 |
|---|---|---|
| WS를 통한 파싱 (거래소, 금융 데이터) | 데이터 센터 프록시 | 높은 속도, 낮은 지연, 안정적인 연결 |
| WebSocket 서비스 차단 우회 | 주거용 프록시 | 실제 IP, 최소 차단 위험 |
| WS가 있는 모바일 애플리케이션 (모바일 API 작업) | 모바일 프록시 | 모바일 운영자의 IP — 서비스의 높은 신뢰도 |
| WS 서버 부하 테스트 | 데이터 센터 프록시 | 저렴하고 빠르며 많은 동시 연결 가능 |
| WS 지리적 테스트 | 주거용 프록시 | 다양한 국가 및 도시 선택 가능 |
WebSocket을 위한 프록시의 중요한 매개변수
WebSocket 작업을 위한 프록시 제공업체를 선택할 때 다음 매개변수에 주의하세요:
- SOCKS5 지원: 제공업체가 SOCKS5를 제공하는지 확인하세요, HTTP만 지원하지 않도록.
- 세션 지속 시간 (session duration): WebSocket에 대해 "sticky" 세션이 중요합니다 — 하나의 IP를 오랜 시간 동안 사용해야 합니다. 회전하는 프록시는 연결을 끊을 것입니다.
- 연결 타임아웃: 프록시는 장기 TCP 연결을 지원해야 합니다 (몇 분에서 몇 시간까지).
- 대역폭: 스트리밍 WebSocket (비디오, 거래 데이터)에 대해 높은 대역폭이 중요합니다.
- 지연 (latency): 금융 애플리케이션 및 거래 봇의 경우 최소 지연이 중요합니다 — 목표 서비스에 가까운 서버를 선택하세요.
💡 WebSocket 지원을 확인하는 프록시 제공업체
WebSocket 작업을 위한 프록시를 구매하기 전에 무료 에코 서버를 통해 테스트하세요:
wss://echo.websocket.org 또는
wss://ws.postman-echo.com/raw.
연결이 설정되고 메시지가 반환되면 — 프록시가 WebSocket과 올바르게 작동합니다.
WebSocket을 위한 sticky 세션 설정
대부분의 주거용 프록시 제공업체는 기본적으로 IP 회전을 사용합니다. WebSocket의 경우 이는 허용되지 않습니다 — IP가 변경될 때마다 연결이 끊어집니다. sticky session 모드를 사용하고 있는지 확인하세요 (고정 IP). 일반적으로 프록시의 특별한 URL 형식을 통해 이루어집니다:
# sticky 세션 형식 예제 (제공업체에 따라 다름):
# 회전형 (WebSocket에 적합하지 않음):
socks5://user:[email protected]:1080
# Sticky 세션 (WebSocket에 적합):
socks5://user-session-abc123:[email protected]:1080
# 또는 국가 및 세션 매개변수를 통해:
socks5://user-country-us-session-12345:[email protected]:1080
결론
WebSocket은 단순히 "오랜 연결을 가진 HTTP"가 아닙니다. 이는 프록시할 때 특별한 접근 방식이 필요한 별도의 프로토콜입니다. 이 기사에서의 주요 결론은 다음과 같습니다:
- SOCKS5는 WebSocket에 최적의 선택: 전송 계층에서 작동하며 프로토콜을 분석하지 않고 모든 포트를 지원합니다.
- CONNECT를 통한 HTTP 프록시도 작동하지만 포트 제한 및 SSL 검사 문제로 인해 제한이 있습니다.
- Sticky 세션은 필수: 회전하는 프록시는 IP가 변경될 때마다 WebSocket 연결을 끊습니다.
- Ping/pong keepalive는 프록시의 타임아웃으로 인한 연결 끊김을 방지하는 데 필요합니다.
- 프록시를 통한 WSS는 안전합니다: TLS 암호화는 클라이언트와 서버 간에 직접 설정되며, 프록시는 내용을 보지 않습니다.
프록시를 통해 WebSocket과 함께 작업하는 애플리케이션을 개발하는 경우 — SOCKS5와 sticky 세션으로 시작하세요. 이는 디버깅 시간을 절약할 것입니다. 높은 속도와 안정성이 중요한 작업(거래 봇, 데이터 스트리밍)에는 데이터 센터 프록시가 적합합니다. 그러나 대상 서비스가 데이터 센터 IP를 적극적으로 차단하는 경우 주거용 프록시를 고려하세요: 이들은 실제 가정 사용자의 IP를 가지고 있으며, 긴 WebSocket 세션에서도 차단될 가능성이 훨씬 적습니다.
```