3

Problem Summary

I am sending data to a front-end (React component) using Django and web-sockets. When I run the app and send the data from my console everything works. When I use a button on the front-end to trigger a Django view that runs the same function, it does not work and generates a confusing error message.

I want to be able to click a front-end button which begins sending the data to the websocket.

I am new to Django, websockets and React and so respectfully ask you to be patient.

Overview

  1. Django back-end and React front-end connected using Django Channels (web-sockets).
  2. User clicks button on front-end, which does fetch() on Django REST API end-point.
  3. [NOT WORKING] The above endpoint's view begins sending data through the web-socket.
  4. Front-end is updated with this value.

Short Error Description

The error Traceback is long, so it is included at the end of this post. It begins with:

Internal Server Error: /api/run-create

And ends with:

ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host

What I've Tried

Sending Data Outside The Django View

  • The function below sends data to the web-socket.
  • Works perfectly when I run it in my console - front-end updates as expected.
  • Note: the same function causes the attached error when run from inside the Django view.
    import json
    import time
    
    import numpy as np
    import websocket
    
    
    def gen_fake_path(num_cities):
        path = list(np.random.choice(num_cities, num_cities, replace=False))
        path = [int(num) for num in path]
        return json.dumps({"path": path})
    
    
    def fake_run(num_cities, limit=1000):
        ws = websocket.WebSocket()
        ws.connect("ws://localhost:8000/ws/canvas_data")
        while limit:
            path_json = gen_fake_path(num_cities)
            print(f"Sending {path_json} (limit: {limit})")
            ws.send(path_json)
            time.sleep(3)
            limit -= 1
        print("Sending complete!")
        ws.close()
        return

Additional Detail

Relevant Files and Configuration

consumer.py

    class AsyncCanvasConsumer(AsyncWebsocketConsumer):
        async def connect(self):
            self.group_name = "dashboard"
            await self.channel_layer.group_add(self.group_name, self.channel_name)
            await self.accept()
    
        async def disconnect(self, close_code):
            await self.channel_layer.group_discard(self.group_name, self.channel_name)
    
        async def receive(self, text_data=None, bytes_data=None):
            print(f"Received: {text_data}")
            data = json.loads(text_data)
            to_send = {"type": "prep", "path": data["path"]}
            await self.channel_layer.group_send(self.group_name, to_send)
    
        async def prep(self, event):
            send_json = json.dumps({"path": event["path"]})
            await self.send(text_data=send_json)

Relevant views.py

    @api_view(["POST", "GET"])
    def run_create(request):
        serializer = RunSerializer(data=request.data)
        if not serializer.is_valid():
            return Response({"Bad Request": "Invalid data..."}, status=status.HTTP_400_BAD_REQUEST)
        # TODO: Do run here.
        serializer.save()
        fake_run(num_cities, limit=1000)
        return Response(serializer.data, status=status.HTTP_200_OK)

Relevant settings.py

    WSGI_APPLICATION = 'evolving_salesman.wsgi.application'
    ASGI_APPLICATION = 'evolving_salesman.asgi.application'
    
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels.layers.InMemoryChannelLayer"
        }
    }

Relevant routing.py

    websocket_url_pattern = [
        path("ws/canvas_data", AsyncCanvasConsumer.as_asgi()),
    ]

Full Error

https://pastebin.com/rnGhrgUw

EDIT: SOLUTION

The suggestion by Kunal Solanke solved the issue. Instead of using fake_run() I used the following:

    layer = get_channel_layer()
    for i in range(10):
        path = list(np.random.choice(4, 4, replace=False))
        path = [int(num) for num in path]
        async_to_sync(layer.group_send)("dashboard", {"type": "prep", "path": path})
        time.sleep(3)

1 Answer 1

1

Rather than creating a new connection from same server to itself , I'd suggest you to use the get_channel_layer utitlity .Because you are in the end increasing the server load by opening so many connections . Once you get the channel layer , you can simply do group send as we normally do to send evnets . You can read more about here

from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
def media_image(request,chat_id) :
    if request.method == "POST" :
        data = {}
        if request.FILES["media_image"] is not None :
            item = Image.objects.create(owner = request.user,file=request.FILES["media_image"])
            message=Message.objects.create(item =item,user=request.user )
            chat = Chat.objects.get(id=chat_id)
            chat.messages.add(message)
            layer = get_channel_layer()
            item = {
               "media_type": "image",
                "url" : item.file.url,
                "user" : request.user.username,
                'caption':item.title
            }
            async_to_sync(layer.group_send)(
                'chat_%s'%str(chat_id),
#this is the channel group name,which is defined inside your consumer"
                {
                    "type":"send_media",
                    "item" : item
                    
                }
            )

        return HttpResponse("media sent")

In the error log, I can see that the handshake succeded for the first iteration and failed for 2nd . You can check that by printing something in the for loop . If that's the case the handshake most probably failed due to mulitple connections . I don't know how many connections the Inmemrorycache supports from same origin,but that can be reason that the 2nd connection is getting diconnected . You can get some idea in channel docs.Try using redis if you don't want to change your code,its pretty easy if you are using linux .

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Kunal, this solved it! I used the group send from within the view, as you described.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.