WebSocket连接在生产中失败,(Nginx,Gunicorn,Daphne)(Django/React)

nxagd54h  于 2023-05-06  发布在  Nginx
关注(0)|答案(1)|浏览(160)

我正在尝试将我的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没有显示任何错误。

bxpogfeg

bxpogfeg1#

你应该在developer tools〉console中显示/检查你在页面上看到的javascript错误。
然而,我可以看到,你将无法通过互联网连接,因为你试图通过ws连接,它可能抛出一个HTTPDOMException或某种类型的连接失败。您需要使用wss连接到Internet上的实时服务器。
下面是一个apache服务器配置,我希望你能把它翻译成nginx。我们的想法是,我们的daphne服务实际上是在服务器本地运行python应用程序。尽管您的daphne命令指定了一个asgi.py文件,但我们的服务器配置还需要指定一个路径来访问本地运行的这个WebSocket应用程序。我们需要将wss重定向到ws。例如:

RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR]
RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
RewriteRule .* ws://127.0.0.1:8001%{REQUEST_URI} [P,QSA,L]
ProxyPass /wss/ wss://127.0.0.1:8001/
ProxyPassReverse /wss/ wss://127.0.0.1:8001/
...
SSLEngine on
SSLCertificateFile /etc/ssl/certificate.crt
SSLCertificateKeyFile /etc/ssl/private/private.key
SSLCertificateChainFile /etc/ssl/ca_bundle.crt

在生产环境中,您还需要SSL证书来确保加密连接,并且您需要在daphne指令中指定该证书。

daphne -b 0.0.0.0 -p 8001 django_project.asgi:application // Local Development Level
daphne -e ssl:443:privateKey=key.pem:certKey=crt.pem django_project.asgi:application // Production Level

相关问题