我正在尝试将我的 Django Rest Framework 应用程序部署到生产环境中。我有自己的运行 Debian 的服务器。我对部署 DRF 和 React 应用程序并不陌生,应用程序的 WSGI 部分与 Gunicorn 配合得很好。我无法解决的问题是无论我做什么我都无法从 Django Channels 连接到我的 Websocket。
有关更多信息,运行
python manage.py runserver
并在本地运行一切正常,我通常连接到我的 websocket。
我的routing.py文件:
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
)
,
})
我的消费者档案:
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()
我的 asgi.py 文件:
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))),
}
)
我的daphne.service文件:
[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
我的 gunicorn.service 文件:
[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
我的 gunicorn.socket 文件:
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target
最后,我的 nginx 配置文件:
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;
}
}
所有服务(gunicorn socket、gunicorn service、daphne、nginx)都正常运行。 gunicorn WSGI 部分工作正常,整个应用程序正常工作,一切正常,除了我无法连接到我的 websocket。这就是我在客户端代码中连接到 websocket 的方式:
const client = useMemo(() => {
return new w3cwebsocket(`ws://mydomain:8001/ws/chat/${id}/${userId}/`);
}, [id, userId]);
此外,我尝试输入 [serveripv4address]:8001 而不是 mydomain:8001,我在没有端口 8001 的情况下尝试了它,我尝试了 wss 和 ws,即使它是 HTTP。同样在我允许的主机中,我允许域甚至服务器 ipv4 地址。
我几乎尝试了所有我能想到的和我看到的每一个帖子。我的 Nginx、gunicorn 或 Daphne 没有显示任何错误。