I recently deployed a project I’m working on to production. I use DjangoChannelsGraphqlWs
for GraphQL subscription functionalities. and I have GraphQL Playground set up via django-graphql-playground
. Everything works fine locally – there are no issues whatsoever – subscriptions work fine. However, when I deployed I get the error below when I hit the Play button in Playground:
{
"error": "Could not connect to websocket endpoint wss://api-such.andsuch.xyz/graphql/. Please check if the endpoint url is correct."
}
…and in my browser console, it says
WebSocket connection to 'wss://api-such.andsuch.xyz/graphql/' failed: Error during WebSocket handshake: Unexpected response code: 400
One thing to note is that the application is dockerized. Could it be from there? I don’t think so because it works locally. Here’s what my docker-compose
file looks like:
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;
}
}
What could be wrong? I’m outta ideas. Thanks!
Update
I checked the Network tab in chrome’s developer console and discovered that websocket connections close immediately. Why is this happening?
2
Answers
After going through lots of articles, I discovered Dockerizing Django with Postgres, Gunicorn, and Nginx, and the section on Nginx made me realize that the only thing that was wrong was the fact that I didn't expose port 8000 internally to other docker services.
In my
docker-compose
file, the web service was supposed to have the following, which resolved the issue.You’re missing the
routing.py
file; for example:While
urls.py
declares all published HTTP end-points, for other protocols (notably websockets) you should userouting.py
, instead.In the Consumer, which is responsible for handling all websocket events, you should immetiatly
accept
the incoming connection, since this is required by the protocol:otherwise, the connection will fail after a short timeout.