我正在尝试将我的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]);
另外,我没有把mydomain:8001放入[serveripv4address]:8001,而是在没有端口8001的情况下尝试,我尝试了wss和ws,尽管它是HTTP。此外,在我允许的主机中,我允许域甚至服务器ipv4地址。
我尝试了我能想到的一切,以及我看到的每一篇文章。我的Nginx,gunicorn或Daphne没有显示任何错误。
1条答案
按热度按时间bxpogfeg1#
你应该在developer tools〉console中显示/检查你在页面上看到的javascript错误。
然而,我可以看到,你将无法通过互联网连接,因为你试图通过
ws
连接,它可能抛出一个HTTPDOMException或某种类型的连接失败。您需要使用wss
连接到Internet上的实时服务器。下面是一个apache服务器配置,我希望你能把它翻译成nginx。我们的想法是,我们的daphne服务实际上是在服务器本地运行python应用程序。尽管您的daphne命令指定了一个asgi.py文件,但我们的服务器配置还需要指定一个路径来访问本地运行的这个WebSocket应用程序。我们需要将
wss
重定向到ws
。例如:在生产环境中,您还需要SSL证书来确保加密连接,并且您需要在daphne指令中指定该证书。