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 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.