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 |
|---|---|---|
| 接続の種類 | リクエスト → レスポンス → クローズ | 恒久的、双方向 |
| 寿命 | 秒(1つのリクエスト) | 分、時間、日 |
| データの発信者 | クライアントのみ | クライアントとサーバー |
| ハンドシェイク後のプロトコル | 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なし)を手動で
暗号化されていないWS(ポート80)をHTTPプロキシ経由で使用するには、標準のエージェントが通常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モード(ホスト名解決付きSOCKS5)は、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 Proxy Authentication Required
プロキシが認証を要求していますが、資格情報を渡していないか、間違って渡しています。
# ❌ 不正 — 認証なし
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: Handshake status 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のみではありません。
- セッションの持続時間: WebSocketには「スティッキー」セッションが重要です — 1つのIPを長期間使用します。ローテーションプロキシは接続を切断します。
- 接続のタイムアウト: プロキシは長寿命のTCP接続をサポートする必要があります(数分から数時間)。
- 帯域幅: ストリーミングWebSocket(ビデオ、取引データ)には、制限なしの高い帯域幅が重要です。
- レイテンシ: 金融アプリケーションや取引ボットにとって、最小限のレイテンシが重要です — 目標サービスに近いサーバーのプロキシを選択してください。
💡 WebSocketプロキシプロバイダーのサポートを確認する
WebSocketタスク用のプロキシを購入する前に、無料のエコーサーバーでテストしてください:
wss://echo.websocket.orgまたは
wss://ws.postman-echo.com/raw。
接続が確立され、メッセージが返される場合 — プロキシはWebSocketで正しく動作します。
WebSocketのスティッキーセッションの設定
ほとんどの住宅用プロキシプロバイダーは、デフォルトでIPのローテーションを使用します。WebSocketにはこれが受け入れられません — 各IPの変更は接続の切断を意味します。スティッキーセッションモード(固定IP)を使用していることを確認してください。通常、これはプロキシの特別なURLフォーマットを介して行われます:
# スティッキーセッションのフォーマットの例(プロバイダーによって異なります):
# ローテーション(WebSocketには適していません):
socks5://user:[email protected]:1080
# スティッキーセッション(WebSocketに適しています):
socks5://user-session-abc123:[email protected]:1080
# または国とセッションのパラメータを介して:
socks5://user-country-us-session-12345:[email protected]:1080
結論
WebSocketは単なる「長い接続のHTTP」ではありません。これは、プロキシ時に特別なアプローチが必要な別のプロトコルです。この記事からの主な結論は次のとおりです:
- SOCKS5はWebSocketに最適な選択: トランスポート層で動作し、プロトコルを解析せず、任意のポートをサポートします。
- CONNECT経由のHTTPプロキシも動作しますが、ポートに制限があり、SSL検査に関する問題が発生する可能性があります。
- スティッキーセッションは必須: ローテーションプロキシはIPの変更ごとにWebSocket接続を切断します。
- Ping/pong keepaliveはプロキシのタイムアウトによる接続の切断を防ぐために必要です。
- プロキシ経由のWSSは安全です: TLS暗号化はクライアントとサーバーの間で直接確立され、プロキシは内容を見ません。
プロキシ経由でWebSocketを使用するアプリケーションを開発している場合は、SOCKS5とスティッキーセッションから始めてください。これにより、デバッグにかかる時間を節約できます。高い速度と接続の安定性が重要なタスク(取引ボット、データストリーミング)には、低レイテンシのデータセンターのプロキシが最適です。しかし、ターゲットサービスがデータセンターのIPを積極的にブロックしている場合は、住宅用プロキシを検討してください。これらは実際の家庭ユーザーのIPを持ち、長時間のWebSocketセッションでもブロックされにくいです。
```