WebSocket پس از handshake، ارتباط دوطرفهٔ طولانی می‌سازد. امشب یک echo server محلی را از مسیر wss://app.example.test/ws/ منتشر می‌کنیم و دریافت 101 را با تبادل واقعی پیام تکمیل می‌کنیم.

این جلسه بخشی از دورهٔ آپاچی در ۵۰ شب است. قرارداد نام‌ها، مسیرها و روش اعمال تغییر را در راهنمای دوره بخوان.

محیط: Apache با قابلیت upgrade=websocket از 2.4.47 به بعد یا معادل backport مستند؛ Python حداقل 3.10. در RHEL 9 از python3.12 طبق راهنما استفاده کن.

backend واقعی WebSocket#

روی VM وب، محیط مجازی بساز؛ دانلود بسته صرفاً مرحلهٔ آماده‌سازی lab است:

"$LAB_PYTHON" -m venv ~/apache-lab/ws-venv
~/apache-lab/ws-venv/bin/python -m pip install websockets
~/apache-lab/ws-venv/bin/python -m pip freeze > ~/apache-lab/ws-requirements.txt

فایل ~/apache-lab/ws.py را بساز:

import asyncio
from websockets.asyncio.server import serve

async def echo(ws):
    async for message in ws:
        await ws.send(message)

async def main():
    async with serve(echo, "127.0.0.1", 8765,
                     origins=["https://app.example.test"]):
        await asyncio.get_running_loop().create_future()

asyncio.run(main())

با Python همان venv اجرا کن. بررسی Origin از اتصال مرورگر با مبدأ ناخواسته جلوگیری می‌کند؛ جای احراز هویت نیست. در app پیش از نگاشت /:

ProxyPass /ws/ http://127.0.0.1:8765/ upgrade=websocket timeout=60
ProxyPassReverse /ws/ http://127.0.0.1:8765/

در نسخه‌های قدیمی‌تر، مسیر ws:// با mod_proxy_wstunnel طبق مستندات همان بسته لازم است. از upgrade=ANY یا NONE برای دور زدن بررسی پروتکل استفاده نکن.

تبادل پیام از کلاینت#

روی کلاینت venv مشابه بساز و نام app را در hosts به IP وب‌سرور نگاشت کن. فایل آزمون:

import asyncio, ssl
from pathlib import Path
from websockets.asyncio.client import connect

async def main():
    ctx = ssl.create_default_context(cafile=str(Path("~/apache-lab/pki/ca.crt").expanduser()))
    async with connect("wss://app.example.test/ws/", ssl=ctx,
                       origin="https://app.example.test", proxy=None) as ws:
        await ws.send("apache lab ping")
        print(await ws.recv())

asyncio.run(main())

این API با نسخهٔ فعلی کتابخانه است؛ نسخه را در requirements ثبت کن. باید همان پیام برگردد؛ Origin نامجاز باید رد شود. handshake 101 به‌تنهایی سلامت پیام‌ها را ثابت نمی‌کند.

بازگشت: دو نگاشت ws را بردار و backend را متوقف کن. reconnect، ping و timeout اتصال بیکار را در برنامه طراحی کن؛ WebSocket خام با پروتکل‌هایی مانند Socket.IO یکسان نیست.

منبع: mod_proxy_wstunnel ، سرور asyncio کتابخانهٔ websockets ، کلاینت asyncio .