【问题标题】:WebSocket connection to 'wss://api-such.andsuch.xyz/graphql/' failed: Error during WebSocket handshake: Unexpected response code: 400WebSocket 连接到“wss://api-such.andsuch.xyz/graphql/”失败:WebSocket 握手期间出错:意外响应代码:400
【发布时间】:2020-11-16 08:49:14
【问题描述】:

我最近将我正在处理的项目部署到生产环境。我将 DjangoChannelsGraphqlWs 用于 GraphQL 订阅功能。我通过django-graphql-playground 设置了 GraphQL Playground。在本地一切正常 - 没有任何问题 - 订阅工作正常。但是,当我在 Playground 中点击 Play 按钮时,我得到以下错误:

{
  "error": "Could not connect to websocket endpoint wss://api-such.andsuch.xyz/graphql/. Please check if the endpoint url is correct."
}

...在我的浏览器控制台中,它说

WebSocket connection to 'wss://api-such.andsuch.xyz/graphql/' failed: Error during WebSocket handshake: Unexpected response code: 400

需要注意的一点是应用程序是 dockerized。会不会是从那里来的?我不这么认为,因为它在本地有效。这是我的docker-compose 文件的样子:

version: '3.7'

services:
  nginx:
    container_name: nginx
    image: nginx
    restart: always
    depends_on:
      - web
    volumes:
      - ./web/dev.nginx.template:/etc/nginx/conf.d/dev.nginx.template
      - ./static:/static
      - ./media:/media
    ports:
      - "8080:80"
    networks:
      - SOME_NETWORK
    command: /bin/bash -c "envsubst \"`env | awk -F = '{printf \" $$%s\", $$1}'`\" < /etc/nginx/conf.d/dev.nginx.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"

  web:
    container_name: web
    restart: always
    build: ./web
    networks:
      - SOME_NETWORK
    depends_on:
      - postgres
      - redis
    volumes:
      - ./web:/usr/src/app/
    environment:
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - GRAPHQL_ENDPOINT=https://api-such.andsuch.xyz/graphql/
    command: bash -c /start.sh

  postgres:
    container_name: postgres
    restart: always
    image: postgres:latest
    networks:
      - SOME_NETWORK
    volumes:
      - pgdata:/var/lib/postgresql/data/

  redis:
   container_name: redis
   restart: always
   image: redis:latest
   networks:
    - SOME_NETWORK
   ports:
     - "6379:6379"
   volumes:
     - redisdata:/data

volumes:
  pgdata:
  redisdata:

networks:
  SOME_NETWORK:
    name: SOME_NETWORK
    driver: bridge

settings.py

...
...
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            'hosts': [(os.getenv('REDIS_HOST', 'redis'), os.getenv('REDIS_PORT', 6379))],
        }
    }
}
...
...

routing.py

application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter([
            path('graphql/', GraphQLWSConsumer)
        ]),
    )
})

consumers.py

class GraphQLWSConsumer(channels_graphql_ws.GraphqlWsConsumer):
    schema = schema

    async def on_connect(self, payload):
        self.scope['user'] = await get_user(self.scope)

urls.py

...
...
from graphql_playground.views import GraphQLPlaygroundView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('playground/', GraphQLPlaygroundView.as_view(
        endpoint=os.getenv('GRAPHQL_ENDPOINT'))),
]
...

nginx

server {
    client_max_body_size 10M; 
    
    listen 443 ssl;
    listen [::]:443 ssl;
    
    server_name api-such.andsuch.xyz;
    
    ssl_certificate /etc/ssl/certs/andsuch.xyz.pem;
    ssl_certificate_key /etc/ssl/certs/andsuch.xyz.key;
    
    location = /favicon.ico { access_log off; log_not_found off; }
    
    location / {
        proxy_set_header Host $http_host;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection ‘upgrade’;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://0.0.0.0:8080;
    }
}

可能出了什么问题?我没主意了。谢谢!

更新 我检查了 chrome 开发者控制台中的网络选项卡,发现 websocket 连接立即关闭。为什么会这样?

【问题讨论】:

  • 你把它部署到哪里了? stackoverflow.com/questions/59624757/… 有人将 Django Channels 应用程序部署到 Google Cloud,结果发现并非所有 Google Cloud 环境都支持 Websockets - 所以它可能与生产环境有关
  • @bouteillebleu 这是 DigitalOcean

标签: django docker-compose graphql-subscriptions graphene-django graphql-playground


【解决方案1】:

看了很多文章后,我发现this onethis section 让我意识到唯一的错误是我没有在内部将端口 8000 暴露给其他 docker 服务。在我的docker-compose 文件中,Web 服务应该具有...

...
...
expose:
    - 8000
...
...

... 在里面。我添加了它,它已经解决了。

【讨论】:

    【解决方案2】:

    您缺少routing.py 文件;例如:

    from django.urls import path
    from channels.routing import ProtocolTypeRouter, URLRouter
    from . import consumers
    
    
    application = ProtocolTypeRouter({
        "websocket": URLRouter([
            path("api-such.andsuch.xyz/graphql/", consumers.MyGraphqlConsumer),
        ]),
    })
    

    虽然 urls.py 声明了所有已发布的 HTTP 端点,但对于其他协议(尤其是 websocket),您应该改用 routing.py

    在负责处理所有 websocket 事件的 Consumer 中,您应该立即accept 传入连接,因为这是协议要求的:

    from channels.consumer import SyncConsumer
    
    class MyGraphqlConsumer(SyncConsumer):
    
        def websocket_connect(self, event):
            self.send({
                'type': 'websocket.accept'
            })
    

    否则,连接将在短暂超时后失败。

    【讨论】:

    • 非常感谢马里奥的回答。我认为图书馆已经处理好了。问题不在于它在短暂的超时后失败。它在本地运行得非常好。问题发生在生产中。不知道为什么
    • Mhhh .. 接缝我误解了你的问题。您是否将 Daphne 用于 HTTP 和 WebSocket 流量?
    • 不幸的是,我对 Docker 不是很熟悉……但是,我注意到 Nginx 映像的 Dockerfile 中的 ports: - "8080:80" 行。既然您使用的是 https,您不应该改为公开端口 443 吗?另外,我会尝试使用 http/ws 和端口 80 作为初步步骤,仅用于测试目的
    猜你喜欢
    • 1970-01-01
    • 2020-01-24
    • 2015-07-22
    • 1970-01-01
    • 2019-09-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    相关资源
    最近更新 更多