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

There are 2 answers

0
Caspian On BEST ANSWER

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.

expose:
    - 8000
3
Mario Orlandi On

You're missing the routing.py file; for example:

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),
    ]),
})

While urls.py declares all published HTTP end-points, for other protocols (notably websockets) you should use routing.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:

from channels.consumer import SyncConsumer

class MyGraphqlConsumer(SyncConsumer):

    def websocket_connect(self, event):
        self.send({
            'type': 'websocket.accept'
        })

otherwise, the connection will fail after a short timeout.