Websockets с фреймворком rest каналов Django в docker-compose

<
#routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from django.urls import re_path
from gnss.consumers import CoordinateConsumer  # noqa
from operations.consumers import OperationConsumer  # noqa

websocket_urlpatterns = [
    re_path(r'ws/coordinates/', CoordinateConsumer.as_asgi()),
    re_path(r'ws/operations/', OperationConsumer.as_asgi()),
]

application = ProtocolTypeRouter({
    'http': get_asgi_application(),
    'websocket': AuthMiddlewareStack(
        URLRouter(websocket_urlpatterns)
    ),
})

# consumers.py
from djangochannelsrestframework.decorators import action  # noqa
from djangochannelsrestframework.generics import GenericAsyncAPIConsumer  # noqa
from djangochannelsrestframework.observer import model_observer  # noqa
from djangochannelsrestframework.observer.generics import action  # noqa
from djangochannelsrestframework.permissions import AllowAny  # noqa

from .models import Coordinate
from .serializers import CoordinateSerializer


class CoordinateConsumer(GenericAsyncAPIConsumer):
    queryset = Coordinate.objects.all()
    serializer_class = CoordinateSerializer
    permission_classes = (AllowAny,)

    @model_observer(Coordinate)
    async def coordinates_activity(self, message, action=None, **kwargs):
        await self.send_json(message)

    @coordinates_activity.serializer
    def coordinates_activity(self, instance: Coordinate, action, **kwargs):
        return dict(CoordinateSerializer(instance).data,
                    action=action.value, pk=instance.pk)

    @action()
    async def subscribe_to_coordinates_activity(self, request_id, **kwargs):
        await self.coordinates_activity.subscribe(request_id=request_id)

Что касается consumers.py для ws/operations/, то он точно такой же. Когда я запускаю свое приложение Django на локальной машине на сервере разработки (python manage.py runserver), все работает нормально. Но по какой-то причине, когда я запускаю свое приложение в docker-compose, ws/operations/ работает, а ws/coordinates нет. По какой-то причине я не получаю сообщения от Django.

Мой docker-compose

version: "3.7"

x-api-common: &api
  build: ./backend
  restart: always
  env_file: ./config/api/.env
  volumes:
    - ./backend/api:/opt/code
    - static_volume:/home/app/web/staticfiles
    - media_volume:/home/app/web/media
  networks:
    - nginx_network
    - postgres_network
  depends_on:
    - redis
    - postgres

services:

  wsgi:
    <<: *api
    command: "bash /opt/code/docker-wsgi-entrypoint.sh"
    container_name: operations_wsgi
    environment:
      is_wsgi: 'true'
    ports:
      - 8000:8000

  asgi:
    <<: *api
    command: "bash /opt/code/docker-asgi-entrypoint.sh"
    container_name: operations_asgi
    ports:
      - 8001:8001
    depends_on:
      - wsgi

  admin:
    <<: *api
    container_name: operations_admin
    ports:
      - 8002:8002
    depends_on:
      - wsgi
    command: gunicorn api.wsgi:application --bind 0.0.0.0:8002 --workers 2 --timeout 600

  web:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    restart: "on-failure"
    ports:
      - 80:80
    volumes:
      - ./frontend:/app
      - '/app/node_modules'
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/media
    depends_on:
      - wsgi
      - asgi
      - admin
    networks:
      - nginx_network

  redis:
    image: "redis:6.2.7-alpine"
    restart: always
    command:
      - /bin/sh
      - -c
      - redis-server --requirepass "$${REDIS_PASSWORD:?REDIS_PASSWORD variable is not set}"
    ports:
      - "6379:6379"
    env_file:
      - ./config/redis/.env
    networks:
      - nginx_network

  postgres:
    image: postgres:13.0
    restart: always
    ports:
      - 5432:5432
    env_file: config/postgres/.env
    networks:
      - postgres_network
    volumes:
      - ./data/postgres-data:/var/lib/postgresql/data

  rabbitmq:
    image: 'rabbitmq:3.6-management-alpine'
    restart: always
    ports:
      - '5672:5672'
      - '15672:15672'
    env_file: config/rabbitmq/.env

networks:
  nginx_network:
    driver: bridge
  postgres_network:
    driver: bridge

volumes:
  static_volume:
  media_volume:

И моя конфигурация nginx

upstream wsgi_server {
    server wsgi:8000;
}

upstream asgi_server {
    server asgi:8001;
}

upstream admin_server {
    server admin:8002;
}

server {

  listen 80;
  server_name localhost;
  charset utf-8;
  server_tokens off;
  client_max_body_size 20M;
  resolver 10.0.0.2 valid=300s;
  resolver_timeout 10s;

    location ~* ^/ws/ {
      proxy_pass http://asgi_server;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";
      proxy_set_header Host            $host;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Scheme $scheme;
      proxy_set_header REMOTE_ADDR $remote_addr;
    }


    location ~ ^/static/(?<base_path>rest_framework|admin|drf-yasg)/(?<tail>.*)$ {
        autoindex on;
        alias /home/app/web/staticfiles/$base_path/$tail;
    }

    location ~ ^/media/(?<base_path>images)/(?<tail>.*)$ {
        autoindex on;
        alias /home/app/web/media/$base_path/$tail;
        add_header Content-disposition "attachment";
    }

    location ~ ^/api|swagger|redoc/ {
        proxy_pass http://wsgi_server;
        proxy_pass_request_headers on;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }

    location ~ ^/admin/ {
        proxy_pass http://admin_server;
        proxy_pass_request_headers on;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

  error_page   500 502 503 504  /50x.html;

  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}

Я не знаю, что здесь не так

Для решения проблемы:

  1. Добавьте функцию def ready() в apps.py следующим образом:
from django.apps import AppConfig

class GnssConfig(AppConfig):
    name = 'gnss'
    verbose_name = 'GNSS management'

    def ready(self) -> None:
        from .consumers import CoordinateConsumer  # noqa
  1. Настройте nginx следующим образом:
    location ~ ^/ws/operations|coordinates/ {
      proxy_pass http://asgi_server;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }

Это пример для двух конечных точек ws/operations и ws/coordinates

Вернуться на верх