I am trying to deploy my Django Rest Framework application to production. I have my own server running Debian. I am not new to deploying DRF and React applications and the WSGI part of the application works fine with Gunicorn. The problem I can’t solve is I cannot connect to my Websocket from Django Channels no matter what I do.
For further information, running python manage.py runserver
and running everything locally works, I normally connect to my websocket.
My routing.py file:
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path, re_path
from apps.chat_app.consumers import ChatConsumer
websocket_urlpatterns = [
path('ws/chat/<int:id>/<int:curr>/', ChatConsumer.as_asgi()),
]
application = ProtocolTypeRouter({
'websocket':
URLRouter(
websocket_urlpatterns
)
,
})
My consumers file:
import json
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer
from django.contrib.auth import get_user_model
from apps.chat_app.models import Message
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
current_user_id = self.scope['url_route']['kwargs']['curr']
other_user_id = self.scope['url_route']['kwargs']['id']
self.room_name = (
f'{current_user_id}_{other_user_id}'
if int(current_user_id) > int(other_user_id)
else f'{other_user_id}_{current_user_id}'
)
self.room_group_name = f'chat_{self.room_name}'
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.room_group_name, self.channel_layer)
await self.disconnect(close_code)
async def receive(self, text_data=None, bytes_data=None):
data = json.loads(text_data)
message = data.get('message', '')
sender_username = data['sender'].replace('"', '')
sender = await self.get_user(username=sender_username)
typing = data.get('typing', False)
delete = data.get('delete', '')
if typing:
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'user_typing',
'sender': sender_username,
'msg': f'{sender.first_name.capitalize()} {sender.last_name.capitalize()} is typing...',
}
)
elif delete:
await self.delete_message(msg_id=data['delete'])
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'message_delete',
'msg_id': data['delete'],
}
)
else:
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'user_typing',
'sender': sender_username,
'msg': '',
}
)
if message:
msg = await self.save_message(sender=sender, message=message, thread_name=self.room_group_name)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'msg_id': msg.id,
'message': message,
'sender': sender_username,
'timestamp': msg.timestamp.strftime('%d/%m/%Y %H:%M'),
'full_name': f'{sender.first_name.capitalize()} {sender.last_name.capitalize()}',
},
)
async def message_delete(self, event):
msg_id = event['msg_id']
await self.send(
text_data=json.dumps(
{
'delete': msg_id,
}
)
)
async def user_typing(self, event):
username = event['sender']
msg = event['msg']
await self.send(
text_data=json.dumps(
{
'is_typing': True,
'sender': username,
'msg': msg,
}
)
)
async def chat_message(self, event):
message = event['message']
username = event['sender']
full_name = event['full_name']
msg_id = event['msg_id']
timestamp = event['timestamp']
typing = event.get('typing', False)
delete = event.get('delete', '')
if typing:
await self.send(
text_data=json.dumps(
{
'sender': username,
'typing': typing,
}
)
)
elif delete:
await self.send(
text_data=json.dumps(
{
'delete': delete,
}
)
)
else:
if message:
await self.send(
text_data=json.dumps(
{
'msg_id': msg_id,
'message': message,
'timestamp': timestamp,
'sender': username,
'full_name': full_name,
}
)
)
@database_sync_to_async
def get_user(self, username):
return get_user_model().objects.filter(username=username).first()
@database_sync_to_async
def save_message(self, sender, message, thread_name):
return Message.objects.create(sender=sender, message=message, thread_name=thread_name)
@database_sync_to_async
def delete_message(self, msg_id):
Message.objects.filter(id=msg_id).delete()
My asgi.py file:
import os
from django.core.asgi import get_asgi_application
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'inp_proj.settings')
django_asgi_app = get_asgi_application()
import apps.chat_app.routing
application = ProtocolTypeRouter(
{
'http': django_asgi_app,
'websocket': AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(apps.chat_app.routing.websocket_urlpatterns))),
}
)
My daphne.service file:
[Unit]
Description=WebSocket Daphne Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/www/projectdir
ExecStart=/www/projectdir/venv/bin/python /www/projectdir/venv/bin/daphne -b 0.0.0.0 -p 8001 proj.asgi:application
Restart=on-failure
[Install]
WantedBy=multi-user.target
My gunicorn.service file:
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=jan
Group=www-data
WorkingDirectory=/www/projectdir
ExecStart=/www/projectdir/venv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
proj.wsgi:application
[Install]
WantedBy=multi-user.target
My gunicorn.socket file:
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target
And finally, my nginx configuration file:
upstream websocket {
server 127.0.0.1:8001;
}
server {
server_name 127.0.0.1 mydomain;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /www/projdir;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
location /ws/ {
proxy_pass http://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $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;
}
}
All of the services (gunicorn socket, gunicorn service, daphne, nginx) work normally and are up and running. The gunicorn WSGI part works fine and the whole application works normally, everything works except I cannot connect to my websocket. This is how I connect to the websocket in my client code:
const client = useMemo(() => {
return new w3cwebsocket(`ws://mydomain:8001/ws/chat/${id}/${userId}/`);
}, [id, userId]);
Also, instead of the mydomain:8001 i tried putting in [serveripv4address]:8001, I tried it without port 8001, I tried both wss and ws even though it is HTTP. Also in my allowed hosts I allowed the domains and even the server ipv4 address.
I tried literally everything I can think of and every post I saw. My Nginx, gunicorn or Daphne don’t show any errors.