مستندات Django Channels - معرفی

0 دقیقه

زمان مطالعه

0 رای

۳۷

بازدید

۱۴۰۵/۶/۳

تاریخ انتشار

به Channels خوش آمدید!

Channels پشتیبانی بومی Django از Viewهای غیرهمزمان (Asynchronous) را گسترش می‌دهد و به پروژه‌های Django اجازه می‌دهد علاوه بر HTTP، پروتکل‌هایی را هم مدیریت کنند که به اتصال‌های طولانی‌مدت نیاز دارند؛ مانند WebSocket، MQTT، چت‌بات‌ها، رادیوی آماتوری و موارد دیگر.

Channels این کار را در حالی انجام می‌دهد که ماهیت همزمان (Synchronous) و استفاده آسان Django را حفظ می‌کند. بنابراین شما می‌توانید خودتان انتخاب کنید که کدتان را چگونه بنویسید:

  • به‌صورت Synchronous، مشابه Viewهای معمولی Django
  • کاملاً Asynchronous
  • یا ترکیبی از هر دو

علاوه بر این، Channels با سیستم‌های داخلی Django مانند Authentication و Session یکپارچه می‌شود. در نتیجه، توسعه دادن پروژه‌ای که در ابتدا فقط با HTTP کار می‌کرد و اضافه کردن پروتکل‌های دیگر، ساده‌تر از همیشه خواهد بود.

Channels همچنین این معماری Event-Driven را همراه با چیزی به نام Channel Layer ارائه می‌کند. Channel Layer سیستمی است که به شما اجازه می‌دهد به‌سادگی بین Process های مختلف ارتباط برقرار کنید و پروژه خود را به Process های جداگانه تقسیم کنید.

اگر هنوز Channels را نصب نکرده‌اید، بهتر است ابتدا بخش Installation را بخوانید تا آن را نصب کنید.

این بخش یک آموزش قدم‌ به‌ قدم مستقیم نیست، اما باید بتوانید با استفاده از آن مفاهیم را دنبال کنید و در صورت تمایل، تغییراتی در یک پروژه Django موجود ایجاد کنید.


همه‌چیز در نهایت به یک چیز برمی‌گردد (Turtles All The Way Down)

Channels بر اساس یک اصل معروف به نام "Turtles All The Way Down" کار می‌کند.

ایده اصلی این است که ما فقط یک مفهوم واحد از یک Channels Application داریم و حتی ساده‌ترین نوع Consumer ــ که معادل View در Django است ــ خودش یک ASGI Application کاملاً معتبر محسوب می‌شود و می‌تواند به‌تنهایی اجرا شود.

نکته

ASGI نام استاندارد سرورهای Asynchronous است که Channels بر اساس آن ساخته شده است.

همان‌طور که WSGI طراحی شده تا شما بتوانید بین Serverها و Frameworkهای مختلف انتخاب کنید و به یک Server یا Framework خاص وابسته نباشید، ASGI نیز همین هدف را دنبال می‌کند.

بنابراین شما مجبور نیستید فقط از Channels و Server آن یعنی Daphne استفاده کنید.

Channels ابزارهایی در اختیار شما قرار می‌دهد تا این Consumer های پایه را ایجاد کنید.

هر Consumer می‌تواند یک بخش مستقل از برنامه باشد؛ برای مثال:

  • مدیریت پیام‌های Chat
  • ارسال Notification
  • دریافت پیام‌های WebSocket
  • مدیریت اتصال کاربران

سپس می‌توانید این Consumerها را با استفاده از چیزهایی مانند:

  • URL Routing
  • تشخیص Protocol
  • Middleware
  • و سایر ابزارها

به یک Application کامل تبدیل کنید.

ما HTTP و Application فعلی Django را بخشی از یک سیستم بزرگ‌تر در نظر می‌گیریم.

Viewهای سنتی Django همچنان در Channels وجود دارند و می‌توانید از آن‌ها استفاده کنید؛ به‌خصوص با پشتیبانی بومی Django از ASGI.

در کنار آن‌ها می‌توانید قابلیت‌های دیگری هم بنویسید، مثلاً:

  • HTTP Long Polling
  • WebSocket Receiver
  • پروتکل‌های سفارشی

و این کدها را در کنار کدهای فعلی پروژه Django خود قرار دهید.

حتی مواردی مانند:

  • URL Routing
  • Middleware

همگی در نهایت ASGI Application هستند.

ایده اصلی Channels این است که شما برای بیشتر قسمت‌های برنامه بتوانید از روش‌های ساده و امن و Synchronous مانند Viewهای Django استفاده کنید، اما هر زمان که برای انجام کارهای پیچیده‌تر نیاز داشتید، بتوانید به یک Interface مستقیم و Asynchronous بروید.


Scope و Eventها

Channels و ASGI، Connectionهای ورودی را به دو بخش اصلی تقسیم می‌کنند:

  1. Scope
  2. Event

Scope چیست؟

Scope مجموعه‌ای از اطلاعات مربوط به یک Connection ورودی است.

برای مثال در یک درخواست Web:

  • مسیر درخواست چیست؟
  • درخواست از چه IP آمده؟
  • HTTP Method چیست؟
  • Headerها چه هستند؟

یا در یک WebSocket:

  • اتصال از چه IP آمده؟
  • کاربر چه کسی است؟

یا در یک Chatbot:

  • کاربر چه کسی است؟
  • Username او چیست؟

Scope در طول عمر Connection باقی می‌ماند.

اما مدت زمان باقی ماندن Scope به نوع Protocol بستگی دارد.

در HTTP

Scope فقط به اندازه عمر یک Request وجود دارد.

یعنی:

Request
   ↓
Scope ایجاد می‌شود
   ↓
Request پردازش می‌شود
   ↓
Response
   ↓
Scope از بین می‌رود

در WebSocket

Scope به اندازه عمر WebSocket Connection باقی می‌ماند.

اگر WebSocket قطع شود و دوباره متصل شود، Scope جدیدی ساخته خواهد شد.

در Protocolهای دیگر

مدت زمان Scope به نحوه تعریف ASGI Specification آن Protocol بستگی دارد.

مثلاً احتمال دارد یک Chatbot برای تمام مدت گفت‌وگوی کاربر با Bot، یک Scope را باز نگه دارد؛ حتی اگر Protocol مربوط به Chat در سطح زیرین خودش Stateless باشد.


Event چیست؟

در طول عمر یک Scope، مجموعه‌ای از Eventها اتفاق می‌افتند.

Eventها نشان‌دهنده اتفاقاتی هستند که در Connection رخ می‌دهند.

مثلاً:

  • ارسال یک HTTP Request
  • دریافت یک WebSocket Message
  • ارسال یک WebSocket Frame
  • دریافت پیام از Chatbot

Application شما برای هر Scope یک بار ساخته می‌شود و سپس Eventهایی که در طول عمر آن Scope اتفاق می‌افتند، یکی‌یکی به آن داده می‌شوند.

Application بر اساس این Eventها تصمیم می‌گیرد چه کاری انجام دهد.


یک مثال با HTTP

فرض کنید کاربر یک HTTP Request ارسال می‌کند.

مراحل به این شکل هستند:

  1. کاربر یک HTTP Request ارسال می‌کند.
  2. یک Scope جدید از نوع http ایجاد می‌شود.
  3. اطلاعاتی مانند:
    • Path
    • Method
    • Headers
    • و سایر اطلاعات Request
      داخل Scope قرار می‌گیرند.
  4. سپس یک Event از نوع http.request ارسال می‌شود که Body مربوط به HTTP Request را شامل می‌شود.
  5. Channels یا ASGI Application این Event را پردازش می‌کند.
  6. Application یک Event از نوع http.response تولید می‌کند.
  7. Response به Browser ارسال می‌شود.
  8. Connection بسته می‌شود.
  9. Request/Response تمام می‌شود.
  10. Scope از بین می‌رود.

یک مثال با Chatbot

فرض کنید کاربر با یک Chatbot صحبت می‌کند.

مراحل:

  1. کاربر اولین پیام خود را برای Chatbot ارسال می‌کند.
  2. یک Scope ایجاد می‌شود که اطلاعاتی مانند:
    • Username
    • نام انتخاب‌شده توسط کاربر
    • User ID
      را نگه می‌دارد.
  3. Application یک Event از نوع:
chat.received_message

دریافت می‌کند که متن پیام کاربر را شامل می‌شود.

Application مجبور نیست به این پیام پاسخ دهد.

اما اگر بخواهد، می‌تواند یک یا چند پیام دیگر را با Eventهایی مانند:

chat.send_message

برای کاربر ارسال کند.

  1. کاربر پیام‌های بیشتری ارسال می‌کند.
  2. در نتیجه Eventهای بیشتری از نوع:
chat.received_message

ایجاد می‌شوند.
6. بعد از یک Timeout یا Restart شدن Process مربوط به Application، Scope بسته می‌شود.

بنابراین در طول عمر یک Scope ــ چه Scope مربوط به Chat باشد، چه HTTP Request، چه Socket Connection یا چیز دیگری ــ یک Application Instance مسئول مدیریت تمام Eventهای آن Scope خواهد بود.

حتی می‌توانید اطلاعاتی را روی همان Application Instance نگه دارید.

البته اگر بخواهید، می‌توانید مستقیماً یک Raw ASGI Application بنویسید؛ اما Channels یک Abstraction ساده‌تر و راحت‌تر به نام Consumer در اختیار شما قرار می‌دهد.


Consumer چیست؟

Consumer واحد پایه در کد Channels است.

به آن Consumer گفته می‌شود چون Eventها را مصرف (Consume) می‌کند.

اما می‌توانید Consumer را مثل یک Application کوچک و مستقل در نظر بگیرید.

وقتی یک Request یا یک Socket جدید وارد می‌شود، Channels بر اساس Routing Table خود ــ که کمی جلوتر درباره آن صحبت می‌کنیم ــ Consumer مناسب را برای آن Connection پیدا می‌کند و یک Instance از آن ایجاد می‌کند.

این موضوع باعث می‌شود Consumerها برخلاف Viewهای معمولی Django بتوانند Long-Running باشند.

البته Consumer می‌تواند کوتاه‌مدت هم باشد؛ برای مثال HTTP Requestها نیز می‌توانند توسط Consumerها مدیریت شوند.

اما طراحی Consumerها بر اساس این ایده است که برای مدتی زنده بمانند؛ یعنی به اندازه مدت عمر یک Scope.


یک Consumer ساده

مثلاً:

class ChatConsumer(WebsocketConsumer):

    def connect(self):
        self.username = "Anonymous"
        self.accept()
        self.send(text_data="[Welcome %s!]" % self.username)

    def receive(self, *, text_data):
        if text_data.startswith("/name"):
            self.username = text_data[5:].strip()
            self.send(text_data="[set your username to %s]" % self.username)
        else:
            self.send(text_data=self.username + ": " + text_data)

    def disconnect(self, message):
        pass

هر Protocol Eventهای متفاوتی دارد و هر نوع Event معمولاً توسط یک Method متفاوت مدیریت می‌شود.

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

Channels وظیفه دارد:

  • Eventها را زمان‌بندی کند.
  • آن‌ها را اجرا کند.
  • اجرای چند Event را در کنار یکدیگر مدیریت کند.

Consumerهای Synchronous

در لایه زیرین، Channels روی یک Asynchronous Event Loop اجرا می‌شود.

اما اگر Consumer خود را مانند مثال قبلی به‌صورت Synchronous بنویسید، Channels آن را در یک Thread همزمان (Synchronous Thread) اجرا می‌کند.

این موضوع به شما اجازه می‌دهد با خیال راحت عملیات Blocking انجام دهید؛ مثلاً از Django ORM استفاده کنید.

برای مثال:

class LogConsumer(WebsocketConsumer):

    def connect(self, message):
        Log.objects.create(
            type="connected",
            client=self.scope["client"],
        )

در اینجا Consumer هنگام اتصال کاربر، یک رکورد در Database ایجاد می‌کند.

چون این Consumer به شکل Synchronous اجرا می‌شود، استفاده از عملیات Blocking مانند Django ORM مشکلی ایجاد نمی‌کند.


Consumerهای Asynchronous

اگر کنترل بیشتری بخواهید و آماده باشید که فقط با Functionهای Asynchronous کار کنید، می‌توانید Consumer کاملاً Asynchronous بنویسید.

مثلاً:

class PingConsumer(AsyncConsumer):

    async def websocket_connect(self, message):
        await self.send({
            "type": "websocket.accept",
        })

    async def websocket_receive(self, message):
        await asyncio.sleep(1)

        await self.send({
            "type": "websocket.send",
            "text": "pong",
        })

اینجا همه چیز Asynchronous است.

برای مثال:

await asyncio.sleep(1)

به جای اینکه Thread را به مدت یک ثانیه متوقف کند، به Event Loop اجازه می‌دهد در این فاصله کارهای دیگری انجام دهد.

برای مطالعه بیشتر درباره Consumerها، می‌توانید بخش Consumers را مطالعه کنید.


Routing و چند Protocol

می‌توانید چند Consumer مختلف را که هرکدام خودشان یک ASGI Application هستند، با استفاده از Routing در قالب یک Application بزرگ‌تر ترکیب کنید.

مثلاً:

application = URLRouter([
    path("chat/admin/", AdminChatConsumer.as_asgi()),
    path("chat/", PublicChatConsumer.as_asgi()),
])

در اینجا:

اگر Request به:

/chat/admin/

برود، به:

AdminChatConsumer

ارسال می‌شود.

و اگر به:

/chat/

برود، به:

PublicChatConsumer

ارسال می‌شود.


Channels فقط برای HTTP و WebSocket نیست

Channels فقط برای HTTP و WebSocket طراحی نشده است.

بلکه اجازه می‌دهد تقریباً هر Protocol را وارد محیط Django کنید، به شرط اینکه Server مربوط به آن Protocol بتواند آن را به مجموعه‌ای مشابه از Eventها تبدیل کند.

مثلاً می‌توانید یک Chatbot بسازید.

class ChattyBotConsumer(SyncConsumer):

    def telegram_message(self, message):
        """
        Simple echo handler for telegram messages in any chat.
        """

        self.send({
            "type": "telegram.message",
            "text": "You said: %s" % message["text"],
        })

در این مثال Consumer یک Event به نام:

telegram_message

دریافت می‌کند.

سپس همان پیام را دوباره برای کاربر ارسال می‌کند.


استفاده از چند Protocol در یک پروژه

حالا می‌توانیم از یک Router دیگر استفاده کنیم تا یک پروژه بتواند همزمان چند Protocol مختلف را سرو کند.

مثلاً:

application = ProtocolTypeRouter({

    "websocket": URLRouter([
        path(
            "chat/admin/",
            AdminChatConsumer.as_asgi()
        ),

        path(
            "chat/",
            PublicChatConsumer.as_asgi()
        ),
    ]),

    "telegram": ChattyBotConsumer.as_asgi(),
})

اینجا:

                    Django Channels
                          │
             ┌────────────┴────────────┐
             │                         │
        websocket                  telegram
             │                         │
       URLRouter                ChattyBotConsumer
          │
     ┌────┴────┐
     │         │
   admin     public
     │         │
 AdminChat  PublicChat

بنابراین Channels به شما اجازه می‌دهد یک پروژه Django داشته باشید که همزمان Protocolهای مختلف را مدیریت کند.

هدف Channels این است که بتوانید پروژه‌های Django خود را برای کار با هر Protocol یا Transport مدرن موردنیاز توسعه دهید، در حالی که همچنان بتوانید از Componentها و سبک کدنویسی آشنای Django استفاده کنید.

برای اطلاعات بیشتر درباره Protocol Routing، بخش Routing را مطالعه کنید.


ارتباط بین Processها

مشابه یک WSGI Server معمولی، کدی که Eventهای مربوط به Protocol را مدیریت می‌کند، داخل خود Server Process اجرا می‌شود.

برای مثال:

کدی که WebSocketها را مدیریت می‌کند، داخل Process مربوط به WebSocket Server اجرا می‌شود.

هر Socket یا Connection مربوط به Application شما توسط یک Application Instance در یکی از این Serverها مدیریت می‌شود.

این Application Instance می‌تواند:

  • Event دریافت کند.
  • آن را پردازش کند.
  • مستقیماً اطلاعاتی برای Client ارسال کند.

اما وقتی Application شما پیچیده‌تر می‌شود، به ارتباط بین Application Instanceهای مختلف نیاز پیدا می‌کنید.

مثلاً فرض کنید یک Chat Room ساخته‌اید.

کاربر A یک پیام ارسال می‌کند.

Application Instance مربوط به کاربر A پیام را دریافت می‌کند.

اما باید بتواند آن پیام را برای Application Instanceهای دیگری که نماینده کاربران B، C، D و غیره هستند نیز ارسال کند.


Channel Layer چیست؟

می‌توانید برای حل این مشکل از Database استفاده کنید و دائماً Database را Poll کنید.

اما Channels مفهوم Channel Layer را معرفی می‌کند.

Channel Layer یک Abstraction سطح پایین روی مجموعه‌ای از Transportهاست که به شما اجازه می‌دهد بین Processهای مختلف اطلاعات ارسال کنید.

هر Application Instance یک Channel Name منحصر‌به‌فرد دارد.

همچنین می‌تواند عضو یک یا چند Group شود.

این موضوع اجازه می‌دهد هم ارتباط:

  • Point-to-Point

و هم:

  • Broadcast

داشته باشید.


نکته مهم

Channel Layer یک بخش اختیاری در Channels است.

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

برای این کار می‌توانید تنظیمات CHANNEL_LAYERS را خالی کنید.


ارسال پیام از طریق Channel Layer

مثلاً داخل یک Consumer:

self.channel_layer.send(
    'event',
    {
        'type': 'message',
        'channel': channel,
        'text': text,
    }
)

اینجا Consumer یک Event را از طریق Channel Layer ارسال می‌کند.


ارسال پیام به یک Process مشخص

همچنین می‌توانید پیام را برای یک Process مشخص که روی یک Channel Name ثابت در حال گوش دادن است ارسال کنید.

مثلاً:

self.channel_layer.send(
    "myproject.thumbnail_notifications",
    {
        "type": "thumbnail.generate",
        "id": 90902949,
    },
)

در اینجا یک پیام برای Channel مشخص زیر ارسال شده است:

myproject.thumbnail_notifications

و پیام می‌گوید:

thumbnail.generate

برای ID زیر ایجاد شود:

90902949

برای مطالعه بیشتر درباره Channel Layerها، بخش Channel Layers را ببینید.


یکپارچه‌سازی با Django

Channels پشتیبانی ساده و آماده‌ای برای بسیاری از قابلیت‌های معمول Django ارائه می‌دهد.

از جمله:

  • Session
  • Authentication

بنابراین می‌توانید Authentication را با WebSocketهای خود ترکیب کنید.

برای این کار کافی است Middleware مناسب را اطراف WebSocket قرار دهید.

مثلاً:

from django.core.asgi import get_asgi_application from django.urls import re_path

# Initialize Django ASGI application early to ensure the AppRegistry # is populated before importing code that may import ORM models. django_asgi_app = get_asgi_application()

from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack from channels.security.websocket import AllowedHostsOriginValidator

application = ProtocolTypeRouter({

    "http": django_asgi_app,

    "websocket": AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter([
                re_path(
                    r"^front(end)/$",
                    consumers.AsyncChatConsumer.as_asgi(),
                ),
            ])
        )
    ),
})

بیایید ساختار این کد را بررسی کنیم.

در ابتدا:

django_asgi_app = get_asgi_application()

Application مربوط به Django و ASGI ساخته می‌شود.

بعد:

application = ProtocolTypeRouter({

مشخص می‌کنیم که بر اساس Protocol ورودی، چه Applicationای باید اجرا شود.

برای HTTP:

"http": django_asgi_app,

یعنی Requestهای معمولی HTTP را به Application استاندارد Django بده.

اما برای WebSocket:

"websocket": ...

از Application مخصوص WebSocket استفاده کن.

سپس:

AllowedHostsOriginValidator(

برای بررسی Origin و Host مربوط به WebSocket استفاده می‌شود.

بعد:

AuthMiddlewareStack(

سیستم Authentication Django را وارد WebSocket می‌کند.

در نتیجه Consumer می‌تواند به اطلاعات کاربر دسترسی داشته باشد.

در نهایت:

URLRouter([
    re_path(
        r"^front(end)/$",
        consumers.AsyncChatConsumer.as_asgi(),
    ),
])

مشخص می‌کند که WebSocket مربوط به این مسیر باید توسط:

AsyncChatConsumer

مدیریت شود.


جمع‌بندی کلی

اگر بخواهیم کل Introduction را در یک تصویر ذهنی خلاصه کنیم، ساختار Channels تقریباً این است:

                         Django Channels
                               │
                    ┌──────────┴──────────┐
                    │                     │
                   HTTP                WebSocket
                    │                     │
              Django ASGI             Consumer
                 App                    │
                                        │
                              ┌─────────┴─────────┐
                              │                   │
                          Synchronous        Asynchronous
                          Consumer             Consumer
                              │                   │
                         Django ORM          async/await
                              │                   │
                              └─────────┬─────────┘
                                        │
                                  Channel Layer
                                        │
                         ┌──────────────┼──────────────┐
                         │              │              │
                      Process A      Process B      Process C
                         │              │              │
                         └──────────────┼──────────────┘
                                        │
                                  Groups / Events

مهم‌ترین مفاهیمی که از این Introduction باید در ذهنت بماند:

  • ASGI → استانداردی برای Applicationهای Asynchronous
  • Channels → قابلیت‌های Django را فراتر از HTTP گسترش می‌دهد.
  • Scope → اطلاعات و وضعیت مربوط به یک Connection در طول عمر آن
  • Event → اتفاقی که در طول عمر Scope رخ می‌دهد
  • Consumer → واحد اصلی کدنویسی در Channels که Eventها را دریافت و پردازش می‌کند.
  • Routing → مشخص می‌کند هر Connection یا Protocol به کدام Consumer برود.
  • ProtocolTypeRouter → Protocolهای مختلف مثل HTTP و WebSocket را از هم جدا می‌کند.
  • Channel Layer → امکان ارتباط بین Consumerها و Processهای مختلف را فراهم می‌کند.
  • Group → برای ارسال Broadcast به چند Connection استفاده می‌شود.
  • Sync Consumer → مناسب زمانی که می‌خواهی با کدهای Blocking مثل Django ORM کار کنی.
  • Async Consumer → برای کد کاملاً Asynchronous و کنترل بیشتر روی Event Loop.

و مهم‌تر از همه، Channels قرار نیست Django را کنار بگذارد؛ بلکه روی Django سوار می‌شود و به آن اجازه می‌دهد در کنار HTTP، با چیزهایی مثل WebSocket و Connectionهای طولانی‌مدت هم کار کند.

این محتوا چطور بود؟

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

شهریار سجادیه
شهریار سجادیه
500k دنبال‌کننده