Routing – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
內容目錄
A. 簡介
在使用 Django Channels 時,雖然消費者(consumers)本身就是有效的 ASGI 應用程式,但你不應該僅僅撰寫一個消費者並將其設置為提供給像 Daphne 這樣的協議伺服器的唯一應用程式。為了更靈活地管理應用程式,Channels 提供了路由(routing)類,可以讓你將消費者和其他有效的 ASGI 應用程式合併和堆疊,以便根據連接的性質進行分派。
Channels 的路由器僅在範圍(scope)層工作,而不是在個別事件的層次上運作,這意味著對於任何給定的連接,你只能指定一個消費者。路由的目的是決定要將一條連接交給哪個單一消費者,而非將一個連接的事件分散至多個消費者。
路由器本身也是有效的 ASGI 應用程式,而且可以嵌套。建議將 ProtocolTypeRouter 設為專案的根應用程式,這個應用程式是你會傳遞給協議伺服器的,然後在其中嵌套其他更具體的協議路由。Channels 預期你能夠定義一個單一的根應用程式,並透過 ASGI_APPLICATION 設定提供其路徑(類似於 Django 中的 ROOT_URLCONF 設定)。對於路由和根應用程式應放置在哪裡並沒有固定的規則,但建議遵循 Django 的慣例,將它們放在名為 asgi.py 的項目層級檔案中,並放置在 urls.py 的旁邊。
以下是asgi.py的範例:
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
from django.urls import path
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
# 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 chat.consumers import AdminChatConsumer, PublicChatConsumer
application = ProtocolTypeRouter({
# Django's ASGI application to handle traditional HTTP requests
"http": django_asgi_app,
# WebSocket chat handler
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter([
path("chat/admin/", AdminChatConsumer.as_asgi()),
path("chat/", PublicChatConsumer.as_asgi()),
])
)
),
})
在配置 Django Channels 的路由時,我們會使用 `as_asgi()` 類方法。這個方法會返回一個 ASGI 包裝應用程序,這樣每當有新的連線或 scope(範圍)時,就會自動實例化一個新的 consumer(消費者)實例。這種設計理念與 Django 的 `as_view()` 類似,後者是在每個請求中為 class-based views(基於類的視圖)創建相應的實例。
B. ProtocolTypeRouter
channels.routing.ProtocolTypeRouter
在建立 Django Channels 應用時,ASGI(Asynchronous Server Gateway Interface)應用程式堆疊的頂層應用,通常會是在路由檔案(routing file)中的主要入口。這個入口負責根據「範疇」(scope)中的 type 值,將請求分派給不同的其他 ASGI 應用。因此,協議會定義範疇內固定的 type 值,讓你可以利用這樣的資訊來辨別進來連線的類型。
具體來說,它需要一個參數:一個字典,鍵指的是 type 名稱,值則是提供這些連接類型服務的 ASGI 應用程式。這樣設計的目的是為了能夠柔性地處理不同類型的連接,例如 HTTP 請求、WebSocket 連接等等。
ProtocolTypeRouter({
"http": some_app,
"websocket": some_other_app,
})
此外,如果你希望在 HTTP 處理上區分長輪詢(long-poll)處理器與 Django 傳統的視圖,你可以在使用 URLRouter 路由時,把 Django 的 `get_asgi_application()` 指定為最後一個進入點,並使用一個「匹配所有」的模式(pattern)。這樣的設定會確保 Django 在其他特定 ASGI 處理應用都沒有匹配上請求時,作為最後的處理者來處理剩下的 HTTP 請求。
這種架構的好處是可擴展性:你可以在不改變整體結構的情況下,增加更多不同類型的 ASGI 應用處理不同的連接類型,或擴充新的功能,讓 Django Channels 的應用更加靈活和強大。
C. URLRouter
channels.routing.URLRouter
在 Django Channels 中,路由配置負責通過 HTTP 路徑來管理 HTTP 或 WebSocket 類型的連接。這個路由配置需要一個參數,即 Django URL 對象的列表,這些對象可以是 `path()` 或 `re_path()` 函數的結果。
URLRouter([
re_path(r"^longpoll/$", LongPollConsumer.as_asgi()),
re_path(r"^notifications/(?P<stream>\w+)/$", LongPollConsumer.as_asgi()),
re_path(r"", get_asgi_application()),
])
任何捕獲到的群組將以字典的形式在 scope 中提供,這個字典的鍵為 url_route,包含兩個鍵:kwargs 和 args。kwargs 鍵對應一個字典,該字典包含所有命名的正則表達式群組,而 args 鍵對應一個列表,包含位置正則表達式群組。
stream = self.scope["url_route"]["kwargs"]["stream"]
注意,命名和未命名的群組不能混用:一旦匹配到一個命名群組,位置群組將被拋棄。
from django.urls import re_path
from myapp import consumers
# 使用命名群組的路由定義
websocket_urlpatterns_named = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
# 使用位置群組的路由定義
websocket_urlpatterns_positional = [
re_path(r'ws/chat/(\w+)/$', consumers.ChatConsumer.as_asgi()),
]
在這個範例中,我們有兩個路由定義:
1. **命名群組路由**:使用 `(?P<room_name>\w+)`,這是一個命名的正則表達式群組,它會將匹配到的字串存入 `scope[‘url_route’][‘kwargs’]` 中,作為一個 key-value 項,被命名為 `room_name`。
2. **位置群組路由**:使用 `(\w+)`,這是一個位置的正則表達式群組,匹配的字串會存入 `scope[‘url_route’][‘args’]` 中,作為一個列表項。
若你嘗試在同一個路由中混用命名和位置群組,例如:
re_path(r'ws/chat/(?P<room_name>\w+)/(\d+)/$', consumers.ChatConsumer.as_asgi())
在這個例子中,`(?P<room_name>\w+)` 是命名群組,而 `(\d+)` 是未命名的(位置)群組。根據 Django Channels 的行為,一旦匹配到命名群組,所有位置群組將被丟棄,也就是說 `(\d+)` 的匹配結果不會被儲存,因此這種混用方式會導致資料無法被正確捕獲。這就是為何建議不要混用兩者的原因。
注意,如果 URLRouter 的內層路由器被額外的中介軟體包裹,那麼 path() 路徑形式的路由嵌套將無法正常運作。
假設我們有一個 Django Channels 配置想要通過 URLRouter 嵌套來管理不同的路徑,但我們還在使用中介軟體來處理某些請求:
from django.urls import path
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.middleware import MiddlewareStack
from myapp import consumers
# 定義頂層的路由
application = ProtocolTypeRouter({
# HTTP 協議不進行任何路由,直接傳遞
"http": URLRouter([
path("ws/", MiddlewareStack( # 在這裡使用中介軟體包裹 URLRouter
URLRouter([
path("chat/", consumers.ChatConsumer.as_asgi()),
])
)),
]),
})
在這個範例中,我們使用 `MiddlewareStack` 來包裹內層的 `URLRouter`。如果你使用的是 `path()`,而不是 `re_path()`,這種方式可能會引發問題。這是因為 `path()` 路由與中介軟體的某些交互可能會導致路徑未按預期匹配。
解決方法之一是避免這種嵌套的結構,或者在特定需要的情況下使用正則表達式(`re_path()`)來處理更複雜的匹配。
總結來說,當內層路由器包被額外的中介軟體包裹時,確保將可能存在的問題考慮在內,並根據需求選擇適當的路徑匹配方式。
D. ChannelNameRouter
channels.routing.ChannelNameRouter
根據 scope 中 channel 鍵的值來路由頻道類型範圍。此功能旨在用於 Worker 和背景任務上。它接收一個參數(字典資料格式),該字典將頻道名稱映射到為它們服務的 ASGI 應用程式。
ChannelNameRouter({
"thumbnails-generate": some_app,
"thumbnails-delete": some_other_app,
})
E. 總結
Django Channels 為 Django 應用程式提供了處理異步通信和長連接協議的能力。使用 Channels 時,應用程序的架構通常由不同的 ASGI 路由器組成,每個路由器負責將請求分派給適當的消費者(consumers)。在這個架構中,`ProtocolTypeRouter` 充當根路由器,區分不同的協議類型,例如 HTTP 和 WebSocket,並進行相應的處理。`URLRouter` 則利用 URL 路徑路由 HTTP 或 WebSocket 連接,其核心在於處理 URL 對象列表,以管理請求的路徑匹配。對於需要根據特定頻道名稱來處理的情況,`ChannelNameRouter` 提供了一個針對風格化命名頻道的映射解決方案,特別適合於後台的工作者任務。在這些工具的協作下,Django Channels 提供了靈活且可擴展的設計,簡化了日益增長的實時網絡功能需求。
F. 系列文章
-
簡介 – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
Django Channels 是擴展 Django 的一個框架,它增加了非同步支持和長連接協議支持,包括 WebSockets 和 MQTT,適合需要持久連接的應用場景。相對於WSGI,ASGI 支… [閱讀全文]
-
實時更新的多人聊天室 – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
文章介紹了如何使用 Django Channels 在 Django 架構中建立即時更新的多人聊天室。文章首先講解了安裝 Channels 這個套件的基本步驟,包括 Django 專案初始化、安裝相關… [閱讀全文]
-
Consumers深入說明 – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
文章中根據官方文件對Consumer的說明,簡單的翻譯成了繁體中文,同時也增加了些許補充。文章探討了 Django Channels 中 Consumers 的使用,強調其在支持異步通信和長連接協議上… [閱讀全文]
-
資料庫存取 – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
ASGI_THREADS, AsyncConsumer, close_old_connections, database_sync_to_async, django channels, Django ORM, SyncConsumer在 Django Channels 中,當處理資料庫存取時,需要區分同步和異步操作。同步消費者不需要額外處理,但異步消費者需使用 `database_sync_to_async` 或異步模型方法來安全… [閱讀全文]
-
Routing – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
Django Channels 提供了處理異步通信和長連接的能力,通過使用 ASGI 路由器來靈活地管理連接。核心元件包括 `ProtocolTypeRouter`,用於協議類型分派,如 HTTP 和… [閱讀全文]
-
Channel Layers – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
async_to_sync, channel, channel layer, channel layer specification, django channels, group, redis channel layer本文探討了 Django Channels 的通道層(Channel Layers),其允許應用實例之間進行非同步通信,適合用於分散式實時應用。通道層透過 `CHANNEL_LAYERS` 配置,支持… [閱讀全文]
-
Sessions、身份驗證、安全性 – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
authentication, channels-auth-token-middlewares, csrf, django channels, security, SessionMiddlewareStack, sessionsDjango Channels 支援異步通訊,並在會話、身份驗證和安全性上提供解決方案。它使用 SessionMiddlewareStack 來簡化會話管理,並透過 AuthMiddleware 支援… [閱讀全文]
-
Workers與背景任務 – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
Django Channels 和 Celery 是兩種用於處理背景任務和非同步操作的工具,各有其特定優勢。Django Channels 適用於即時通訊、WebSocket 連接和需要即時響應的應用… [閱讀全文]
-
部署 – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
部署 Django Channels(ASGI)應用程式類似於 WSGI,主要需要載入至伺服器如 Daphne,並可以設定通道層,通常會使用 Redis 伺服器。應用程式運行在協議伺服器中,兼容 HT… [閱讀全文]
-
ASGI – Django Channels:實現 WebSocket 異步通信,打造高效的長連接系統
ASGI(非同步伺服器網關介面)是一個規範,用於解綁Django Channels應用程式與特定伺服器,提供撰寫應用程式和中介軟體的共同方式。它採用一個異步可調用函數,包括範圍(Scope)字典、接收… [閱讀全文]