← 블로그로 돌아가기

프록시를 통한 WebSocket, gRPC, HTTP/2: 스택에 적합한 프로토콜 선택하기

WebSocket, gRPC 및 HTTP/2 프록시의 특징을 분석합니다. 어떤 프로토콜을 선택할지, 프록시 서버를 어떻게 설정할지, 개발자가 자주 범하는 오류를 피하는 방법에 대해 설명합니다.

📅2026년 9월 21일

프록시를 사용할 때 개발자들은 종종 전통적인 HTTP 프록시 설정이 WebSocket 연결에 대해 작동하지 않으며, gRPC는 TLS 핸드셰이크 오류로 인해 실패하는 경우를 겪습니다. 문제는 WebSocket, HTTP/2 및 gRPC가 단순히 "HTTP의 변형"이 아니라, 터널링, 멀티플렉싱 및 헤더 처리에 대한 요구 사항이 있는 서로 다른 전송 모델이라는 것입니다. 이 기사에서는 이러한 각 프로토콜을 프록시화하는 방법, 실제에서 마주치는 함정, 특정 작업에 맞는 프로토콜 및 프록시 유형을 선택하는 방법을 살펴보겠습니다. WebSocket을 통한 파싱에서부터 고부하 gRPC 마이크로서비스에 이르기까지.

프로토콜의 차이점과 프록시에 대한 중요성

HTTP/1.1은 "요청 - 응답" 모델로 작동합니다: 클라이언트가 연결을 열고 요청을 보내며, 응답을 받고 연결을 닫거나 다음 요청을 위해 연결을 유지합니다(keep-alive). 프록시 서버는 수십 년 동안 이 모델에 최적화되어 왔습니다 — 헤더 파싱, 요청 본문 버퍼링, Host 헤더에 따른 간단한 라우팅.

WebSocket은 이 모델을 깨뜨립니다: 초기 HTTP 핸드셰이크(Upgrade: websocket) 후 연결은 지속적인 양방향 채널로 변환되어 데이터가 언제든지 양방향으로 흐를 수 있습니다. 프록시는 이 연결을 "해방"하고 단순히 바이트를 양방향으로 전송해야 하며, 이를 HTTP 요청으로 해석하려고 해서는 안 됩니다.

HTTP/2는 멀티플렉싱을 추가합니다 — 여러 개의 논리적 요청이 하나의 TCP 연결을 통해 동시에 진행되며, 텍스트 헤더 대신 이진 프레임을 사용합니다. 프록시에게는 단순히 텍스트를 줄 단위로 읽는 것이 아니라, 프록시 수준에서 HTTP/2의 이진 프로토콜 지원이 필요하거나 TLS 위에서 투명한 TCP 터널링이 필요하다는 것을 의미합니다.

gRPC는 더 나아갑니다: HTTP/2에 엄격히 기반하고, 직렬화를 위해 Protocol Buffers를 사용하며, 클라이언트에서 서버까지의 모든 경로에서 HTTP/2 호환 전송이 필요합니다. 만약 프록시가 어떤 지점에서 연결을 HTTP/1.1로 "다운그레이드"하면, gRPC 요청은 단순히 통과하지 못합니다.

이러한 차이를 이해하는 것은 매우 중요합니다. 잘못된 유형의 프록시를 선택하거나 터널링을 잘못 설정하면 연결이 끊기고, 타임아웃이 발생하며, 전송 수준을 이해하지 않고는 진단하기 어려운 설명할 수 없는 오류가 발생할 수 있습니다.

프록시를 통한 WebSocket: 설정 및 코드

프록시를 통한 WebSocket에는 두 가지 주요 시나리오가 있습니다: WSS(즉, TLS를 통한 WebSocket)를 위한 CONNECT 메소드를 사용하는 HTTP 프록시와 프로토콜을 해석하지 않고 TCP 연결을 터널링하는 SOCKS5 프록시입니다. SOCKS5는 원래 모든 TCP 트래픽을 위한 "투명한 파이프"로 설계되었기 때문에 일반적으로 문제가 적습니다.

Python의 websockets 및 python-socks 라이브러리를 사용한 SOCKS5 프록시를 통한 WebSocket 연결 예제:

import asyncio
from python_socks.sync import Proxy
import websockets
import websockets.sync.client as ws_client

def connect_ws_via_proxy():
    proxy = Proxy.from_url("socks5://user:pass@proxy_host:1080")
    sock = proxy.connect(dest_host="echo.websocket.events", dest_port=443)

    ws = ws_client.connect(
        "wss://echo.websocket.events",
        sock=sock,
        server_hostname="echo.websocket.events"
    )
    ws.send("프록시를 통해 안녕하세요")
    print(ws.recv())
    ws.close()

connect_ws_via_proxy()

Node.js에서는 ws 패키지를 socks-proxy-agent와 함께 사용하여 유사한 문제를 해결합니다:

const WebSocket = require('ws');
const { SocksProxyAgent } = require('socks-proxy-agent');

const agent = new SocksProxyAgent('socks5://user:pass@proxy_host:1080');
const socket = new WebSocket('wss://echo.websocket.events', { agent });

socket.on('open', () => socket.send('프록시를 통해 안녕하세요'));
socket.on('message', (data) => console.log(data.toString()));

만약 프록시가 HTTP(CONNECT 메소드 사용)로만 접근 가능하다면, 구조는 유사합니다 — 대부분의 현대 WebSocket 클라이언트는 HTTP 터널을 통해 작동할 수 있으며, 프록시가 장기 연결을 타임아웃으로 끊지 않는지 확인하는 것이 중요합니다. 이는 비싼 데이터 센터 프록시의 경우 자주 발생하는 문제로, 비활성 연결에 대해 30-60초의 엄격한 제한이 설정되어 있어 스트리밍 WebSocket 작업에는 치명적일 수 있으며, 공급자에게 keep-alive 매개변수를 확인하는 것이 좋습니다.

프록시를 통한 HTTP/2: 멀티플렉싱 및 함정

HTTP/2를 프록시를 통해 사용할 때의 주요 어려움은 많은 프록시 서버(특히 오래된 Squid 또는 간단한 포워드 프록시)가 HTTP/1.1로만 작동하며 연결을 자동으로 "다운그레이드"한다는 것입니다. 클라이언트에게는 종종 눈에 띄지 않지만(요청은 여전히 수행됨), 멀티플렉싱의 이점이 사라집니다 — 지연이 증가하며, 특히 하나의 호스트에 대한 많은 병렬 요청이 있을 때 그렇습니다.

프록시를 통한 HTTP/2 연결 지원 여부를 확인하려면 cURL을 사용하여 --http2 플래그와 함께 자세한 출력을 확인할 수 있습니다:

curl -v --http2 -x http://user:pass@proxy_host:8080 https://example.com

# 출력에서 다음 줄을 찾으세요:
# * Using HTTP2, server supports multiplexing
# 만약 이 줄이 없다면 — 연결이 HTTP/1.1로 다운그레이드되었습니다.

Python에서 프록시를 통한 HTTP/2를 사용하려면 httpx 라이브러리가 필요하며, HTTP/2 지원이 활성화되어 있어야 합니다:

import httpx

proxies = {
    "http://": "http://user:pass@proxy_host:8080",
    "https://": "http://user:pass@proxy_host:8080",
}

with httpx.Client(proxies=proxies, http2=True) as client:
    response = client.get("https://example.com")
    print(response.http_version)  # "HTTP/2"를 기대합니다.
    print(response.status_code)

중요한 점: HTTP/2는 프로토콜 협상을 위한 ALPN 확장이 있는 TLS를 요구하므로, 프록시는 CONNECT를 통해 TLS 트래픽을 투명하게 터널링해야 하며, TLS를 직접 종료해서는 안 됩니다(전문 HTTP/2 호환 프록시가 아닌 경우). 이 때문에 HTTP/2 작업을 위해서는 레지던트 프록시와 데이터 센터 프록시가 다르게 작동합니다 — 공급자에게 ALPN 협상 중단 없이 종단 간 TLS 터널링이 지원되는지 확인하는 것이 중요합니다.

프록시를 통한 gRPC: TLS, ALPN 및 특별한 요구 사항

gRPC는 이 조합에서 가장 요구사항이 많은 프로토콜입니다. HTTP/2에 엄격히 의존하며, 종종 인증을 위해 양방향 TLS(mTLS)를 사용합니다. HTTP/2 CONNECT 터널링을 지원하지 않는 일반 포워드 프록시는 gRPC 트래픽을 통과시키지 않으며, 연결은 UNAVAILABLE: upstream connect error와 같은 오류로 실패합니다.

Python에서 gRPC를 프록시화하려면(grpc 라이브러리 사용) 프록시는 환경 변수를 통해 설정해야 하며, 채널 옵션에서 프록시 지원이 내장되어 있지 않았습니다:

import os
import grpc

os.environ["grpc_proxy"] = "http://user:pass@proxy_host:8080"
os.environ["https_proxy"] = "http://user:pass@proxy_host:8080"

channel = grpc.secure_channel(
    "grpc.example.com:443",
    grpc.ssl_channel_credentials()
)

# 생성된 스텁을 통한 호출 예제
# stub = YourServiceStub(channel)
# response = stub.YourMethod(request)

Node.js에서 gRPC를 위한 프록시는 grpc-js의 채널 옵션을 통해 설정되며, HTTP/2 호환 프록시 에이전트와 함께 사용됩니다:

const grpc = require('@grpc/grpc-js');
const { HttpsProxyAgent } = require('https-proxy-agent');

process.env.grpc_proxy = 'http://user:pass@proxy_host:8080';
process.env.https_proxy = 'http://user:pass@proxy_host:8080';

const client = new YourServiceClient(
  'grpc.example.com:443',
  grpc.credentials.createSsl()
);

프록시를 통한 gRPC의 안정적인 작동을 위해서는 낮은 지연 시간과 잦은 연결 끊김이 없는 안정적인 TCP 연결이 매우 중요합니다 — 멀티플렉스된 gRPC 스트림은 개별 HTTP/1.1 요청보다 네트워크 장애에 더 민감합니다. 이러한 시나리오에서는 레지던트 프록시가 일반적으로 저렴한 데이터 센터 풀보다 더 나은 안정성을 보입니다. 이는 목표 서버까지의 경로가 더 예측 가능한 네트워크 인프라를 통과하기 때문입니다.

프로토콜 비교 표

프로토콜 연결 유형 프록시 요구 사항 추천 프록시 유형
HTTP/1.1 요청-응답, keep-alive 최소 요구 사항, 모든 HTTP 프록시 데이터 센터 프록시
WebSocket 지속적인 양방향 채널 장기 연결 지원, 타임아웃 없음 레지던트 프록시
HTTP/2 멀티플렉스된 이진 스트림 종단 간 TLS 터널링, ALPN 레지던트 또는 데이터 센터 프록시
gRPC HTTP/2 + Protobuf, 종종 mTLS 낮은 지연 시간, 안정성, HTTP/2 CONNECT 모바일 프록시로 엄격한 필터 우회

자신의 스택에 맞는 프로토콜 및 프록시 선택 방법

프로토콜 선택은 자유로운 결정이 드물며 — 이는 목표 API에 의해 결정됩니다. 그러나 이 프로토콜에 맞는 프록시 유형 선택은 귀하의 책임이며, 여기서는 구체적인 시나리오에 의존해야 합니다:

WebSocket을 통한 데이터 파싱 (예: 마켓플레이스 사이트의 실시간 데이터 스트리밍)은 타임아웃으로 인한 연결 끊김 없이 안정적이고 긴 연결을 요구합니다. 이 경우 레지던트 프록시가 더 잘 작동합니다 — 이들은 목표 서비스의 안티프로드 알고리즘에 덜 걸리며, 일반 데이터 센터 풀보다 더 오랫동안 연결을 유지합니다.

외부 프록시를 통한 gRPC 마이크로서비스 통합 (예: 다양한 지리적 위치에서 API 테스트)는 낮은 지연 시간과 HTTP/2 지원을 요구합니다. 이를 위해서는 좋은 라우팅을 가진 레지던트 IP가 적합하며, 시간에 민감한 작업의 경우 데이터 센터 범위를 공격적으로 필터링하는 경우 모바일 프록시가 필요합니다.

REST/GraphQL API에 대한 대량 HTTP/2 요청은 멀티플렉싱이 이루어지는 가장 일반적인 시나리오로, 목표 서비스가 기본적으로 데이터 센터 IP 범위를 차단하지 않는 경우 빠르고 저렴한 데이터 센터 프록시로 충분합니다.

일반적인 규칙: 프로토콜이 연결의 안정성에 "더 까다로울수록" (WebSocket, gRPC), 레지던트 또는 모바일 IP의 의미가 더 커집니다. 요청이 더 간단하고 짧을수록 (일반 HTTP/1.1 또는 긴 세션이 없는 HTTP/2), 데이터 센터 프록시와 작업하는 것이 더 편리합니다 — 이들은 더 빠르고 더 높은 대역폭을 제공합니다.

일반적인 오류 및 해결 방법

오류 1: WebSocket이 매 30-60초마다 끊깁니다. 원인은 프록시 또는 중간 로드 밸런서가 "비활성" 연결을 닫기 때문입니다. 해결 방법: 애플리케이션 수준에서 ping/pong 프레임을 프록시 타임아웃보다 짧은 간격으로 활성화합니다(보통 20-25초면 충분합니다).

오류 2: gRPC가 프록시를 통해 UNAVAILABLE로 실패하지만 직접 연결할 때는 작동합니다. 프록시가 HTTP/2 CONNECT 터널링을 지원하지 않습니다. 해결 방법: 공급자의 문서를 확인하여 HTTP/2 지원 여부를 확인하거나, 프로토콜 해석 없이 TCP를 터널링하는 SOCKS5 프록시를 사용합니다.

오류 3: HTTP/2가 눈에 띄지 않게 HTTP/1.1로 다운그레이드됩니다. 이는 종종 프록시에서 ALPN 지원이 없기 때문에 발생합니다. 코드에서 프로토콜 버전을 명시적으로 확인하십시오(위의 httpx 예제처럼) — 응답에서 http_version을 확인하지 않으면 "모든 것이 작동한다"고 믿지 마십시오.

오류 4: 프록시를 통한 gRPC 멀티플렉싱에서 높은 지연 시간이 발생합니다. 이는 종종 프록시 공급자의 중간 노드 수가 많기 때문입니다. 해결 방법: 간단한 RTT 요청을 통해 지연 시간을 미리 테스트하고, 특히 시간에 민감한 gRPC 호출을 위해 직접 라우팅을 제공하는 공급자를 선택합니다.

결론

WebSocket, HTTP/2 및 gRPC는 서로 다른 전송 모델에서 작동하기 때문에 프록시 설정에 대해 서로 다른 접근 방식이 필요합니다: WebSocket의 경우 단순히 "TCP 연결을 해방"하는 것부터 gRPC의 경우 HTTP/2 CONNECT 및 ALPN에 대한 엄격한 지원까지. 코드에서 프로토콜 버전을 명시적으로 확인하고, 장기 연결의 안정성을 미리 테스트하며, 최대한의 투명한 터널링이 필요한 경우 SOCKS5를 선택하십시오.

만약 귀하의 스택에 장기 지속적인 WebSocket 연결이나 지연에 민감한 gRPC 호출이 포함되어 있다면, 레지던트 프록시를 사용해보는 것을 권장합니다 — 이들은 일반 데이터 센터 풀보다 더 안정적이고 예측 가능한 연결을 제공합니다. 높은 대역폭의 간단한 HTTP/2 요청에는 데이터 센터 프록시가 적합하며, 목표 서비스의 공격적인 안티프로드 필터링 작업에는 모바일 프록시가 필요합니다.