Search code examples
nginxwebsocketdjango-channelsdaphne

error "Failed to execute send on Websocket: Still in CONNECTING state" in Django Channels Production environment using SSL


I'm currently deploying an application using Django Channels in Production environment.

The target application is based on the sample app from the official django channel documentation. https://channels.readthedocs.io/en/latest/tutorial/index.html

I've confirmed that it works well in a production environment that does not use SSL, but it does not work well when changing the settings using SSL.

When executing the chatSocket.send function, the following error occurs in the developer tools console.

Uncaught DOMException: Failed to execute send on Websocket: Still in CONNECTING state

So I modified the code with reference to the article below, but chatSocket.readyState does not change from 0.

Uncaught InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state

How can I resolve this error?


Here are the codes:

room.html(javascript)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="100" rows="20"></textarea><br>
<input id="chat-message-input" type="text" size="100"><br>
<input id="chat-message-submit" type="button" value="Send">
{{ room_name|json_script:"room-name" }}
<script>
    const roomName = JSON.parse(document.getElementById('room-name').textContent);

    const ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";
    const daphne_port = window.location.protocol == "https:" ? ":8001/" : "/";
 
    const url = ws_scheme + '://' + window.location.host + daphne_port + 'ws/chat/' + roomName + '/';

    const chatSocket = new WebSocket(url);

    chatSocket.onmessage = function (e) {
        const data = JSON.parse(e.data);
        document.querySelector('#chat-log').value += (data.message + '\n');
    };

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

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

    function waitForConnection(callback, interval) {
        console.log('state:', chatSocket.readyState)
        if (chatSocket.readyState === 1) {
            callback();
        } else {
            const that = this;
            // optional: implement backoff for interval here
            setTimeout(function () {
                that.waitForConnection(callback, interval);
            }, interval);
        }
    };

    document.querySelector('#chat-message-submit').onclick = function (e) {
        const messageInputDom = document.querySelector('#chat-message-input');
        const message = messageInputDom.value;

        waitForConnection(function () {
            chatSocket.send(JSON.stringify({
                'message': message
            }));
            }, 1000
        )
        messageInputDom.value = '';
    };
</script>
</body>
</html>

daphne.service

[Unit]
Description=WebSocket Daphne Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/home/ubuntu/chat_test
ExecStart=/home/ubuntu/chat_test/venv/bin/python /home/ubuntu/chat_test/venv/bin/daphne -e ssl:8001:privateKey=/etc/letsencrypt/live/sample.com/privkey.pem:certKey=/etc/letsencrypt/live/sample.com/fullchain.pem chat_test.asgi:application
Restart=on-failure

[Install]
WantedBy=multi-user.target

nginx

server {
    listen 80;
    listen [::]:80;
    server_name sample.com;
    return 301 https://$host$request_uri;
}

server {
    listen 80;
    listen 443 ssl;
    server_name www.sample.com;
    ssl_certificate /etc/letsencrypt/live/sample.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/sample.com/privkey.pem;
    return 301 https://sample.com$request_uri;
}

server {
    listen 443 ssl default_server;
    server_name sample.com;
    ssl_certificate /etc/letsencrypt/live/sample.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/sample.com/privkey.pem;


    location =/fabicon.ico {access_log off; log_not_found off;}

    location /static{
            alias /usr/share/nginx/html/static;
        }

    location /media{
        alias /usr/share/nginx/html/media;
    }


    location /{
            include proxy_params;
            proxy_pass http://unix:/home/ubuntu/chat_test/chat_test.sock;
    }

    location /ws/ {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_redirect off;
        proxy_pass http://127.0.0.1:8001;

    }

}

updated:

I changed the nginx file, but still have the same error.

# location /ws/ {
#    proxy_http_version 1.1;
#    proxy_set_header Upgrade $http_upgrade;
#    proxy_set_header Connection "upgrade";
#    proxy_redirect off;
#    proxy_pass http://127.0.0.1:8001;
#
# }

↓    

location /ws/ {
    proxy_set_header Host               $http_host;
    proxy_set_header X-Real-IP          $remote_addr;
    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host   $server_name;
    proxy_set_header X-Forwarded-Proto  $scheme;
    proxy_set_header X-Url-Scheme       $scheme;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_redirect off;
    proxy_pass http://127.0.0.1:8001;

}

updated:

I changed room.html to Alexander's answer, but I still get the error.

The developer tools console show like this.

WebSocket connection to 'wss://sample.com/ws/chat/room/' failed:

Then I check /var/log/nginx/error.log , get the following error:

[error] 34188#34188: *10 upstream prematurely closed connection while reading response header from upstream, client: 123.456.789.0, server: sample.com, request: "GET /ws/chat/room/ HTTP/1.1", upstream: "http://127.0.0.1:8001/ws/chat/room/", host: "sample.com"

Then I check Daphne Service status with sudo systemctl status daphne, can see like below:

daphne.service - WebSocket Daphne Service
     Loaded: loaded (/etc/systemd/system/daphne.service; disabled; vendor preset: enabled)
     Active: active (running) since Sat 2024-03-30 14:03:14 UTC; 6min ago
   Main PID: 34858 (python)
      Tasks: 1 (limit: 4671)
     Memory: 43.0M
     CGroup: /system.slice/daphne.service
             └─34858 /home/ubuntu/chat_test/venv/bin/python /home/ubuntu/chat_test/venv/bin/daphne -e ssl:8001:privateKey=/etc/letsencrypt/live/sample.com/privkey.pem:certKey=/etc/letsencrypt/live/sample.com/fullcha>

Mar 30 14:03:14 ip-123-456-789-0 systemd[1]: Started WebSocket Daphne Service.
Mar 30 14:03:15 ip-123-456-789-0 python[34858]: 2024-03-30 05:03:15,114 INFO     Starting server at ssl:8001:privateKey=/etc/letsencrypt/live/sample.com/privkey.pem:certKey=/etc/letsencrypt/live/sample.com/fullchain>
Mar 30 14:03:15 ip-123-456-789-0 python[34858]: 2024-03-30 05:03:15,114 INFO     HTTP/2 support not enabled (install the http2 and tls Twisted extras)
Mar 30 14:03:15 ip-123-456-789-0 python[34858]: 2024-03-30 05:03:15,115 INFO     Configuring endpoint ssl:8001:privateKey=/etc/letsencrypt/live/sample.com/privkey.pem:certKey=/etc/letsencrypt/live/sample.com/fullcha>
Mar 30 14:03:15 ip-123-456-789-0 python[34858]: 2024-03-30 05:03:15,119 INFO     Listening on TCP address 0.0.0.0:8001

Then I checkdaphne.service info with ls -l, can see like below:

-rw-r--r-- 1 root root  455 Mar 23 20:41  daphne.service

Then I check ufw status with sudo ufw status like below:

To                         Action      From
--                         ------      ----
8000                       ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
8001                       ALLOW       Anywhere
22/tcp                     ALLOW       Anywhere
80                         ALLOW       Anywhere
443                        ALLOW       Anywhere
8000 (v6)                  ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)
8001 (v6)                  ALLOW       Anywhere (v6)
22/tcp (v6)                ALLOW       Anywhere (v6)
80 (v6)                    ALLOW       Anywhere (v6)
443 (v6)                   ALLOW       Anywhere (v6)

ubuntu: 20.40

python: 3.8.0

django: 4.2

channels: 4.0.0

daphne: 4.1.0

nginx: 1.18.0


Solution

  • From what I can tell it has to do with your javascript in room.html, after some experimenting myself, I believe you can get rid of this daphne_port constant.

    const url = ws_scheme + '://' + window.location.host + '/ws/chat/' + roomName + '/';
    

    Only after removing it, I got everything to work locally and online with https/wss.


    What was happening is, only when the server was running with https, daphne_port was being set to :8001/ which would make the url equal to https://example.com:8001/ws/chat/room/ which is (propably) not desired.

    And since window.location.host includes the port number if running locally (daphne_port was empty), everything worked just fine.