Question

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

Posted September 1, 2020 279 views
NginxPythonDockerDjangoGraphQL

Hello everyone! 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
      - ./web/static:/static
      - ./web/media:/media
    ports:
      - "8080:80"
    networks:
      - RIO_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:
      - RIO_NETWORK
    depends_on:
      - postgres
      - redis
    volumes:
      - ./web:/usr/src/app/
    environment:
      - SECRET_KEY=enteryoursecretkey
      - POSTGRES_DB=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_HOST=postgres
      - POSTGRES_PORT=5432
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - TWILIO_ACCOUNT_SID=AC3c9e65abfaba8f446f6c6b3ad7553752
      - TWILIO_AUTH_TOKEN=0e66ca6bfe00e72542beb5fbf9f747f3
      - GBN_USERNAME=smsleak
      - GBN_PASSWORD=smsleak
      - DEBUG=True
      - MAILJET_API_KEY=97feac351b0184555c3cbb6374671c52
      - MAILJET_SECRET_KEY=913d594e24564d6c37c2faa2acf4740e
      - GRAPHQL_ENDPOINT=https://api-rio-dev.cnpro.xyz/graphql/
    command: bash -c /start.sh

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

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

volumes:
  pgdata:
  redisdata:

networks:
  RIO_NETWORK:
    name: RIO_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))],
        }
    }
}
...
...

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!

These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.

×
2 answers

you must proxying websocket to pass docker..

Change the settings.py DEBUG to True, then Django should give you more information regarding the bad request. In my case it was an incorrect ALLOWED_HOSTS in settings.py that debug mode showed info enough to fix it.

  • Thanks for this @niloteixeira
    I have ALLOWED_HOSTS set to [’*’]. I don’t see why this should be a problem. Do you think it is? I have also turned DEBUG on but all I get from Django is Bad request /graphql/ 400

    • @niloteixeira I just used the actual domain in ALLOWED_HOSTS but it’s still the same error.

      • The bare Bad Request 400 response AFAIK is only shown when DEBUG is set to False.

        It could be that the settings isn’t being read.

        Verify this: https://docs.djangoproject.com/en/3.1/topics/settings/#envvar-DJANGO_SETTINGS_MODULE

        (make sure its value is pointing to the correct settings module).

        Also you could provide the Dockerfile for your python build.

        • @niloteixeira it’s not an issue with settings. Other aspects of the application work just fine.
          Have a look at this:

          (app) root@d52252f70073:/usr/src/app# python manage.py shell
          Python 3.7.4 (default, Oct 17 2019, 06:10:02)
          [GCC 8.3.0] on linux
          Type "help", "copyright", "credits" or "license" for more information.
          (InteractiveConsole)
          >>> from django.conf import settings
          >>> settings.DEBUG
          True
          >>>
          

          Here’s my Dockerfile:

          FROM python:3.7.4-slim-buster
          
          WORKDIR /usr/src/app
          
          ENV PYTHONDONTWRITEBYTECODE 1
          ENV PYTHONUNBUFFERED 1
          
          RUN apt-get update \
              && apt-get install -y --no-install-recommends git \
              && python -m pip install --upgrade pip \
              && pip install pipenv
          COPY ./Pipfile /usr/src/app/Pipfile
          COPY ./Pipfile.lock /usr/src/app/Pipfile.lock
          RUN python -m pipenv install
          
          COPY . /usr/src/app/
          
          COPY ./start.sh /start.sh
          
          RUN chmod +x /start.sh
          

          Then start.sh

          function manage_app() {
              pipenv run python manage.py migrate
              pipenv run python manage.py collectstatic --no-input --clear
          }
          
          function start_development() {
              # use django runserver as development server here.
              manage_app
              pipenv run python manage.py process_tasks & pipenv run python manage.py runserver 0.0.0.0:8000
          }
          
          function start_production() {
              # use gunicorn for production server here
              manage_app
              gunicorn cnapi.asgi:application -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 --chdir=/usr/src/app
          }
          
          if [ ${DEBUG} == "True" ]
          then
              # use development server
              start_development
          else
              # use production server
              start_production
          fi
          
          
Submit an Answer