← بازگشت به وبلاگ

وب‌سوکت، gRPC، HTTP/2 از طریق پروکسی: چگونه پروتکل مناسب برای استک خود را انتخاب کنیم

ویژگی‌های پروکسی‌کردن WebSocket، gRPC و HTTP/2 را بررسی می‌کنیم - کدام پروتکل را انتخاب کنیم، چگونه سرور پروکسی را تنظیم کنیم و از اشتباهات مکرر توسعه‌دهنده جلوگیری کنیم.

📅۳۰ شهریور ۱۴۰۵

هنگام کار با پروکسی، توسعه‌دهندگان اغلب با این مشکل مواجه می‌شوند که تنظیمات کلاسیک پروکسی HTTP برای اتصالات WebSocket کار نمی‌کند و gRPC به طور کلی با خطاهای TLS handshake قطع می‌شود. مشکل این است که WebSocket، HTTP/2 و gRPC فقط «نسخه‌های HTTP» نیستند، بلکه مدل‌های حمل و نقل مختلفی با الزامات خاص خود برای تونل‌سازی، چندگانه‌سازی و پردازش هدرها هستند. در این مقاله بررسی می‌کنیم که چگونه هر یک از این پروتکل‌ها را پروکسی کنیم، چه مشکلاتی در عمل وجود دارد و چگونه پروتکل و نوع پروکسی را برای یک وظیفه خاص انتخاب کنیم — از تجزیه داده‌ها از طریق WebSocket تا میکروسرویس‌های gRPC با بار بالا.

تفاوت پروتکل‌ها و اهمیت آن برای پروکسی

HTTP/1.1 بر اساس مدل «درخواست — پاسخ» کار می‌کند: کلاینت یک اتصال باز می‌کند، درخواست ارسال می‌کند، پاسخ را دریافت می‌کند و یا اتصال را می‌بندد یا آن را برای درخواست بعدی باز نگه می‌دارد (keep-alive). سرورهای پروکسی برای دهه‌ها به طور خاص برای این مدل بهینه‌سازی شده‌اند — تجزیه هدرها، بافر کردن بدنه درخواست، مسیریابی ساده بر اساس هدر Host.

WebSocket این مدل را می‌شکند: پس از handshake اولیه HTTP (Upgrade: websocket) اتصال به یک کانال دوطرفه دائمی تبدیل می‌شود که داده‌ها می‌توانند در هر لحظه به هر دو سمت ارسال شوند. پروکسی باید این اتصال را «آزاد» کند و فقط بایت‌ها را به دو سمت منتقل کند، بدون اینکه سعی کند آن‌ها را به عنوان درخواست‌های HTTP تفسیر کند.

HTTP/2 چندگانه‌سازی را اضافه می‌کند — چندین درخواست منطقی از طریق یک اتصال TCP به طور همزمان انجام می‌شوند و از فریم‌های باینری به جای هدرهای متنی استفاده می‌کنند. برای پروکسی این به این معنی است که نمی‌توان به سادگی متن را خط به خط خواند — نیاز به پشتیبانی از پروتکل باینری HTTP/2 در سطح پروکسی یا تونل‌سازی TCP شفاف بر روی TLS است.

gRPC حتی فراتر می‌رود: این پروتکل به طور خاص بر روی HTTP/2 ساخته شده است، از Protocol Buffers برای سریال‌سازی استفاده می‌کند و نیاز به حمل و نقل سازگار با HTTP/2 در تمام مسیر از کلاینت به سرور دارد. اگر پروکسی در بخشی از مسیر اتصال را به HTTP/1.1 «کاهش دهد»، درخواست gRPC به سادگی عبور نخواهد کرد.

درک این تفاوت‌ها حیاتی است، زیرا انتخاب نوع نادرست پروکسی یا تنظیم نادرست تونل‌سازی منجر به قطع اتصالات، تایم‌اوت‌ها و خطاهای غیرقابل توضیحی می‌شود که بدون درک سطح حمل و نقل تشخیص آن‌ها دشوار است.

WebSocket از طریق پروکسی: تنظیمات و کد

برای WebSocket از طریق پروکسی دو سناریوی اصلی وجود دارد: پروکسی HTTP با روش CONNECT (برای WSS، یعنی WebSocket بر روی TLS) و پروکسی SOCKS5 که اتصال TCP را بدون تفسیر پروتکل تونل‌سازی می‌کند. SOCKS5 معمولاً مشکلات کمتری ایجاد می‌کند، زیرا به طور اولیه به عنوان «لوله شفاف» برای هر ترافیک TCP طراحی شده است.

مثال اتصال به WebSocket از طریق پروکسی SOCKS5 در Python با کتابخانه websockets و python-socks:

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("Hello via proxy")
    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('Hello via proxy'));
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 کار می‌کنند و به طور خودکار اتصال را «کاهش» می‌دهند. برای کلاینت این معمولاً نامحسوس است (درخواست به هر حال انجام می‌شود)، اما مزایای چندگانه‌سازی از بین می‌رود — تأخیرها افزایش می‌یابند، به ویژه در هنگام وجود تعداد زیادی درخواست همزمان به یک هاست.

می‌توانید با استفاده از cURL و پرچم --http2 و خروجی دقیق بررسی کنید که آیا اتصال از طریق پروکسی HTTP/2 را پشتیبانی می‌کند:

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

# در خروجی به دنبال خط زیر باشید:
# * Using HTTP2, server supports multiplexing
# اگر این خط وجود نداشته باشد — اتصال به HTTP/1.1 کاهش یافته است

برای HTTP/2 از طریق پروکسی در Python به کتابخانه 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 به TLS با گسترش ALPN برای توافق پروتکل نیاز دارد، بنابراین پروکسی باید ترافیک TLS را به طور شفاف از طریق CONNECT تونل‌سازی کند و نه اینکه TLS را به طور مستقل خاتمه دهد (مگر اینکه این یک پروکسی خاص HTTP/2 باشد). به همین دلیل است که برای وظایف با HTTP/2، پروکسی‌های مقیم و پروکسی‌های مرکز داده رفتار متفاوتی دارند — مهم است که از ارائه‌دهنده بپرسید که آیا تونل‌سازی TLS بدون قطع مذاکرات ALPN پشتیبانی می‌شود.

gRPC از طریق پروکسی: TLS، ALPN و الزامات خاص

gRPC — سخت‌گیرترین پروتکل در این مجموعه است. این پروتکل به طور خاص به HTTP/2 وابسته است و اغلب از TLS دوطرفه (mTLS) برای احراز هویت استفاده می‌کند. یک پروکسی معمولی که از تونل‌سازی HTTP/2 CONNECT پشتیبانی نمی‌کند، به سادگی ترافیک gRPC را عبور نخواهد داد — اتصال با خطاهایی از نوع UNAVAILABLE: upstream connect error قطع خواهد شد.

برای پروکسی‌سازی gRPC در Python (با کتابخانه 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 تولید شده
# 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 (به عنوان مثال، استریم کردن قیمت‌ها یا داده‌های real-time از وب‌سایت‌های بازار) نیاز به یک اتصال پایدار و طولانی بدون قطع به دلیل تایم‌اوت دارد. در اینجا پروکسی‌های مقیم بهتر عمل می‌کنند — آن‌ها کمتر تحت تأثیر الگوریتم‌های ضد تقلب سرویس هدف قرار می‌گیرند و اتصال را بیشتر از استخرهای معمولی مرکز داده نگه می‌دارند.

ادغام با میکروسرویس‌های gRPC از طریق پروکسی خارجی (به عنوان مثال، هنگام تست API از مکان‌های جغرافیایی مختلف) نیاز به تأخیر کم و پشتیبانی از HTTP/2 در تمام مسیر دارد. برای این کار IP‌های مقیم با مسیریابی خوب مناسب هستند و برای وظایف حساس به زمان — پروکسی‌های موبایل، اگر سرویس هدف به شدت دامنه‌های مرکز داده را فیلتر کند.

درخواست‌های انبوه HTTP/2 به REST/GraphQL API با چندگانه‌سازی — رایج‌ترین سناریو است که در آن پروکسی‌های مرکز داده سریع و ارزان کافی هستند، اگر سرویس هدف به طور پیش‌فرض دامنه‌های مرکز داده را مسدود نکند.

قاعده کلی: هر چه پروتکل «حساس‌تر» به ثبات اتصال باشد (WebSocket، gRPC)، بیشتر به IP‌های مقیم یا موبایل نیاز دارد. هر چه درخواست ساده‌تر و کوتاه‌تر باشد (HTTP/1.1 عادی یا HTTP/2 بدون جلسات طولانی)، کار با پروکسی‌های مرکز داده راحت‌تر است — آن‌ها سریع‌تر هستند و پهنای باند بیشتری ارائه می‌دهند.

اشتباهات رایج و راه‌حل‌های آن‌ها

خطا 1: WebSocket هر 30-60 ثانیه قطع می‌شود. دلیل این است که پروکسی یا متعادل‌کننده بار میانجی «اتصالات غیرفعال» را می‌بندد. راه‌حل: فعال کردن فریم‌های ping/pong در سطح برنامه با فاصله‌ای کمتر از تایم‌اوت پروکسی (معمولاً 20-25 ثانیه کافی است).

خطا 2: gRPC با UNAVAILABLE از طریق پروکسی قطع می‌شود، در حالی که به طور مستقیم کار می‌کند. پروکسی از تونل‌سازی HTTP/2 CONNECT پشتیبانی نمی‌کند. راه‌حل: مستندات ارائه‌دهنده را برای بررسی پشتیبانی از HTTP/2 بررسی کنید یا از پروکسی SOCKS5 استفاده کنید که TCP را بدون تفسیر پروتکل تونل‌سازی کند.

خطا 3: HTTP/2 به طور نامحسوس به HTTP/1.1 کاهش می‌یابد. این معمولاً به دلیل عدم پشتیبانی ALPN در پروکسی اتفاق می‌افتد. نسخه پروتکل را به وضوح در کد بررسی کنید (مانند مثال با httpx در بالا) — به این که «همه چیز کار می‌کند» اعتماد نکنید، اگر http_version را در پاسخ بررسی نکرده‌اید.

خطا 4: تأخیر بالا در چندگانه‌سازی gRPC از طریق پروکسی. این معمولاً ناشی از تعداد زیاد گره‌های میانجی در ارائه‌دهنده پروکسی است. راه‌حل: تأخیر را از قبل از طریق یک درخواست RTT ساده تست کنید و ارائه‌دهنده‌ای با مسیریابی مستقیم انتخاب کنید، به ویژه برای فراخوانی‌های gRPC حساس به زمان.

نتیجه‌گیری

WebSocket، HTTP/2 و gRPC نیاز به رویکردهای متفاوتی برای تنظیم پروکسی دارند، زیرا بر روی مدل‌های حمل و نقل مختلف کار می‌کنند: از «آزاد کردن اتصال TCP» برای WebSocket تا پشتیبانی دقیق از HTTP/2 CONNECT و ALPN برای gRPC. نسخه پروتکل را به وضوح در کد بررسی کنید، ثبات اتصالات طولانی‌مدت را از قبل تست کنید و در جاهایی که حداکثر شفافیت تونل‌سازی نیاز است، از SOCKS5 استفاده کنید.

اگر استک شما شامل اتصالات WebSocket طولانی‌مدت یا فراخوانی‌های gRPC حساس به تأخیر است، توصیه می‌کنیم از پروکسی‌های مقیم استفاده کنید — آن‌ها اتصالات پایدارتر و قابل پیش‌بینی‌تری نسبت به استخرهای معمولی مرکز داده ارائه می‌دهند. برای درخواست‌های ساده HTTP/2 با پهنای باند بالا، پروکسی‌های مرکز داده مناسب هستند و برای وظایفی که فیلترهای ضد تقلب سختی دارند — پروکسی‌های موبایل مناسب‌اند.