python - 为什么 django-channels 无法连接到安全的 Websockets wss?

标签 python django websocket django-channels asgi

最近我一直在开发一个名为(DBSF - 不要成为一个 sh *** y friend )的应用程序,它的功能有点像 facebook,但它会提醒你时不时地与你的 friend 互动。
我遇到了一个我很长时间无法修复的错误。
该应用程序在我的本地机器上运行良好,但是当我尝试将该应用程序部署到 heroku 时遇到了一个错误。
问题是关于使用 Websockets 和 Django-channels 的用户之间的聊天功能。
这是由 heroku 需要 https 引起的,因此 websockets 也必须是安全的(wss://而不是 ws://)。
所以我这样做了,我创建了一个安全的 webSocket,其 url 以 wss://开头
这就是错误发生的地方:由于某种原因,asgi.py 文件或routing.py 文件无法将websocket 连接到consumers.py 中的正确消费者,因此无法建立连接
这是我尝试修复错误的方法:

  • 在 asgi.py 中,我将 http 更改为 https,或将 websocket 更改为其中之一(websockets、ws wss)
  • 更改 settings.py 中的安全设置
  • 尝试了 websocket url 的不同组合
  • 使用 daphne 而不是 Django 的开发服务器运行它

  • 这些都没有改变错误甚至错误消息。
    错误消息总是提示套接字仍在连接或已经关闭或处于关闭状态
    这是一些代码
    在浏览器上(为了在我的本地机器上重新创建错误,我只是硬编码“wss”):
    var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";
          const chatSocket = new WebSocket(
            ws_scheme
            + '://'
            + window.location.host
            + '/ws/chat/'
            + friendship_id
            + '/'
          );
    
    这是 asgi.py
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "DBSF.settings")
    
    import django
    django.setup()
    
    from django.core.management import call_command
    
    
    from django.core.asgi import get_asgi_application
    from channels.routing import ProtocolTypeRouter, URLRouter
    from channels.auth import AuthMiddlewareStack
    import social.routing
    
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DBSF.settings')
    
    application = ProtocolTypeRouter({
        "http": get_asgi_application(),
        "websocket": AuthMiddlewareStack(
            URLRouter(
                social.routing.websocket_urlpatterns
            )
        ),
    })
    
    这是routing.py
    from django.urls import re_path, path
    from . import consumers
    
    websocket_urlpatterns = [
        re_path(r'ws/chat/(?P<friendship_id>\w+)/$', consumers.ChatConsumer.as_asgi()),
        
    ]
    
    这是消费者.py
    import json
    from asgiref.sync import async_to_sync
    from channels.generic.websocket import WebsocketConsumer
    from .models import Message, Friendship, User
    import datetime
    
    class ChatConsumer(WebsocketConsumer):
       
        def connect(self):
            print('fuuuuuuuu')
            self.room_name = self.scope['url_route']['kwargs']['friendship_id']
            self.room_group_name = 'chat_%s' % self.room_name
            # Join room group
            async_to_sync(self.channel_layer.group_add)(
                self.room_group_name,
                self.channel_name
            )
    
            self.accept()
    
        def disconnect(self, close_code):
            # Leave room group
            async_to_sync(self.channel_layer.group_discard)(
                self.room_group_name,
                self.channel_name
            )
    
        # Receive message from WebSocket
        def receive(self, text_data):
            text_data_json = json.loads(text_data)
            message = text_data_json['message']
            sender = text_data_json['sender']
            receiver = text_data_json['receiver']
            friendship_id = self.scope['url_route']['kwargs']['friendship_id']
            message_to_save = Message(conversation=Friendship.objects.get(id=friendship_id), sender=User.objects.get(username=sender), receiver=User.objects.get(username=receiver), text=message, date_sent=datetime.datetime.now())
            message_to_save.save()
    
            # Send message to room group
            async_to_sync(self.channel_layer.group_send)(
                self.room_group_name,
                {
                    'type': 'chat_message',
                    'message': message,
                    'sender': sender,
                    'receiver': receiver,
                    'id': message_to_save.id
                }
            )
    
        # Receive message from room group
        def chat_message(self, event):
            message = event['message']
            sender = event['sender']
            receiver = event['receiver']
            id = event['id']
    
            # Send message to WebSocket
            self.send(text_data=json.dumps({
                'message': message,
                'sender': sender,
                'receiver': receiver,
                'id': id,
            }))
    
    这是settings.py:
    """
    Django settings for DBSF project.
    
    Generated by 'django-admin startproject' using Django 3.1.2.
    
    For more information on this file, see
    https://docs.djangoproject.com/en/3.1/topics/settings/
    
    For the full list of settings and their values, see
    https://docs.djangoproject.com/en/3.1/ref/settings/
    """
    
    from pathlib import Path
    import django_heroku
    import os
    from dotenv import load_dotenv
    load_dotenv()
    # Build paths inside the project like this: BASE_DIR / 'subdir'.
    BASE_DIR = Path(__file__).resolve().parent.parent
    
    # Quick-start development settings - unsuitable for production
    # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
    
    # SECURITY WARNING: keep the secret key used in production secret!
    SECRET_KEY = os.environ['SECRET_KEY']
    AUTH_USER_MODEL = 'social.User'
    # SECURITY WARNING: don't run with debug turned on in production!
    DEBUG = True
    ALLOWED_HOSTS = ['desolate-lowlands-74512.herokuapp.com', 'localhost', '127.0.0.1']
    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
    SECURE_HSTS_SECONDS = 3600
    SECURE_SSL_REDIRECT = False
    SESSION_COOKIE_SECURE = False
    CSRF_COOKIE_SECURE = False
    # Application definition
    
    INSTALLED_APPS = [
        'channels',
        'social',
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    ]
    
    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'whitenoise.middleware.WhiteNoiseMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    
    ROOT_URLCONF = 'DBSF.urls'
    
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    
    REST_FRAMEWORK = {
        # Use Django's standard `django.contrib.auth` permissions,
        # or allow read-only access for unauthenticated users.
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
        ]
    }
    
    WSGI_APPLICATION = 'DBSF.wsgi.application'
    ASGI_APPLICATION = 'DBSF.asgi.application'
    CHANNEL_LAYERS = {
        'default': {
            'BACKEND': 'channels_redis.core.RedisChannelLayer',
            'CONFIG': {
                "hosts": [('https://desolate-lowlands-74512.herokuapp.com/', 6379)],
            },
        },
    }
    
    # Database
    # https://docs.djangoproject.com/en/3.1/ref/settings/#databases
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': BASE_DIR / 'db.sqlite3',
            'TIME_ZONE': 'EST'
        }
    }
    
    
    # Password validation
    # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
    
    AUTH_PASSWORD_VALIDATORS = [
        {
            'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
        },
    ]
    
    
    # Internationalization
    # https://docs.djangoproject.com/en/3.1/topics/i18n/
    
    LANGUAGE_CODE = 'en-us'
    
    TIME_ZONE = 'EST'
    
    USE_I18N = True
    
    USE_L10N = True
    
    USE_TZ = True
    
    
    # Static files (CSS, JavaScript, Images)
    # https://docs.djangoproject.com/en/3.1/howto/static-files/
    STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
    STATIC_URL = '/static/'
    MEDIA_ROOT= os.path.join(BASE_DIR, 'media/')
    MEDIA_URL= "/media/"
    
    
    
    
    这是错误消息:
    layout.js:108 WebSocket connection to 'wss://desolate-lowlands-74512.herokuapp.com/ws/chat/19/' failed: Error during WebSocket handshake: Unexpected response code: 500
    
    这是完整的项目:
    https://github.com/fabianomobono/DBSF
    这是heroku上的应用程序:
    https://desolate-lowlands-74512.herokuapp.com/
    基本上文件 asgi.py 、 routing.py 或 consumer.py 之一不能与 wss WebSockets 一起使用。
    当我在本地机器上使用普通的 webSocket(ws) 时,该应用程序可以工作。
    我真的认为这将是一个容易解决的问题,但我已经尝试了数周。
    这是一个小错误还是我试图解决这个问题的方式完全错误?
    这可能是 django-channels 的错误吗?
    请让我知道您是否能够帮助我解决这个问题,或者您是否可以为我指出以前遇到此问题的人的方向。
    请让我知道我是否足够好地解释了这个错误,或者我不清楚我在问什么。
    有谁知道如何解决这个问题?

    最佳答案

    我能够通过一些更改连接到本地主机。在此更改之前,它甚至不适用于 ws://协议(protocol)。所以我的改变:

  • 你确定你设置了正确的redis?为了测试,您可以尝试
  • CHANNEL_LAYERS = {
        'default': {
            "BACKEND": "channels.layers.InMemoryChannelLayer"
        },
    }
    
  • 在 github 上的代码中,你的路由有错误,它是:
  •    re_path(r'wss/chat/(?P<friendship_id>\w+)/$', consumers.ChatConsumer.as_asgi()),
    
    但应该是
        re_path(r'ws/chat/(?P<friendship_id>\w+)/$', consumers.ChatConsumer.as_asgi()),
    
    根据您的 javascript 代码。
    祝你好运,我希望它有帮助。

    关于python - 为什么 django-channels 无法连接到安全的 Websockets wss?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65726281/

    相关文章:

    python - 正则表达式帮助 - python - 从 css 中提取所有图像 url

    python - psycopg2.extras.DictCursor 不在 postgres 中返回字典

    python - 如何使用 Django REST Framework 返回生成的文件下载?

    reactjs - 在 websocket 回调中的 redux-saga 中调用 redux 操作 (stomp + sockjs)

    javascript - 退休的 TogetherJS 的替代品

    python - 导入错误: cannot import name 'parameter_parser' from 'parser' (unknown location)

    python - 带有交互式 SVG 图像的 pyQt

    django - 使用带有 S3boto 后端的 django-storages 保存到 S3 时,如何设置 "Content-Type"?

    django - 没有创建 celeryd 和 celerybeat pid 文件,工作人员没有启动,但输出显示 OK

    python - Websocket 代码适用于 Windows 但不适用于 Linux