Flask و WebSockets: ساخت یک چت ساده

فهرست مطالب

Flask و WebSockets: ساخت یک چت بلادرنگ ساده

در دنیای پرشتاب وب امروز، تقاضا برای تجربه‌های کاربری بلادرنگ (real-time) بیش از هر زمان دیگری در حال افزایش است. از شبکه‌های اجتماعی و اپلیکیشن‌های همکاری تیمی گرفته تا بازی‌های آنلاین و پلتفرم‌های معاملاتی، نیاز به ارتباط دوطرفه و لحظه‌ای میان سرور و کلاینت، سنگ بنای بسیاری از نوآوری‌هاست. فریم‌ورک سبک و قدرتمند Flask، به همراه پروتکل WebSockets، ابزارهایی عالی را برای توسعه‌دهندگانی فراهم می‌آورند که به دنبال ساخت چنین اپلیکیشن‌هایی هستند. این مقاله به صورت عمیق به بررسی چگونگی ادغام Flask با WebSockets برای ساخت یک اپلیکیشن چت ساده اما کاربردی می‌پردازد. هدف ما ارائه یک راهنمای جامع و فنی است که هم جنبه‌های نظری و هم ملاحظات عملی پیاده‌سازی را برای مخاطبان متخصص پوشش دهد.

ما گام به گام از مفهوم HTTP و محدودیت‌های آن برای اپلیکیشن‌های بلادرنگ عبور کرده، به معرفی پروتکل WebSockets و مزایای آن می‌پردازیم، سپس نحوه راه‌اندازی محیط توسعه، انتخاب کتابخانه‌های مناسب مانند Flask-SocketIO، و در نهایت پیاده‌سازی کامل سمت سرور و کلاینت یک اپلیکیشن چت ساده را بررسی خواهیم کرد. همچنین به مباحث پیشرفته‌تر، مدیریت هویت کاربران، و ملاحظات امنیتی نیز خواهیم پرداخت تا تصویری کامل از این فرآیند ارائه دهیم. با ما همراه باشید تا قدرت Flask و WebSockets را در کنار یکدیگر کشف کنید.

HTTP و محدودیت‌های آن برای برنامه‌های بلادرنگ

قبل از اینکه به دنیای WebSockets قدم بگذاریم، ضروری است که با محدودیت‌های پروتکل HTTP آشنا شویم، به‌خصوص زمانی که صحبت از اپلیکیشن‌های بلادرنگ به میان می‌آید. HTTP، که ستون فقرات اینترنت مدرن را تشکیل می‌دهد، یک پروتکل بدون حالت (stateless) و در اصل “درخواست-پاسخ” (request-response) است. این یعنی:

  1. مدل درخواست-پاسخ: ارتباط توسط کلاینت آغاز می‌شود. کلاینت یک درخواست به سرور می‌فرستد و سرور یک پاسخ ارسال می‌کند. پس از ارسال پاسخ، اتصال معمولاً بسته می‌شود (در HTTP/1.0) یا برای مدتی کوتاه باز می‌ماند (در HTTP/1.1 با Keep-Alive). سرور نمی‌تواند به صورت فعالانه داده‌ای را به کلاینت بدون درخواست قبلی از سمت کلاینت “پوش” (push) کند.
  2. بدون حالت (Stateless): هر درخواست HTTP به صورت مستقل از درخواست‌های قبلی مدیریت می‌شود. سرور هیچ اطلاعاتی در مورد وضعیت ارتباط کلاینت بین درخواست‌ها نگهداری نمی‌کند. این ویژگی مقیاس‌پذیری HTTP را افزایش می‌دهد، اما برای اپلیکیشن‌های بلادرنگ که نیاز به حفظ وضعیت و ارتباط مداوم دارند، چالش‌برانگیز است.
  3. سربار (Overhead) بالای هدرها: هر درخواست و پاسخ HTTP شامل هدرهای متعددی است که اطلاعاتی مانند کوکی‌ها، نوع محتوا، کش و غیره را حمل می‌کنند. این هدرها، به‌ویژه در ارتباطات مکرر و کوچک، سربار قابل توجهی را به شبکه تحمیل می‌کنند.

برای غلبه بر این محدودیت‌ها در اپلیکیشن‌های بلادرنگ، روش‌های مختلفی ابداع شده‌اند که هر کدام دارای نقاط قوت و ضعف خاص خود هستند:

  • Polling (نظرسنجی): کلاینت به صورت دوره‌ای (مثلاً هر چند ثانیه) درخواست‌های HTTP جدیدی را به سرور ارسال می‌کند تا بررسی کند آیا داده جدیدی وجود دارد یا خیر. این روش ساده است اما ناکارآمدی‌های زیادی دارد: مصرف بالای منابع سرور و شبکه، و تاخیر در دریافت داده‌های جدید. اگر زمان نظرسنجی طولانی باشد، تاخیر افزایش می‌یابد؛ اگر کوتاه باشد، سربار شبکه و سرور افزایش می‌یابد.
  • Long Polling (نظرسنجی طولانی): کلاینت یک درخواست HTTP به سرور ارسال می‌کند و سرور پاسخ آن را تا زمانی که داده جدیدی در دسترس قرار گیرد، یا یک مهلت زمانی (timeout) مشخص منقضی شود، به تعویق می‌اندازد. پس از دریافت پاسخ، کلاینت بلافاصله درخواست جدیدی ارسال می‌کند. این روش تاخیر را کاهش می‌دهد اما همچنان دارای سربار نسبی HTTP است و مدیریت آن در سرور پیچیده‌تر است.
  • Server-Sent Events (SSE): این فناوری به سرور اجازه می‌دهد تا به صورت یک‌طرفه (uni-directional) داده‌ها را به کلاینت پوش کند. کلاینت یک اتصال HTTP را باز نگه می‌دارد و سرور می‌تواند در هر زمان داده‌های جدید را از طریق این اتصال ارسال کند. SSE برای مواردی که فقط نیاز به ارسال داده از سرور به کلاینت وجود دارد (مانند فیدهای خبری یا اعلان‌ها) مناسب است، اما برای ارتباط دوطرفه (مانند چت) کافی نیست.

هیچ یک از این روش‌ها راه حلی ایده‌آل برای ارتباطات دوطرفه و با تاخیر کم در مقیاس بزرگ ارائه نمی‌دهند. اینجا جایی است که WebSockets وارد عمل می‌شود و یک تغییر پارادایم در نحوه ارتباطات وب ایجاد می‌کند.

آشنایی با WebSockets

WebSockets یک پروتکل ارتباطی استاندارد شده است که امکان ایجاد کانال‌های ارتباطی تمام‌دوطرفه (full-duplex) و پایدار (persistent) را از طریق یک تک اتصال TCP فراهم می‌کند. به زبان ساده‌تر، به جای اینکه کلاینت برای هر بار دریافت یا ارسال داده مجبور به باز کردن یک اتصال جدید باشد (مانند HTTP)، WebSockets یک تونل ارتباطی باز و پایدار را بین کلاینت و سرور ایجاد می‌کند که از طریق آن هر دو طرف می‌توانند در هر زمان داده ارسال یا دریافت کنند. این ویژگی‌ها WebSockets را به گزینه‌ای ایده‌آل برای اپلیکیشن‌های بلادرنگ تبدیل می‌کند.

چگونه WebSockets کار می‌کند؟

  1. هندشیک (Handshake): ارتباط WebSocket با یک درخواست HTTP استاندارد آغاز می‌شود که به عنوان “هندشیک” شناخته می‌شود. کلاینت یک درخواست HTTP را به سرور ارسال می‌کند و هدر Upgrade: websocket و Connection: Upgrade را به همراه هدر Sec-WebSocket-Key در آن قرار می‌دهد.
  2. تایید سرور: اگر سرور از WebSockets پشتیبانی کند، با یک پاسخ HTTP با کد وضعیت 101 Switching Protocols و هدرهای Upgrade: websocket و Connection: Upgrade و Sec-WebSocket-Accept پاسخ می‌دهد. این پاسخ نشان‌دهنده موفقیت‌آمیز بودن فرآیند هندشیک است.
  3. اتصال پایدار: پس از هندشیک موفق، اتصال HTTP اولیه “بالا برده” (upgraded) می‌شود و به یک اتصال WebSocket پایدار تبدیل می‌گردد. از این نقطه به بعد، ارتباطات دیگر از طریق پروتکل HTTP انجام نمی‌شود، بلکه از طریق پروتکل WebSocket صورت می‌گیرد.
  4. ارتباط تمام‌دوطرفه: از این پس، سرور و کلاینت می‌توانند به صورت مستقل و همزمان داده‌ها را به یکدیگر ارسال کنند. این داده‌ها به صورت “فریم” (frames) ارسال می‌شوند که سربار بسیار کمتری نسبت به هدرهای HTTP دارند.

مزایای WebSockets

  • ارتباط بلادرنگ واقعی: با اتصال پایدار و تمام‌دوطرفه، داده‌ها می‌توانند به صورت آنی بین سرور و کلاینت منتقل شوند، که برای اپلیکیشن‌هایی مانند چت، بازی‌های آنلاین و استریم زنده حیاتی است.
  • سربار کم: پس از فاز هندشیک اولیه، فریم‌های داده WebSocket دارای سربار بسیار کمتری نسبت به درخواست‌ها و پاسخ‌های HTTP هستند. این باعث افزایش کارایی و کاهش مصرف پهنای باند می‌شود.
  • کاهش تاخیر (Latency): عدم نیاز به باز و بسته کردن مکرر اتصال برای هر تبادل داده، به طور قابل توجهی تاخیر را در ارتباطات کاهش می‌دهد.
  • سهولت پیاده‌سازی: با وجود کتابخانه‌ها و فریم‌ورک‌های مدرن، پیاده‌سازی WebSockets در سمت سرور و کلاینت نسبتاً آسان شده است.
  • پشتیبانی گسترده: تقریباً تمام مرورگرهای مدرن از WebSockets پشتیبانی می‌کنند، و کتابخانه‌های سمت سرور برای زبان‌های برنامه‌نویسی مختلفی در دسترس هستند.

با درک این مفاهیم، اکنون آماده‌ایم تا به سراغ ادغام Flask با این فناوری قدرتمند برویم.

چرا Flask برای WebSockets؟ انتخاب ابزار مناسب

فریم‌ورک‌های مختلفی برای توسعه برنامه‌های وب پایتون وجود دارند، از جمله Django، FastAPI و Flask. هر کدام ویژگی‌ها و فلسفه خاص خود را دارند. اما چرا Flask انتخاب مناسبی برای ادغام با WebSockets است، به‌خصوص برای یک اپلیکیشن چت ساده؟

  • سبک و Micro-framework: Flask یک “میکرو فریم‌ورک” است. این بدان معناست که هسته آن بسیار کوچک و مینیمال است و هیچ تصمیم از پیش تعیین‌شده‌ای در مورد پایگاه داده، ORM، یا حتی نحوه مدیریت تمپلیت‌ها به شما تحمیل نمی‌کند. این ویژگی به توسعه‌دهنده انعطاف‌پذیری زیادی می‌دهد تا تنها کتابخانه‌های مورد نیاز خود را انتخاب و اضافه کند. برای WebSockets، این انعطاف‌پذیری به ما اجازه می‌دهد تا به راحتی کتابخانه Flask-SocketIO را به پروژه اضافه کنیم بدون اینکه با بخش‌های غیرضروری فریم‌ورک دست و پنجه نرم کنیم.
  • سادگی و یادگیری آسان: منحنی یادگیری Flask نسبتاً هموار است. برای توسعه‌دهندگانی که با پایتون آشنایی دارند، شروع به کار با Flask و درک مفاهیم اصلی آن بسیار سریع است. این سادگی به ما کمک می‌کند تا به جای پیچیدگی‌های فریم‌ورک، بیشتر روی منطق اپلیکیشن چت و WebSockets تمرکز کنیم.
  • اکوسیستم غنی: با وجود سادگی هسته، Flask دارای یک اکوسیستم بسیار غنی از افزونه‌ها (extensions) است. Flask-SocketIO یکی از همین افزونه‌های قدرتمند است که کار ادغام Socket.IO (یک کتابخانه سطح بالا برای WebSockets) را با Flask بسیار آسان می‌کند. این افزونه از جزئیات پیچیده پروتکل WebSocket انتزاع ایجاد می‌کند و به ما اجازه می‌دهد با رویدادها و شنونده‌ها کار کنیم.
  • مقیاس‌پذیری: در حالی که Flask به خودی خود برای مدیریت تعداد زیادی اتصال WebSocket طراحی نشده است (زیرا Flask از مدل WSGI سنکرون استفاده می‌کند)، Flask-SocketIO با استفاده از موتورهای Asynchronous WSGI مانند eventlet یا gevent می‌تواند به طور موثر تعداد زیادی اتصال همزمان را مدیریت کند. این ترکیب Flask را برای ساخت اپلیکیشن‌های بلادرنگ با مقیاس‌پذیری خوب مناسب می‌سازد.

به طور خلاصه، Flask یک بستر عالی برای شروع پروژه‌هایی است که نیاز به کنترل بالا بر اجزا و انعطاف‌پذیری دارند. برای یک اپلیکیشن چت که نیازمند مدیریت ارتباطات بلادرنگ است، ترکیب سادگی Flask با قدرت Flask-SocketIO یک راه حل کارآمد و لذت‌بخش را فراهم می‌آورد.

پیش‌نیازها و راه‌اندازی محیط توسعه

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

پیش‌نیازها

  • پایتون (Python 3.7+): اطمینان حاصل کنید که پایتون ۳ بر روی سیستم شما نصب شده است. می‌توانید با دستور python3 --version در ترمینال آن را بررسی کنید.
  • مدیر بسته pip: pip معمولاً همراه با پایتون نصب می‌شود.

راه‌اندازی محیط مجازی (Virtual Environment)

استفاده از محیط مجازی بهترین روش برای مدیریت وابستگی‌های پروژه است. این کار از تداخل بسته‌ها بین پروژه‌های مختلف جلوگیری می‌کند.

  1. ایجاد پوشه پروژه:
    mkdir flask_chat_app
    cd flask_chat_app
  2. ایجاد محیط مجازی:
    python3 -m venv venv

    این دستور یک پوشه به نام venv ایجاد می‌کند که حاوی فایل‌های محیط مجازی شماست.

  3. فعال‌سازی محیط مجازی:
    • در لینوکس/macOS:
      source venv/bin/activate
    • در ویندوز (Command Prompt):
      venv\Scripts\activate.bat
    • در ویندوز (PowerShell):
      venv\Scripts\Activate.ps1

    پس از فعال‌سازی، نام محیط مجازی ((venv)) باید در ابتدای خط فرمان شما ظاهر شود.

نصب کتابخانه‌های مورد نیاز

اکنون که محیط مجازی فعال است، می‌توانیم کتابخانه‌های لازم را نصب کنیم:

pip install Flask Flask-SocketIO eventlet
  • Flask: فریم‌ورک اصلی وب.
  • Flask-SocketIO: افزونه‌ای برای ادغام Socket.IO با Flask. این افزونه امکان ایجاد ارتباطات بلادرنگ را با استفاده از پروتکل WebSocket فراهم می‌کند و در صورت عدم پشتیبانی مرورگر از WebSocket، به صورت خودکار به Long Polling یا سایر روش‌ها برمی‌گردد.
  • eventlet: یک کتابخانه concurrent برای پایتون که Flask-SocketIO از آن برای مدیریت تعداد زیادی اتصال همزمان به صورت غیرهمزمان (asynchronous) استفاده می‌کند. بدون این یا یک جایگزین مانند gevent، Flask-SocketIO به خوبی مقیاس‌پذیر نخواهد بود. شما می‌توانید به جای eventlet از gevent و gevent-websocket استفاده کنید (pip install Flask-SocketIO gevent gevent-websocket)، اما برای سادگی در این آموزش eventlet را انتخاب می‌کنیم.

پس از نصب، می‌توانید لیست بسته‌های نصب شده را با دستور pip list مشاهده کنید.

محیط توسعه شما اکنون آماده است و می‌توانید به سراغ ساختار پروژه و پیاده‌سازی کد بروید.

ساختار پروژه و کامپوننت‌های اصلی

یک ساختار پروژه سازمان‌یافته به نگهداری و مقیاس‌پذیری اپلیکیشن کمک می‌کند. برای اپلیکیشن چت ما، یک ساختار ساده اما کارآمد را پیشنهاد می‌کنیم:

flask_chat_app/
├── venv/                   # محیط مجازی پایتون
├── app.py                  # فایل اصلی Flask و SocketIO
├── templates/              # پوشه برای فایل‌های HTML
│   └── index.html          # صفحه اصلی چت
└── static/                 # پوشه برای فایل‌های استاتیک (CSS, JS)
    ├── css/
    │   └── style.css       # فایل CSS برای استایل‌دهی
    └── js/
        └── script.js       # فایل JavaScript سمت کلاینت برای SocketIO

بیایید به اختصار وظیفه هر کامپوننت را بررسی کنیم:

  • venv/: این پوشه شامل محیط مجازی پایتون و تمامی کتابخانه‌های نصب شده برای این پروژه است. این پوشه را نباید در کنترل نسخه (مانند Git) قرار دهید.
  • app.py: این فایل هسته اصلی اپلیکیشن Flask و مدیریت WebSockets را در بر می‌گیرد. تمامی منطق سمت سرور، تعریف مسیرها (routes)، و مدیریت رویدادهای Socket.IO در این فایل پیاده‌سازی می‌شود.
  • templates/: Flask از موتور تمپلیت Jinja2 برای رندر کردن فایل‌های HTML استفاده می‌کند. تمام فایل‌های HTML که توسط سرور Flask رندر می‌شوند، در این پوشه قرار می‌گیرند. در مثال ما، index.html رابط کاربری چت را فراهم می‌کند.
  • static/: این پوشه برای نگهداری فایل‌های استاتیک مانند CSS، JavaScript، تصاویر و فونت‌ها استفاده می‌شود. این فایل‌ها به صورت مستقیم توسط مرورگر کلاینت درخواست و دریافت می‌شوند.
    • static/css/style.css: شامل قوانین CSS برای طراحی و ظاهر اپلیکیشن چت.
    • static/js/script.js: شامل منطق سمت کلاینت برای اتصال به Socket.IO سرور، ارسال پیام‌ها و نمایش پیام‌های دریافتی.

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

پیاده‌سازی سمت سرور (Backend) با Flask-SocketIO

سمت سرور مسئولیت مدیریت اتصالات WebSocket، دریافت پیام‌ها، پردازش آنها و ارسال مجدد به سایر کلاینت‌ها را بر عهده دارد. ما از Flask و افزونه Flask-SocketIO برای این منظور استفاده خواهیم کرد.

فایل app.py را ایجاد و محتوای زیر را در آن قرار دهید:

# app.py

from flask import Flask, render_template, request, session, redirect, url_for
from flask_socketio import SocketIO, emit, join_room, leave_room
import eventlet # برای مدیریت همزمان اتصالات

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key_here' # کلید امنیتی برای سشن‌ها (بسیار مهم در تولید)
socketio = SocketIO(app, async_mode='eventlet', cors_allowed_origins="*") # * به معنی اجازه از هر مبدایی است، در محیط تولید باید محدود شود

# یک دیکشنری برای ذخیره نام‌های کاربری و شناسه‌های سشن
# این یک راه‌حل ساده برای این مثال است و برای محیط تولید نیاز به سیستم مدیریت کاربران پایدارتر دارد
users = {}

@app.route('/')
def index():
    """
    مسیر اصلی که صفحه چت را رندر می‌کند.
    اگر کاربر نام کاربری نداشته باشد، به صفحه ورود هدایت می‌شود.
    """
    if 'username' not in session:
        return redirect(url_for('login'))
    return render_template('index.html', username=session['username'])

@app.route('/login', methods=['GET', 'POST'])
def login():
    """
    صفحه ورود برای انتخاب نام کاربری.
    """
    if request.method == 'POST':
        username = request.form['username']
        if username:
            session['username'] = username
            return redirect(url_for('index'))
    return render_template('login.html') # این تمپلیت را بعداً ایجاد می‌کنیم

@app.route('/logout')
def logout():
    """
    خروج کاربر و پاک کردن سشن.
    """
    session.pop('username', None)
    return redirect(url_for('login'))

@socketio.on('connect')
def handle_connect():
    """
    رویداد هنگام اتصال یک کلاینت جدید.
    """
    if 'username' in session:
        username = session['username']
        sid = request.sid # Session ID مربوط به SocketIO
        users[sid] = username
        join_room('general_chat') # همه کاربران را به یک روم عمومی اضافه می‌کنیم
        print(f"User {username} (SID: {sid}) connected.")
        emit('user_joined', {'username': username}, room='general_chat')
        emit('status_message', {'msg': f'{username} به چت پیوست.'}, room='general_chat')
        emit('user_list', {'users': list(users.values())}, room='general_chat')
    else:
        print(f"Unauthorized client tried to connect, disconnecting: {request.sid}")
        # اگر کاربر احراز هویت نشده، اتصال SocketIO را قطع می‌کنیم
        emit('redirect', {'url': url_for('login')})
        return False # جلوگیری از ادامه اتصال

@socketio.on('disconnect')
def handle_disconnect():
    """
    رویداد هنگام قطع اتصال یک کلاینت.
    """
    sid = request.sid
    if sid in users:
        username = users.pop(sid)
        leave_room('general_chat')
        print(f"User {username} (SID: {sid}) disconnected.")
        emit('user_left', {'username': username}, room='general_chat')
        emit('status_message', {'msg': f'{username} چت را ترک کرد.'}, room='general_chat')
        emit('user_list', {'users': list(users.values())}, room='general_chat')

@socketio.on('message')
def handle_message(data):
    """
    رویداد هنگام دریافت پیام از کلاینت.
    پیام را به تمامی کلاینت‌های متصل در روم 'general_chat' ارسال می‌کند.
    """
    if 'username' in session:
        username = session['username']
        message = data.get('msg')
        if message:
            print(f"[{username}]: {message}")
            emit('new_message', {'username': username, 'msg': message}, room='general_chat')
    else:
        print(f"Unauthorized message received from SID: {request.sid}")
        emit('redirect', {'url': url_for('login')})

# برای اجرای اپلیکیشن در حالت توسعه
if __name__ == '__main__':
    # از eventlet.wsgi.server برای مدیریت اتصالات غیرهمزمان استفاده می‌کنیم
    # در محیط توسعه، Flask-SocketIO معمولاً خود eventlet را در صورت نصب تشخیص می‌دهد
    # و از آن استفاده می‌کند. اما صراحتاً مشخص کردن آن مطمئن‌تر است.
    # برای تولید باید از Gunicorn یا مشابه آن استفاده شود.
    print("Starting Flask-SocketIO server with Eventlet...")
    socketio.run(app, debug=True, host='0.0.0.0', port=5000)

توضیحات کد سمت سرور

  1. وارد کردن کتابخانه‌ها:
    • Flask: هسته فریم‌ورک وب.
    • render_template: برای رندر کردن فایل‌های HTML.
    • request, session, redirect, url_for: ابزارهای Flask برای مدیریت درخواست‌ها، سشن‌ها، و ریدایرکت‌ها.
    • SocketIO: کلاس اصلی از Flask-SocketIO.
    • emit: تابع برای ارسال رویدادها به کلاینت‌ها.
    • join_room, leave_room: توابع برای مدیریت کاربران در “روم”‌ها (گروه‌های چت).
    • eventlet: موتور Asynchronous WSGI برای عملکرد بهتر.
  2. راه‌اندازی Flask و SocketIO:
    • app = Flask(__name__): نمونه‌سازی اپلیکیشن Flask.
    • app.config['SECRET_KEY']: یک کلید امنیتی برای رمزنگاری داده‌های سشن. این را در محیط تولید به یک رشته تصادفی و پیچیده تغییر دهید!
    • socketio = SocketIO(app, async_mode='eventlet', cors_allowed_origins="*"): نمونه‌سازی SocketIO. async_mode='eventlet' به Flask-SocketIO می‌گوید از eventlet برای مدیریت همزمان استفاده کند. cors_allowed_origins="*" به منظور توسعه محلی است تا مرورگر با خطای CORS مواجه نشود؛ در محیط تولید، آن را به دامنه مشخص کلاینت خود تغییر دهید.
  3. مدیریت کاربران (users دیکشنری):

    یک دیکشنری ساده برای نگهداری نام کاربری هر کلاینت متصل با استفاده از request.sid (Session ID منحصر به فرد SocketIO). این یک روش ابتدایی است و برای محیط تولید نیاز به سیستم احراز هویت قوی‌تر و پایدارتر (مانند پایگاه داده) دارد.

  4. مسیرهای HTTP (@app.route):
    • / (index): صفحه اصلی چت را رندر می‌کند، اما ابتدا بررسی می‌کند که آیا کاربر نام کاربری در سشن دارد یا خیر. اگر نه، به صفحه ورود ریدایرکت می‌شود.
    • /login: صفحه ورود را نمایش می‌دهد و نام کاربری را از فرم دریافت کرده و در سشن ذخیره می‌کند.
    • /logout: نام کاربری را از سشن پاک کرده و به صفحه ورود ریدایرکت می‌کند.
  5. رویدادهای SocketIO (@socketio.on):
    • connect: این رویداد زمانی فعال می‌شود که یک کلاینت جدید به سرور SocketIO متصل می‌شود.
      • اگر کاربر نام کاربری در سشن داشته باشد، نام کاربری و SID آن را ذخیره می‌کند.
      • join_room('general_chat'): کلاینت را به یک “روم” به نام general_chat اضافه می‌کند. این به ما اجازه می‌دهد پیام‌ها را به تمام اعضای این روم ارسال کنیم.
      • پیام‌های user_joined و status_message را به سایر کاربران در روم ارسال می‌کند.
      • لیست کاربران فعال را با رویداد user_list به همه ارسال می‌کند.
      • اگر نام کاربری در سشن نباشد، اتصال قطع می‌شود و کلاینت به صفحه ورود ریدایرکت می‌شود.
    • disconnect: هنگامی که یک کلاینت قطع می‌شود (مثلاً مرورگر را می‌بندد یا اتصال شبکه از بین می‌رود) فعال می‌شود. نام کاربری را از لیست users حذف کرده و پیام user_left و status_message و user_list را به سایر کاربران ارسال می‌کند.
    • message: این رویداد توسط کلاینت‌ها برای ارسال پیام چت فعال می‌شود.
      • پیام دریافتی (data['msg']) را به همراه نام کاربری فرستنده دریافت می‌کند.
      • سپس با استفاده از emit('new_message', ..., room='general_chat')، این پیام را به تمام کلاینت‌های موجود در روم general_chat (به جز فرستنده اصلی، مگر اینکه include_self=True اضافه شود) ارسال می‌کند.
  6. اجرای اپلیکیشن:
    • if __name__ == '__main__':: بلاک استاندارد برای اجرای اسکریپت پایتون.
    • socketio.run(app, debug=True, host='0.0.0.0', port=5000): اپلیکیشن Flask را با استفاده از سرور SocketIO اجرا می‌کند. debug=True برای توسعه مفید است، host='0.0.0.0' اجازه اتصال از هر آدرسی را می‌دهد و port=5000 پورت پیش‌فرض را تنظیم می‌کند.

حالا که سمت سرور آماده است، به سمت کلاینت می‌رویم.

پیاده‌سازی سمت کلاینت (Frontend) با JavaScript

سمت کلاینت شامل یک صفحه HTML برای نمایش رابط کاربری، CSS برای استایل‌دهی و JavaScript برای مدیریت اتصال به Socket.IO و تعامل با کاربر است.

۱. فایل templates/login.html

این صفحه از کاربر می‌خواهد نام کاربری خود را وارد کند.

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ورود به چت - Flask WebSockets</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
    <div class="container">
        <h1>به چت خوش آمدید!</h1>
        <form method="POST" action="/login" class="login-form">
            <input type="text" name="username" placeholder="نام کاربری خود را وارد کنید" required>
            <button type="submit">ورود</button>
        </form>
    </div>
</body>
</html>

۲. فایل templates/index.html

این فایل ساختار اصلی رابط کاربری چت را فراهم می‌کند.

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>چت بلادرنگ Flask & WebSockets</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
    <!-- Socket.IO Client Library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.min.js"></script>
</head>
<body>
    <div class="container">
        <div class="chat-header">
            <h1>چت عمومی <span id="username-display">({{ username }})</span></h1>
            <a href="{{ url_for('logout') }}" class="logout-button">خروج</a>
        </div>
        
        <div class="chat-main">
            <div class="user-list-container">
                <h3>کاربران آنلاین</h3>
                <ul id="user-list" class="user-list">
                    <!-- Users will be populated here by JavaScript -->
                </ul>
            </div>
            <div class="chat-window">
                <div id="messages" class="messages">
                    <!-- Chat messages will be appended here -->
                </div>
                <form id="message-form" class="message-form">
                    <input type="text" id="message-input" placeholder="پیام خود را بنویسید..." autocomplete="off">
                    <button type="submit">ارسال</button>
                </form>
            </div>
        </div>
    </div>

    <script src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>

۳. فایل static/css/style.css

برای کمی زیبایی بصری، می‌توانید این استایل‌ها را اضافه کنید. (صرفاً برای عملکرد اصلی ضروری نیست)

body {
    font-family: 'Arial', sans-serif;
    background-color: #f4f7f6;
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    direction: rtl; /* برای پشتیبانی از زبان فارسی */
}

.container {
    background-color: #ffffff;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    width: 90%;
    max-width: 900px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.chat-header {
    background-color: #007bff;
    color: white;
    padding: 15px 20px;
    text-align: center;
    border-bottom: 1px solid #0056b3;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.chat-header h1 {
    margin: 0;
    font-size: 1.5em;
}

.logout-button {
    background-color: #dc3545;
    color: white;
    border: none;
    padding: 8px 15px;
    border-radius: 5px;
    cursor: pointer;
    font-size: 0.9em;
    text-decoration: none;
}

.logout-button:hover {
    background-color: #c82333;
}

.chat-main {
    display: flex;
    flex-direction: row-reverse; /* برای اینکه لیست کاربران در سمت راست باشد */
    height: 600px; /* ارتفاع ثابت برای پنجره چت */
}

.user-list-container {
    width: 200px;
    background-color: #e9ecef;
    padding: 15px;
    border-left: 1px solid #dee2e6;
    overflow-y: auto;
}

.user-list-container h3 {
    margin-top: 0;
    color: #343a40;
    font-size: 1.1em;
    border-bottom: 1px solid #ced4da;
    padding-bottom: 10px;
    margin-bottom: 15px;
}

.user-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.user-list li {
    padding: 8px 0;
    color: #495057;
    font-weight: bold;
}

.chat-window {
    flex-grow: 1;
    display: flex;
    flex-direction: column;
}

.messages {
    flex-grow: 1;
    padding: 20px;
    overflow-y: auto;
    background-color: #f8f9fa;
    border-bottom: 1px solid #dee2e6;
}

.message {
    margin-bottom: 10px;
    padding: 8px 12px;
    border-radius: 5px;
    max-width: 80%;
    word-wrap: break-word;
}

.message .username {
    font-weight: bold;
    color: #007bff;
    margin-bottom: 5px;
    display: block;
}

.message .msg-content {
    font-size: 0.95em;
    line-height: 1.4;
    color: #343a40;
}

.status-message {
    text-align: center;
    font-style: italic;
    color: #6c757d;
    font-size: 0.85em;
    margin: 15px 0;
}

.message-form {
    display: flex;
    padding: 15px 20px;
    background-color: #e9ecef;
    border-top: 1px solid #dee2e6;
}

.message-form input[type="text"] {
    flex-grow: 1;
    padding: 10px 15px;
    border: 1px solid #ced4da;
    border-radius: 20px;
    margin-left: 10px; /* برای فضای بین ورودی و دکمه */
    font-size: 1em;
    outline: none;
}

.message-form button {
    background-color: #28a745;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 20px;
    cursor: pointer;
    font-size: 1em;
}

.message-form button:hover {
    background-color: #218838;
}

/* Login Page Styles */
.login-form {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 30px;
    gap: 15px;
}

.login-form input[type="text"] {
    width: 80%;
    max-width: 300px;
    padding: 12px 18px;
    border: 1px solid #ced4da;
    border-radius: 25px;
    font-size: 1.1em;
    text-align: center;
}

.login-form button {
    width: 80%;
    max-width: 300px;
    padding: 12px 18px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 25px;
    cursor: pointer;
    font-size: 1.1em;
}

.login-form button:hover {
    background-color: #0056b3;
}

۴. فایل static/js/script.js

این فایل حاوی منطق JavaScript برای اتصال به Socket.IO سرور، ارسال و دریافت پیام‌ها و به‌روزرسانی رابط کاربری است.

// static/js/script.js

document.addEventListener('DOMContentLoaded', () => {
    // اتصال به سرور Socket.IO
    // اگر اپلیکیشن Flask روی پورت دیگری اجرا می‌شود، باید آدرس را اینجا مشخص کنید:
    // const socket = io('http://localhost:5000');
    const socket = io(); // به صورت پیش‌فرض به دامنه و پورت جاری متصل می‌شود

    const messageForm = document.getElementById('message-form');
    const messageInput = document.getElementById('message-input');
    const messagesDiv = document.getElementById('messages');
    const userListUl = document.getElementById('user-list');

    // تابع کمکی برای اضافه کردن پیام به پنجره چت
    const appendMessage = (username, msg, type = 'user') => {
        const messageElement = document.createElement('div');
        messageElement.classList.add('message');
        if (type === 'status') {
            messageElement.classList.add('status-message');
            messageElement.textContent = msg;
        } else {
            const usernameSpan = document.createElement('span');
            usernameSpan.classList.add('username');
            usernameSpan.textContent = username + ':';

            const msgContentSpan = document.createElement('span');
            msgContentSpan.classList.add('msg-content');
            msgContentSpan.textContent = msg;
            
            messageElement.appendChild(usernameSpan);
            messageElement.appendChild(msgContentSpan);
        }
        messagesDiv.appendChild(messageElement);
        messagesDiv.scrollTop = messagesDiv.scrollHeight; // اسکرول به پایین
    };

    // رویداد: اتصال به سرور موفقیت‌آمیز بود
    socket.on('connect', () => {
        console.log('Connected to server!');
    });

    // رویداد: قطع اتصال از سرور
    socket.on('disconnect', () => {
        console.log('Disconnected from server!');
        appendMessage('', 'اتصال شما به چت قطع شد.', 'status');
    });

    // رویداد: دریافت پیام جدید
    socket.on('new_message', (data) => {
        appendMessage(data.username, data.msg);
    });

    // رویداد: پیام وضعیت (مثل ورود/خروج کاربر)
    socket.on('status_message', (data) => {
        appendMessage('', data.msg, 'status');
    });

    // رویداد: به‌روزرسانی لیست کاربران
    socket.on('user_list', (data) => {
        userListUl.innerHTML = ''; // پاک کردن لیست فعلی
        data.users.forEach(username => {
            const li = document.createElement('li');
            li.textContent = username;
            userListUl.appendChild(li);
        });
    });

    // رویداد: هدایت به صفحه دیگر (مثلاً اگر احراز هویت نشود)
    socket.on('redirect', (data) => {
        window.location.href = data.url;
    });

    // ارسال پیام هنگام سابمیت فرم
    if (messageForm) {
        messageForm.addEventListener('submit', (e) => {
            e.preventDefault(); // جلوگیری از رفرش صفحه
            const msg = messageInput.value.trim();
            if (msg) {
                socket.emit('message', { 'msg': msg }); // ارسال رویداد 'message' به سرور
                messageInput.value = ''; // پاک کردن ورودی
            }
        });
    }
});

توضیحات کد سمت کلاینت

  1. وارد کردن کتابخانه Socket.IO:

    با تگ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.min.js"></script> کتابخانه کلاینت Socket.IO را از CDN بارگذاری می‌کنیم. این کتابخانه شامل متدهای مورد نیاز برای برقراری ارتباط با سرور Socket.IO است.

  2. اتصال به سرور:

    const socket = io(); یک اتصال به سرور Socket.IO برقرار می‌کند. به صورت پیش‌فرض، این متد به همان دامنه و پورتی که صفحه HTML از آن سرو شده است، متصل می‌شود. اگر سرور Flask روی پورت یا دامنه دیگری اجرا می‌شود، باید آدرس کامل را به آن بدهید.

  3. شنیدن رویدادها (socket.on):
    • connect: زمانی که اتصال با سرور Socket.IO برقرار می‌شود، این رویداد فعال می‌شود.
    • disconnect: زمانی که اتصال با سرور قطع می‌شود.
    • new_message: این رویداد توسط سرور ارسال می‌شود (با emit('new_message', ...)) و حاوی پیام جدیدی است که باید در پنجره چت نمایش داده شود. تابع appendMessage مسئول اضافه کردن آن به DOM است.
    • status_message: برای نمایش پیام‌های سیستمی مانند ورود یا خروج کاربران.
    • user_list: برای به‌روزرسانی لیست کاربران آنلاین.
    • redirect: اگر سرور تشخیص دهد که کلاینت احراز هویت نشده، یک رویداد redirect ارسال می‌کند و کلاینت را به صفحه ورود هدایت می‌کند.
  4. ارسال پیام‌ها (socket.emit):
    • فرم ارسال پیام (message-form) یک eventListener برای رویداد submit دارد.
    • هنگام سابمیت فرم، ابتدا از رفرش شدن صفحه (رفتار پیش‌فرض فرم) جلوگیری می‌شود (e.preventDefault()).
    • متن ورودی گرفته شده و اگر خالی نباشد، با socket.emit('message', {'msg': msg}) به سرور ارسال می‌شود. ‘message’ نام رویدادی است که سرور منتظر آن است.
  5. appendMessage:

    این تابع یک پیام را به عنصر <div id="messages"> اضافه می‌کند و اطمینان حاصل می‌کند که پنجره چت به صورت خودکار به پایین اسکرول شود تا جدیدترین پیام قابل مشاهده باشد.

مدیریت هویت کاربران و نام‌های کاربری

در اپلیکیشن چت ما، مدیریت هویت کاربران به سادگی با استفاده از سشن‌های Flask و نام‌های کاربری ورودی انجام می‌شود. این روش برای یک اپلیکیشن چت ساده که هدف آن نمایش مفهوم WebSockets است، کافی است، اما در محیط تولید باید به آن با دقت بیشتری نگریست.

چگونگی عملکرد در اپلیکیشن ما:

  1. ورود کاربر (/login):
    • هنگامی که کاربری به مسیر /login می‌رود و نام کاربری خود را در فرم وارد می‌کند، این نام کاربری در session['username'] ذخیره می‌شود.
    • Flask از کوکی‌های رمزنگاری شده برای ذخیره سشن در سمت کلاینت استفاده می‌کند که با app.config['SECRET_KEY'] رمزنگاری می‌شوند.
  2. دسترسی به نام کاربری در سرور:
    • در مسیر /، نام کاربری از session['username'] خوانده شده و به تمپلیت index.html ارسال می‌شود.
    • در رویدادهای Socket.IO (مانند connect و message)، می‌توانیم با دسترسی به session['username'] نام کاربری کاربر متصل را شناسایی کنیم. این مکانیزم به Flask-SocketIO اجازه می‌دهد تا به سشن Flask در داخل توابع رویداد Socket.IO دسترسی پیدا کند.
  3. لیست کاربران آنلاین:
    • دیکشنری users = {} در app.py، Session ID (request.sid) هر اتصال Socket.IO را به نام کاربری مربوطه نگاشت می‌کند.
    • هنگام اتصال کاربر جدید (connect)، نام کاربری و SID آن به این دیکشنری اضافه می‌شود.
    • هنگام قطع اتصال (disconnect)، ورودی مربوطه از دیکشنری حذف می‌شود.
    • هر زمان که لیست کاربران نیاز به به‌روزرسانی داشته باشد، محتوای users.values() (که لیستی از نام‌های کاربری است) به تمام کلاینت‌ها ارسال می‌شود.
  4. خروج کاربر (/logout):
    • مسیر /logout سشن کاربر را پاک می‌کند (session.pop('username', None)) و او را به صفحه ورود هدایت می‌کند.

محدودیت‌ها و ملاحظات برای محیط تولید:

  • استمرار سشن: سشن‌های Flask به صورت پیش‌فرض در کوکی‌های مرورگر ذخیره می‌شوند. اگر سرور Flask ری‌استارت شود، لیست users بازنشانی می‌شود. در یک اپلیکیشن تولیدی، باید از یک سیستم مدیریت سشن پایدار (مانند Redis یا پایگاه داده) برای ذخیره اطلاعات کاربران آنلاین و سشن‌ها استفاده کنید تا حتی با ری‌استارت شدن سرور، وضعیت حفظ شود.
  • احراز هویت واقعی: در اینجا، هر کسی می‌تواند هر نام کاربری را انتخاب کند. در یک اپلیکیشن واقعی، کاربران باید احراز هویت شوند (مثلاً با نام کاربری و رمز عبور) و هویت آنها از طریق توکن‌ها (مانند JWT) یا سشن‌های امن‌تر مدیریت شود. این توکن‌ها می‌توانند هنگام اتصال به Socket.IO برای تأیید هویت ارسال شوند.
  • مدیریت همزمان کاربران: دیکشنری users در یک محیط چندرشته‌ای (multi-threaded) یا چندپروسه‌ای (multi-processed) ممکن است به درستی کار نکند، مگر اینکه با مکانیزم‌های قفل (locking mechanisms) محافظت شود. eventlet یا gevent به شما کمک می‌کنند تا نگرانی کمتری در مورد این مسائل داشته باشید، اما در مقیاس بسیار بزرگ، باید به راه حل‌های توزیع‌شده فکر کنید.
  • تداخل نام‌های کاربری: در حال حاضر، دو کاربر می‌توانند نام کاربری یکسان داشته باشند که ممکن است منجر به سردرگمی شود. در یک سیستم واقعی، باید از منحصر به فرد بودن نام‌های کاربری اطمینان حاصل کنید.

با وجود سادگی، این سیستم مدیریت نام کاربری، مفهوم اصلی اتصال هویت HTTP (سشن Flask) با اتصال WebSocket (Socket.IO SID) را به خوبی نشان می‌دهد و یک پایه محکم برای ساخت سیستم‌های پیچیده‌تر است.

افزودن امکانات پیشرفته‌تر (ایده‌ها و مسیرهای آینده)

اپلیکیشن چتی که ساختیم یک شروع عالی است، اما دنیای اپلیکیشن‌های بلادرنگ بسیار گسترده‌تر است. در اینجا چند ایده برای گسترش این پروژه و افزودن امکانات پیشرفته‌تر آورده شده است:

۱. چت خصوصی (Private Messaging)

چالش: ارسال پیام تنها به یک کاربر خاص.
پیاده‌سازی:

  • در سمت کلاینت: نیاز به یک رابط کاربری برای انتخاب گیرنده پیام. ممکن است با کلیک روی نام کاربر در لیست کاربران آنلاین، یک پنجره چت خصوصی باز شود.
  • در سمت سرور: رویداد emit() در Flask-SocketIO یک آرگومان to دارد که به شما اجازه می‌دهد پیام را به یک SID خاص یا یک “روم” خاص ارسال کنید.
    • می‌توانید برای هر دو کاربر در یک چت خصوصی، یک “روم” منحصر به فرد ایجاد کنید (مثلاً با ترکیب SIDهای آن‌ها یا IDهای کاربرانشان).
    • هنگام ارسال پیام خصوصی، کلاینت باید SID یا ID کاربر مقصد را به سرور ارسال کند.

۲. تاریخچه پیام‌ها (Message History)

چالش: نمایش پیام‌های قبلی به کاربران جدید یا کاربرانی که مجدداً متصل می‌شوند.
پیاده‌سازی:

  • پایگاه داده: تمام پیام‌ها را در یک پایگاه داده (مانند SQLite، PostgreSQL، MongoDB) ذخیره کنید.
  • هنگام اتصال: وقتی کاربری به چت متصل می‌شود، ۱۰ یا ۲۰ پیام آخر را از پایگاه داده بازیابی کرده و به صورت خصوصی به او ارسال کنید (با استفاده از emit(..., to=request.sid)).

۳. لیست کاربران حاضر در روم‌ها

چالش: مشاهده اینکه چه کسانی در کدام روم چت حضور دارند.
پیاده‌سازی:

  • در سمت سرور، می‌توانید از socketio.server.manager.rooms برای مشاهده ساختار روم‌ها و اعضای آن‌ها استفاده کنید.
  • رویدادهای جدیدی برای درخواست و ارسال لیست اعضای یک روم خاص ایجاد کنید.

۴. تایپینگ ایندیکاتور (Typing Indicator)

چالش: نمایش “فلان شخص در حال تایپ کردن است…”.
پیاده‌سازی:

  • در سمت کلاینت: زمانی که کاربر شروع به تایپ می‌کند (رویداد keydown در ورودی پیام)، یک رویداد typing_start به سرور ارسال کند. پس از چند ثانیه عدم فعالیت یا ارسال پیام، یک رویداد typing_stop ارسال کند.
  • در سمت سرور: رویدادهای typing_start و typing_stop را دریافت کرده و به سایر کاربران در همان روم ارسال کند.
  • در سمت کلاینت: این رویدادها را دریافت کرده و یک پیام وضعیت موقت (مثلاً در کنار نام کاربر) نمایش دهد.

۵. احراز هویت و مجوزدهی پیشرفته

چالش: اطمینان از اینکه فقط کاربران مجاز می‌توانند به چت بپیوندند و عملیات خاصی را انجام دهند (مثلاً فقط ادمین‌ها می‌توانند کاربران را مسدود کنند).
پیاده‌سازی:

  • سیستم کاربران/احراز هویت: از یک سیستم احراز هویت کامل (مانند Flask-Login یا Flask-Security) برای مدیریت کاربران و لاگین آن‌ها استفاده کنید.
  • توکن‌های JWT: پس از ورود موفق کاربر، یک توکن JWT (JSON Web Token) به او اختصاص دهید. کلاینت می‌تواند این توکن را هنگام اتصال به Socket.IO در هدرها یا به عنوان بخشی از پیام اولیه ارسال کند.
  • بررسی توکن در connect: در رویداد connect سمت سرور، توکن JWT را اعتبارسنجی کنید تا هویت و مجوزهای کاربر تأیید شود.

۶. نشانگر آنلاین/آفلاین

چالش: نمایش وضعیت آنلاین یا آفلاین بودن کاربران در لیست.
پیاده‌سازی:

  • این قابلیت تا حدودی با connect و disconnect و به‌روزرسانی user_list پوشش داده شده است.
  • می‌توانید یک نشانگر بصری (مثلاً یک دایره سبز/خاکستری) در کنار نام هر کاربر در لیست کاربران آنلاین اضافه کنید.

۷. استقرار (Deployment)

چالش: اجرای اپلیکیشن در یک محیط تولیدی.
پیاده‌سازی:

  • سرور WSGI: از یک سرور WSGI غیرهمزمان مانند Gunicorn به همراه eventlet یا gevent استفاده کنید: gunicorn --worker-class eventlet -w 1 app:app
  • پراکسی معکوس: جلوی Gunicorn یک پراکسی معکوس (Nginx یا Apache) قرار دهید تا درخواست‌ها را مدیریت کرده و اتصالات WebSocket را به درستی هدایت کند.
  • مقیاس‌پذیری: برای مقیاس‌بندی افقی، از یک پیام‌رسان (Message Queue) مانند Redis برای به اشتراک گذاشتن رویدادهای Socket.IO بین چندین نمونه سرور Flask-SocketIO استفاده کنید. Flask-SocketIO به خوبی با Redis کار می‌کند.

این ایده‌ها تنها نوک کوه یخ هستند و هر کدام می‌توانند به یک پروژه مستقل و پیچیده تبدیل شوند. با این حال، با پایه محکمی که از ادغام Flask و WebSockets ایجاد کرده‌اید، می‌توانید هر یک از این امکانات را به تدریج به اپلیکیشن خود اضافه کنید.

چالش‌ها و ملاحظات امنیتی

همانند هر اپلیکیشن وبی، توسعه اپلیکیشن چت بلادرنگ با Flask و WebSockets نیز با چالش‌ها و ملاحظات امنیتی خاص خود همراه است. نادیده گرفتن این موارد می‌تواند منجر به آسیب‌پذیری‌های جدی شود.

۱. اعتبارسنجی ورودی (Input Validation)

چالش: کاربران می‌توانند هر نوع داده‌ای را از طریق فیلد ورودی پیام ارسال کنند، از جمله اسکریپت‌های مخرب.
راه حل:

  • سمت سرور: همیشه قبل از ذخیره یا انتشار پیام‌ها، ورودی کاربر را اعتبارسنجی و پاک‌سازی (sanitize) کنید. از کتابخانه‌هایی مانند Bleach برای حذف تگ‌های HTML یا کاراکترهای خطرناک استفاده کنید. این کار به جلوگیری از حملات XSS (Cross-Site Scripting) کمک می‌کند.
  • سمت کلاینت: اگرچه اعتبارسنجی سمت کلاینت برای بهبود تجربه کاربری مفید است، هرگز نباید به آن اعتماد کرد، زیرا به راحتی قابل دور زدن است.

۲. حملات XSS (Cross-Site Scripting)

چالش: مهاجم می‌تواند اسکریپت‌های مخرب را در پیام‌های چت تزریق کند که در مرورگر سایر کاربران اجرا می‌شوند.
راه حل:

  • پاک‌سازی خروجی: تمامی محتوای تولید شده توسط کاربر را قبل از نمایش در HTML (چه در سمت سرور و چه در سمت کلاینت) به درستی escape کنید. Jinja2 به صورت پیش‌فرض این کار را برای متغیرها انجام می‌دهد، اما برای محتوای HTML که با JavaScript به DOM اضافه می‌شود، باید مراقب باشید.

۳. حملات CSRF (Cross-Site Request Forgery)

چالش: مهاجم می‌تواند کاربر را فریب دهد تا درخواست‌های ناخواسته‌ای را بدون اطلاع او به وب‌سایت شما ارسال کند.
راه حل:

  • توکن‌های CSRF: Flask-WTF به خوبی از توکن‌های CSRF پشتیبانی می‌کند. برای فرم‌های HTTP (مانند فرم لاگین)، از آن استفاده کنید.
  • WebSockets: پروتکل WebSocket به طور طبیعی کمتر در معرض CSRF است، زیرا هندشیک آن نیاز به هدر Sec-WebSocket-Key دارد که توسط مرورگر مدیریت می‌شود و کلاینت نمی‌تواند آن را به صورت دستی دستکاری کند. با این حال، اطمینان از اینکه سشن‌های HTTP به درستی محافظت می‌شوند (با توکن‌های CSRF) و فقط کاربران احراز هویت شده می‌توانند به Socket.IO متصل شوند، مهم است.

۴. احراز هویت و مجوزدهی (Authentication & Authorization)

چالش: اطمینان از اینکه فقط کاربران مجاز می‌توانند به چت دسترسی داشته باشند و عملیات خاصی را انجام دهند.
راه حل:

  • همانطور که قبلاً ذکر شد، از یک سیستم احراز هویت قوی (مانند Flask-Login) استفاده کنید.
  • قبل از اجازه دادن به اتصال WebSocket، هویت کاربر را بررسی کنید. (همانطور که در handle_connect چک می‌کنیم username در session باشد.)
  • برای ویژگی‌های پیشرفته‌تر (مانند چت خصوصی یا مدیریت ادمین)، مکانیزم‌های مجوزدهی را پیاده‌سازی کنید تا بررسی شود آیا کاربر دارای حقوق لازم برای انجام یک عمل خاص است یا خیر.

۵. حملات DoS (Denial of Service)

چالش: مهاجم می‌تواند با ارسال تعداد زیادی درخواست یا پیام، سرور را از کار بیندازد.
راه حل:

  • Rate Limiting: تعداد پیام‌هایی که یک کاربر می‌تواند در یک بازه زمانی ارسال کند، یا تعداد اتصالات جدیدی که می‌تواند برقرار کند، را محدود کنید. Flask-Limiter می‌تواند برای مسیرهای HTTP استفاده شود، و برای WebSockets باید منطق مشابهی را در رویدادهای Socket.IO پیاده‌سازی کنید.
  • اندازه پیام: حداکثر اندازه پیام‌های Socket.IO را محدود کنید تا از ارسال پیام‌های بسیار بزرگ و مصرف بیش از حد حافظه جلوگیری شود.
  • مدیریت منابع: از یک سرور WSGI مانند Gunicorn به همراه eventlet/gevent استفاده کنید که در مدیریت اتصالات همزمان و منابع بهتر عمل می‌کنند.

۶. افشای اطلاعات (Information Disclosure)

چالش: افشای اطلاعات حساس (مانند آدرس IP سرور، جزئیات خطا، اطلاعات کاربری).
راه حل:

  • خاموش کردن Debug Mode: هرگز debug=True را در محیط تولید استفاده نکنید، زیرا این کار اطلاعات حساس خطا را به مهاجم نشان می‌دهد.
  • Logging مناسب: از سیستم لاگینگ برای ثبت رویدادها و خطاها استفاده کنید، اما مراقب باشید اطلاعات شخصی قابل شناسایی (PII) را لاگ نکنید.
  • HTTPS/WSS: همیشه از HTTPS برای اتصالات HTTP و WSS (WebSocket Secure) برای اتصالات WebSocket استفاده کنید تا ارتباطات رمزنگاری شده و از شنود جلوگیری شود. برای این کار به گواهی SSL/TLS نیاز دارید.

۷. مدیریت روم‌ها (Room Management)

چالش: اگر روم‌ها به درستی مدیریت نشوند، کاربران می‌توانند به روم‌های خصوصی دسترسی پیدا کنند.
راه حل:

  • نام‌گذاری امن روم‌ها: نام‌های روم‌های خصوصی را با استفاده از UUID یا ترکیبی از IDهای کاربران به صورت رمزنگاری شده ایجاد کنید تا حدس زدن آن‌ها دشوار باشد.
  • مجوزدهی روم: قبل از اینکه کاربری را به یک روم join_room کنید، همیشه بررسی کنید که آیا کاربر مجوز لازم برای ورود به آن روم را دارد یا خیر.

با در نظر گرفتن این ملاحظات امنیتی و پیاده‌سازی راه حل‌های مناسب، می‌توانید یک اپلیکیشن چت بلادرنگ قوی‌تر و امن‌تر با Flask و WebSockets بسازید.

جمع‌بندی و گام‌های بعدی

در این مقاله جامع، ما سفر خود را با بررسی محدودیت‌های HTTP برای اپلیکیشن‌های بلادرنگ آغاز کردیم و سپس به معرفی پروتکل قدرتمند WebSockets و مزایای بی‌شمار آن پرداختیم. دیدیم که چگونه Flask، به عنوان یک میکرو فریم‌ورک پایتون، با کمک افزونه Flask-SocketIO، بستری ایده‌آل برای ساخت اپلیکیشن‌های بلادرنگ مانند یک چت ساده را فراهم می‌آورد.

ما گام به گام محیط توسعه را راه‌اندازی کرده، ساختار پروژه را مشخص نمودیم و سپس به پیاده‌سازی دقیق منطق سمت سرور با Flask-SocketIO و سمت کلاینت با HTML، CSS و JavaScript پرداختیم. نحوه مدیریت نام‌های کاربری و سشن‌ها را بررسی کردیم و به اهمیت پیوند دادن سشن‌های HTTP با اتصالات WebSocket پرداختیم.

در نهایت، به ایده‌هایی برای افزودن امکانات پیشرفته‌تر به اپلیکیشن چت خود پرداختیم، از جمله چت خصوصی، تاریخچه پیام‌ها، تایپینگ ایندیکاتور و احراز هویت پیشرفته. همچنین، چالش‌ها و ملاحظات امنیتی حیاتی مانند XSS، CSRF، اعتبارسنجی ورودی و حملات DoS را بررسی کرده و راهکارهایی برای مقابله با آن‌ها ارائه دادیم.

اکنون شما یک پایه محکم برای ساخت اپلیکیشن‌های بلادرنگ با Flask و WebSockets دارید. این دانش به شما امکان می‌دهد تا فراتر از یک چت ساده رفته و پروژه‌های پیچیده‌تری را در زمینه‌های مختلف مانند بازی‌های آنلاین، داشبوردهای بلادرنگ، ابزارهای همکاری و اعلان‌ها توسعه دهید.

گام‌های بعدی برای یادگیری و توسعه:

  1. آزمایش و تغییر: با کد موجود بازی کنید. قابلیت‌هایی مانند پیام‌های خصوصی یا دکمه “Like” برای پیام‌ها را اضافه کنید.
  2. استفاده از پایگاه داده: پیام‌ها را در یک پایگاه داده (مثلاً SQLite با SQLAlchemy) ذخیره کنید و هنگام اتصال، تاریخچه پیام‌ها را نمایش دهید.
  3. احراز هویت واقعی: سیستم احراز هویت و مجوزدهی را با استفاده از Flask-Login و توکن‌های JWT پیاده‌سازی کنید.
  4. مقیاس‌پذیری با Redis: برای مقیاس‌پذیری اپلیکیشن در محیط تولید، از Redis به عنوان بک‌ند (message queue) برای Flask-SocketIO استفاده کنید.
  5. استقرار: اپلیکیشن را در یک محیط واقعی (مانند Heroku، AWS، DigitalOcean) با استفاده از Gunicorn و Nginx/Apache مستقر کنید تا با چالش‌های استقرار اپلیکیشن‌های بلادرنگ آشنا شوید.
  6. کاوش در سایر کتابخانه‌ها: با کتابخانه‌های دیگری مانند FastAPI و Starlette که از قابلیت‌های غیرهمزمان پایتون (async/await) به صورت نیتیو پشتیبانی می‌کنند، آشنا شوید.

با تمرین و کاوش مداوم، مهارت‌های شما در توسعه وب بلادرنگ به اوج خواهد رسید. این سفر تنها آغاز راه است!

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان