Как настроить использование веб-сокетов в проекте на Django REST framework с фронтендом на React?

Как настроить работу с вебсокетами в проекте, разделенном на backend (DRF) и frontend (React)? В полном Django проекте я использовал основанный на Django Channels подход, но сейчас нужно интегрировать компоненты на React и Django REST framework. Как правильно организовать взаимодействие через вебсокеты в DRF? Можно ли применить подход с Channels или следует использовать другие методы с учетом событийного подхода, предпочтительного для React?
  • 3 апреля 2024 г. 9:09
Ответы на вопрос 3
Для настройки использования веб-сокетов в проекте с Django REST framework в качестве backend и React в качестве frontend, можно использовать библиотеку Django Channels для обработки веб-сокетов совместно с DRF. 

Вот общий план действий для настройки веб-сокетов в таком проекте:

1. Установите библиотеку Django Channels:
```
pip install channels
```

2. Создайте файл routing для веб-сокетов в вашем Django проекте:
```python
# myapp/routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from myapp import consumers

application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter([
            path('ws/chat/', consumers.ChatConsumer),
        ]),
    ),
})
```

3. Создайте consumer для обработки веб-сокетов:
```python
# myapp/consumers.py
from channels.generic.websocket import WebsocketConsumer

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.accept()
        
    def disconnect(self, close_code):
        pass
    
    def receive(self, text_data=None, bytes_data=None):
        # Обработка сообщения от frontend
```

4. Используйте ваш consumer для взаимодействия с веб-сокетами в вашем Django проекте, например, через сигналы или другие методы.

5. На стороне frontend с React создайте соединение с веб-сокетом, например, используя WebSocket API:
```javascript
const socket = new WebSocket('ws://localhost:8000/ws/chat/');
```

6. Организуйте отправку и получение сообщений через веб-сокеты на frontend.

Это общий план действий, который позволит вам настроить работу с веб-сокетами в проекте с DRF и React. Помните, что вам также потребуется настроить CORS и обработку разрешений для веб-сокетов на стороне Django.
DRF это REST API, т.е. там архитектура построена на HTTP запросах, а в вебсокет их нет, получает что DRF тут боком и для вебсокетов он вообще не нужен. Правильно - вынести бизнес логику из DRF в отдельный слой, а в вебсокетах дергать только эту логику и ничего не знать про DRF 

UPD: Так же такой вариант, если REST API уже реализован, то написать для вебсокетов отдельный прокси, лучше не на джанго, а на starlette (fastapp скорее тут будут излишен), aiohttp или другом async фреймворки, который умеет в вебсокет. Так вот, этот прокси и будет уже дергать эндпоинты твой DRF, реальными http запросам
Я в проде запускал отдельный экземпляр приложения для web socket. Channels отлично справляется со своей задачей. Документация отлично описана, да и много гайдов есть. 
Как пример:
asgi.py
import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
get_asgi_app = get_asgi_application()

from notification import routing

application = ProtocolTypeRouter({
    "http": get_asgi_app,
    "websocket": AllowedHostsOriginValidator(
        URLRouter(
            routing.websocket_urlpatterns
        )
    ),
})


routing.py
from django.urls import path
from . import consumers

websocket_urlpatterns = [
    path(r'wss/notification/<str:room_name>/', consumers.NotificationConsumer.as_asgi()),
]


consumers.py
import json

from channels.generic.websocket import AsyncWebsocketConsumer

from service.auth.auth import get_auth


class NotificationConsumer(AsyncWebsocketConsumer):
    user = None

    async def connect(self):
        user = await get_auth(self.scope.get('query_string'))
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        max_connections_allowed = 1
        user_connections_key = f'user_connections_{user.id}'

        current_connections = self.scope.get(user_connections_key, 0)

        if current_connections >= max_connections_allowed:
            await self.close()
        else:
            self.scope[user_connections_key] = current_connections + 1
            await self.channel_layer.group_add(
                self.room_name,
                self.channel_name
            )
            if user.is_access_allowed():
                self.user = user
                await self.accept()

    async def disconnect(self, close_code):
        pass

    async def receive(self, text_data):
        await self.send_notification({'message': text_data})

    async def send_notification(self, message):
        message = message["message"]
        await self.send(text_data=json.dumps({"message": message}))


def send_notification(username, message, type_notification, created_at, id_notification, options=None):
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        f'notification-{username}',
        {'type': 'send.notification',
         'message': {'message': message, 'type_notification': type_notification, 'id': id_notification,
                     'created_at': created_at, 'notification_options': options}})


на фронте можно использовать нативные методы (образец из vue)
const socket = ref(null)
socket.value = new WebSocket(
      `ws://127.0.0.1:8000/wss/notification/notification-${user.username}/}`
    )
    socket.value.onopen = () => {
      console.log("otkrit")
    }
    socket.value.onclose = () => {
      console.log("zakrit")
    }

    socket.value.onerror = () => {
      console.log("error")
    }
    socket.value.onmessage = (message) => {
        const data = JSON.parse(message.data)
        // тут делаем что-то с данными
     })
Похожие вопросы