Django REST Framework – WebSocket

In this article we will build an application which uses JWT Authentication that communicates to websocket with Django REST Framework. The main focus of this article is send data to websocket from out of consumer.

Before we start you can find the sourc…


This content originally appeared on DEV Community and was authored by Burak Yılmaz

In this article we will build an application which uses JWT Authentication that communicates to websocket with Django REST Framework. The main focus of this article is send data to websocket from out of consumer.

Before we start you can find the source code here

We have two requests to same endpoint GET and POST, whenever a request comes to endpoint we will send messages to websocket.

This is our models.py, our Message model looks like down below which is very simple.

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Message(models.Model):
    message = models.JSONField()
    user = models.ForeignKey(to=User, on_delete=models.CASCADE, null=True, blank=True)

Configurations

We need a routing.py file which includes url's of related app.

from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r'msg/', consumers.ChatConsumer.as_asgi()),
]

Then we need another routing.py file into project's core directory.

import os

from websocket.middlewares import WebSocketJWTAuthMiddleware
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application

from websocket import routing

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_ws.settings")


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

As you can see here a middleware is used, what this middleware does is control the authentication process, if you follow this link you will see that Django have support for standard Django authentication, since we are using JWT Authentication a custom middleware is needed. In this project Rest Framework SimpleJWT was used, when create a connection we are sending token with a query-string which is NOT so secure. We set user to scope as down below.

from urllib.parse import parse_qs

from channels.db import database_sync_to_async
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from rest_framework_simplejwt.tokens import AccessToken, TokenError

User = get_user_model()


@database_sync_to_async
def get_user(user_id):
    try:
        return User.objects.get(id=user_id)
    except User.DoesNotExist:
        return AnonymousUser()


class WebSocketJWTAuthMiddleware:

    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        parsed_query_string = parse_qs(scope["query_string"])
        token = parsed_query_string.get(b"token")[0].decode("utf-8")

        try:
            access_token = AccessToken(token)
            scope["user"] = await get_user(access_token["user_id"])
        except TokenError:
            scope["user"] = AnonymousUser()

        return await self.app(scope, receive, send)

Last but not least settings.py file, we are using Redis as channel layer therefore we need to start a Redis server, we can do that with docker

docker run -p 6379:6379 -d redis:5

settings.py

ASGI_APPLICATION = 'django_ws.routing.application'

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [('0.0.0.0', 6379)],
        },
    },
}

Consumers

Here is our consumer.py file, since ChatConsumer is asynchronous when a database access is needed, related method needs a database_sync_to_async decorator.

websocket/consumer.py

import json

from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer
from django.contrib.auth.models import AnonymousUser

from websocket.models import Message


class ChatConsumer(AsyncWebsocketConsumer):
    groups = ["general"]

    async def connect(self):
        await self.accept()
        if self.scope["user"] is not AnonymousUser:
            self.user_id = self.scope["user"].id
            await self.channel_layer.group_add(f"{self.user_id}-message", self.channel_name)

    async def send_info_to_user_group(self, event):
        message = event["text"]
        await self.send(text_data=json.dumps(message))

    async def send_last_message(self, event):
        last_msg = await self.get_last_message(self.user_id)
        last_msg["status"] = event["text"]
        await self.send(text_data=json.dumps(last_msg))

    @database_sync_to_async
    def get_last_message(self, user_id):
        message = Message.objects.filter(user_id=user_id).last()
        return message.message

Views

As I mentioned at previous step our consumer is asynchronous we need to convert methods from async to sync just like the name of the function

views.py

from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from .models import Message


class MessageSendAPIView(APIView):
    permission_classes = (IsAuthenticated,)

    def get(self, request):
        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.group_send)(
            "general", {"type": "send_info_to_user_group",
                        "text": {"status": "done"}}
        )

        return Response({"status": True}, status=status.HTTP_200_OK)

    def post(self, request):
        msg = Message.objects.create(user=request.user, message={
                                     "message": request.data["message"]})
        socket_message = f"Message with id {msg.id} was created!"
        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.group_send)(
            f"{request.user.id}-message", {"type": "send_last_message",
                                           "text": socket_message}
        )

        return Response({"status": True}, status=status.HTTP_201_CREATED)

In this view's GET request we are sending message to channel's "general" group so everyone on this group will receive that message, if you check the consumers.py you will see that our default group is "general", on the other hand in POST request we are sending our message to a specified group in an other saying this message is only sent to a group with related user's id which means only this user receives the message.

Up to this point everything seems fine but we can't find out if it actually works fine until we try, so let's do it. I am using a Chrome extension to connect websocket.

Result of GET request

Result of GET request

POST request

POST request

NOTE: Python3.10.0 have compatibility issue with asyncio be sure not using this version.


This content originally appeared on DEV Community and was authored by Burak Yılmaz


Print Share Comment Cite Upload Translate Updates
APA

Burak Yılmaz | Sciencx (2022-01-21T19:47:53+00:00) Django REST Framework – WebSocket. Retrieved from https://www.scien.cx/2022/01/21/django-rest-framework-websocket/

MLA
" » Django REST Framework – WebSocket." Burak Yılmaz | Sciencx - Friday January 21, 2022, https://www.scien.cx/2022/01/21/django-rest-framework-websocket/
HARVARD
Burak Yılmaz | Sciencx Friday January 21, 2022 » Django REST Framework – WebSocket., viewed ,<https://www.scien.cx/2022/01/21/django-rest-framework-websocket/>
VANCOUVER
Burak Yılmaz | Sciencx - » Django REST Framework – WebSocket. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/01/21/django-rest-framework-websocket/
CHICAGO
" » Django REST Framework – WebSocket." Burak Yılmaz | Sciencx - Accessed . https://www.scien.cx/2022/01/21/django-rest-framework-websocket/
IEEE
" » Django REST Framework – WebSocket." Burak Yılmaz | Sciencx [Online]. Available: https://www.scien.cx/2022/01/21/django-rest-framework-websocket/. [Accessed: ]
rf:citation
» Django REST Framework – WebSocket | Burak Yılmaz | Sciencx | https://www.scien.cx/2022/01/21/django-rest-framework-websocket/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.