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
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 thisdaphne_portconstant.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_portwas being set to:8001/which would make theurlequal tohttps://example.com:8001/ws/chat/room/which is (propably) not desired.And since
window.location.hostincludes the port number if running locally (daphne_portwas empty), everything worked just fine.