我最近将正在进行的一个项目部署到生产中。我将DjangoChannelsGraphqlWs
用于GraphQL订阅功能。并且我已经通过CCD_ 2建立了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
需要注意的一点是,该应用程序已被固定化。它可能是从那里来的吗?我不这么认为,因为它在当地有效。以下是我的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连接立即关闭。为什么会发生这种情况?
您缺少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'
})
否则,连接将在短时间超时后失败。
在阅读了大量文章后,我发现了使用Postgres、Gunicorn和Nginx对Django进行Dockerializing,而关于Nginx的部分让我意识到,唯一错误的是我没有在内部向其他docker服务公开8000端口。
在我的docker-compose
文件中,web服务应该具有以下内容,这解决了问题。
expose:
- 8000