عند العمل مع البروكسي، يواجه المطورون غالبًا أن الإعدادات التقليدية لبروكسي HTTP لا تعمل مع اتصالات WebSocket، بينما gRPC يتوقف تمامًا مع أخطاء في TLS handshake. المشكلة تكمن في أن WebSocket و HTTP/2 و gRPC ليست مجرد "خيارات HTTP"، بل نماذج نقل مختلفة لها متطلباتها الخاصة بالتنبيب، والمضاعفة ومعالجة الرؤوس. في هذه المقالة سنستعرض كيفية بروكسي كل من هذه البروتوكولات، وما هي العقبات التي قد تواجهها في الممارسة وكيف تختار البروتوكول ونوع البروكسي المناسب لمهمة معينة - من تحليل البيانات عبر WebSocket إلى خدمات gRPC الموزعة ذات الحمل العالي.
ما الفرق بين البروتوكولات ولماذا هو مهم للبروكسي
يعمل HTTP/1.1 وفق نموذج "طلب - استجابة": يفتح العميل اتصالًا، يرسل طلبًا، يتلقى استجابة، ويغلق الاتصال أو يبقيه مفتوحًا للطلب التالي (keep-alive). تم تحسين خوادم البروكسي لعقود من الزمن وفقًا لهذا النموذج - تحليل الرؤوس، تخزين جسم الطلب، توجيه بسيط بناءً على رأس Host.
يكسر WebSocket هذا النموذج: بعد عملية HTTP handshake الأولية (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 وتقوم تلقائيًا "بتخفيض" الاتصال. بالنسبة للعميل، قد يكون هذا غير ملحوظ (حيث يتم تنفيذ الطلب على أي حال)، ولكن تفقد مزايا المضاعفة - تزداد التأخيرات، خاصة عند وجود عدد كبير من الطلبات المتوازية إلى نفس المضيف.
يمكن التحقق مما إذا كان الاتصال عبر البروكسي يدعم 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، تحتاج إلى مكتبة 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()
);
من الضروري أن تكون هناك تأخيرات منخفضة واتصال TCP مستقر بدون انقطاعات متكررة لعمل gRPC عبر البروكسي - حيث تكون تدفقات gRPC المضاعفة حساسة أكثر للأخطاء الشبكية من طلبات HTTP/1.1 الفردية. في مثل هذه السيناريوهات، غالبًا ما تظهر البروكسيات السكنية استقرارًا أفضل مقارنة بمجموعات مراكز البيانات الرخيصة، حيث يمر المسار إلى الخادم المستهدف عبر بنية تحتية شبكية أكثر توقعًا.
جدول مقارنة البروتوكولات
| البروتوكول | نوع الاتصال | متطلبات البروكسي | نوع البروكسي الموصى به |
|---|---|---|---|
| HTTP/1.1 | طلب-استجابة، keep-alive | حد أدنى، أي بروكسي HTTP | بروكسي مراكز البيانات |
| WebSocket | قناة ثنائية الاتجاه دائمة | دعم الاتصالات الطويلة، بدون تجاوزات زمنية | بروكسي سكنية |
| HTTP/2 | تدفقات ثنائية مضاعفة | تنبيب TLS الشفاف، ALPN | بروكسي سكنية أو مراكز البيانات |
| gRPC | HTTP/2 + Protobuf، غالبًا mTLS | تأخير منخفض، استقرار، HTTP/2 CONNECT | بروكسي موبايل لتجاوز الفلاتر الصارمة |
كيفية اختيار البروتوكول والبروكسي لستاك الخاص بك
نادرًا ما يكون اختيار البروتوكول قرارًا حرًا - فهو مفروض من واجهة برمجة التطبيقات المستهدفة. لكن اختيار نوع البروكسي لهذا البروتوكول هو بالفعل مسؤوليتك، وهنا يجب أن تستند إلى السيناريو المحدد:
تحليل البيانات عبر WebSocket (على سبيل المثال، تدفق الأسعار أو البيانات الحية من مواقع السوق) يتطلب اتصالًا مستقرًا وطويلًا بدون انقطاعات بسبب تجاوز الزمن. هنا تعمل البروكسيات السكنية بشكل أفضل - فهي نادرًا ما تقع تحت خوارزميات مكافحة الاحتيال للخدمة المستهدفة وتحتفظ بالاتصال لفترة أطول من مجموعات مراكز البيانات النموذجية.
التكامل مع خدمات gRPC عبر بروكسي خارجي (على سبيل المثال، عند اختبار واجهة برمجة التطبيقات من مواقع جغرافية مختلفة) يتطلب تأخيرًا منخفضًا ودعمًا لـ HTTP/2 على طول الطريق. لهذا، تناسب عناوين IP السكنية ذات التوجيه الجيد، وللمهام الحرجة من حيث الوقت - البروكسيات الموبايل، إذا كانت الخدمة المستهدفة تقوم بتصفية نطاقات مراكز البيانات بشكل عدواني.
طلبات HTTP/2 الجماعية إلى واجهة برمجة التطبيقات REST/GraphQL مع المضاعفة - هو السيناريو الأكثر شيوعًا حيث تكفي البروكسيات السريعة والرخيصة من مراكز البيانات، إذا لم تقم الخدمة المستهدفة بحظر نطاقات 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، أو استخدم بروكسي 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 البسيطة ذات عرض النطاق العالي، تناسب بروكسي مراكز البيانات، بينما لمهام تصفية مكافحة الاحتيال العدوانية للخدمة المستهدفة - بروكسي موبايل.