与 'wss://api-such.andsuch.xyz/graphql/' 的 WebSocket 连接失败:WebSocket 握手期间出错:意外的响应代码:400
WebSocket connection to 'wss://api-such.andsuch.xyz/graphql/' failed: Error during WebSocket handshake: Unexpected response code: 400
我最近将我正在从事的项目部署到生产环境中。我使用 DjangoChannelsGraphqlWs
for GraphQL subscription functionalities. and I have GraphQL Playground set up via django-graphql-playground
。在本地一切正常 - 没有任何问题 - 订阅工作正常。但是,当我部署时,在 Playground 中点击播放按钮时出现以下错误:
{
"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\", $}'`\" < /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 连接立即关闭。为什么会这样?
您缺少 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 end-points,但对于其他协议(尤其是 websockets),您应该使用 routing.py
。
在负责处理所有 websocket 事件的消费者中,您应该立即 accept
传入连接,因为这是协议要求的:
from channels.consumer import SyncConsumer
class MyGraphqlConsumer(SyncConsumer):
def websocket_connect(self, event):
self.send({
'type': 'websocket.accept'
})
否则,连接将在短时间超时后失败。
阅读大量文章后,我发现 this one, and this section 让我意识到唯一的错误是我没有在内部向其他 docker 服务公开端口 8000。在我的 docker-compose
文件中,Web 服务应该有...
...
...
expose:
- 8000
...
...
...在里面。我添加了它并解决了。
我最近将我正在从事的项目部署到生产环境中。我使用 DjangoChannelsGraphqlWs
for GraphQL subscription functionalities. and I have GraphQL Playground set up via django-graphql-playground
。在本地一切正常 - 没有任何问题 - 订阅工作正常。但是,当我部署时,在 Playground 中点击播放按钮时出现以下错误:
{
"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\", $}'`\" < /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 连接立即关闭。为什么会这样?
您缺少 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 end-points,但对于其他协议(尤其是 websockets),您应该使用 routing.py
。
在负责处理所有 websocket 事件的消费者中,您应该立即 accept
传入连接,因为这是协议要求的:
from channels.consumer import SyncConsumer
class MyGraphqlConsumer(SyncConsumer):
def websocket_connect(self, event):
self.send({
'type': 'websocket.accept'
})
否则,连接将在短时间超时后失败。
阅读大量文章后,我发现 this one, and this section 让我意识到唯一的错误是我没有在内部向其他 docker 服务公开端口 8000。在我的 docker-compose
文件中,Web 服务应该有...
...
...
expose:
- 8000
...
...
...在里面。我添加了它并解决了。