Chat socket closed unexpectedly

I am deploying my chat application on Heroku and facing error when trying to send message, I don’t know how to solve it. When I run code locally it works fine if instead of:

const chatSocket = new WebSocket(
      "wss://" + window.location.host + "/ws/chat/" + roomName + "/"
    );

I use:

const chatSocket = new WebSocket(
      "ws://" + window.location.host + "/ws/chat/" + roomName + "/"
    );

Error in console:

JavaScript in chatroom.html:

const roomName = JSON.parse(document.getElementById("room-name").textContent);
    const chatSocket = new WebSocket(
      "wss://" + window.location.host + "/ws/chat/" + roomName + "/"
    );

    chatSocket.onmessage = function (e) {
      const data = JSON.parse(e.data);

      insertHtml(data.message);
      scrollMsg(chatLog);
    };

    chatSocket.onclose = function (e) {
      console.error("Chat socket closed unexpectedly");
    };

    document.querySelector("#chat-message-input").focus();
    console.log("da");
    document.querySelector("#chat-message-input").onkeyup = function (e) {
      if (e.keyCode === 13) {
        // enter, return
        console.log("da");
        document.querySelector("#chat-message-submit").click();
      }
    };

    document.querySelector("#chat-message-submit").onclick = function (e) {
      const messageInputDom = document.querySelector("#chat-message-input");
      const message = messageInputDom.value;
      const checkInput = message.replaceAll(/\s/g, "");
      if (!checkInput) return;
      chatSocket.onopen = () => {
        chatSocket.send(
          JSON.stringify({
            message: message,
          })
        );
      };
      messageInputDom.value = "";
    };

import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer

from .models import Messages

consumers.py

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
        self.room_group_name = "chat_%s" % self.room_name  
        
        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name, self.channel_name
        )
        self.accept()

    def disconnect(self, close_code):
        print('test1')
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name, self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]

        # Sender
        username = self.scope["user"].username
        finalMessage = (username + ': ' + message)
        
        # Save message in database
        Messages.objects.create(room_name = self.room_name, message = finalMessage)
        
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name, {"type": "chat_message", "message": finalMessage}
        )

    # Receive message from room group
    def chat_message(self, event):
        print('test3')
        message = event["message"]
        username = self.scope["user"].username
        self.send(text_data=json.dumps({'message': message,
        "username": username}))

asgi.py:


import os
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
django_asgi_app = get_asgi_application()

from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator
from anonymous.routing import websocket_urlpatterns


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


application = ProtocolTypeRouter(
    {
        "http": django_asgi_app,
        "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
        ),
    }
)

What asgi container are you using for this? Daphne? What is your Daphne command line?
Do you have HTTPS working on your regular Django application?

This is daphne I used in Proc file to deploy on Heroku daphne -b 0.0.0.0 -p 8001 core.asgi:application
When I run website on local everything works perfectly, I tested it. No bugs or any errors occurred while sending a message or doing any other acction.

You pointed out that you made a change. You switched from this:

to this:

which means you’re now trying to use a websocket with ssl. And that means that your server needs to be configured for ssl usage.

I don’t know anything about heroku here, so you’re going to need to find their docs to figure out how to configure it for ssl. (Also, I notice you’re not specifying a port number for the connection, so I’m guessing you’ve got some type of configuration file defined to forward port 443 to 8001.)

Thanks for the help, this clears up a lot to me. I have one question, what are you using to host your web applications?

I use complete server images - either self-hosted or services such as AWS EC2 and Linode. That way I have complete control over the configurations for docker, nginx, PostgreSQL, redis, postfix, celery, cron, uwsgi and/or Daphne, etc, etc, etc. Also, I do a fair amount of work on internal-only systems, where deployment to an external host wouldn’t work. About a quarter of the projects I work on get deployed to environments that don’t even have internet access.

1 Like

Okay, thanks for the insight. I will research those.

Here is what solved my problem:

In Procfile I needed to use this:
web: daphne core.asgi:application --port $PORT --bind 0.0.0.0 -v2
Instead of this:
web: gunicorn core.wsgi --log-file -

Because obviously, I needed to run the ASGI server, it was a dumb mistake I overlooked. Also, I added this part in my chartroom.html:

let wsStart = "ws://";
    if (window.location.protocol == "https:") {
      wsStart = "wss://";
    }

const chatSocket = new WebSocket(wsStart + window.location.host + "/ws/chat/" + roomName + "/");

This part automatically checks if there is HTTPS and configures the web socket accordingly so it uses WSS and not WS.