Django频道休息框架不返回生产环境中的订阅数据(gunicorn/daphne/nginx)

Ege*_*tti 6 python django websocket django-rest-framework django-channels

大家好,我通过 daphne 为 websocket 部署了 django,并通过 Gunicorn 为正常请求 http 使用反向代理 nginx 部署了 django,我遇到了一个没有答案的问题,它在本地工作正常。我使用 django 库djangochannelsrestframeworkdjangochannelsrestframework通过 websocket 接收 ORM 模型的变化

我使用这个版本的 django,channels。

  1. Django==3.0.8
  2. djangochannelsrestframework==0.1.0
  3. 频道==2.4.0
  4. 达芙妮==2.5.0
  5. 枪兽==20.0.4

消费者.py

import json

from channels.generic.websocket import WebsocketConsumer
from djangochannelsrestframework.observer.generics import ObserverModelInstanceMixin
from app_mobile.API.Client.Utente.serializer import UserSerializer
from app_mobile.API.Client.Azienda.serializer import AziendaObserverSerializer
from app_mobile.API.Client.Ordini.serializer import OrdineConsumerSerializer
from app_mobile.models import Azienda, Ordine, User
from djangochannelsrestframework import permissions
from djangochannelsrestframework.generics import GenericAsyncAPIConsumer

class AziendaObserver(ObserverModelInstanceMixin, GenericAsyncAPIConsumer):
   queryset = Azienda.objects.all()
   serializer_class = AziendaObserverSerializer
   permission_classes = (permissions.AllowAny,)
Run Code Online (Sandbox Code Playgroud)

路由.py

application = ProtocolTypeRouter({
    "websocket": AuthMiddlewareStack(
        URLRouter([
            path("ws/observer/azienda/", AziendaObserver)
        ]),
    ),
})
Run Code Online (Sandbox Code Playgroud)

nginx

# Enable upgrading of connection (and websocket proxying) depending on the
# presence of the upgrade field in the client request header
map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}

server {
    listen xx.it:80;
    server_name xx.it  www.xx.it;

    return 301 https://xx.it$request_uri;
}

server {
    listen xx.it:443 ssl http2;
    server_name xx.it www.xx.it;
    charset utf-8;

    access_log /var/log/nginx/xx.it.access.log;
    error_log /var/log/nginx/xx.it.com.error.log;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/django/xx/;
        expires 1M;
        access_log off;
        add_header Pragma public;
        add_header Cache-Control "public, must-revalidate, proxy-revalidate";
    }

    location / {
        proxy_pass http://0.0.0.0:8000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_redirect off;
    }

    location /ws/ {
        proxy_pass http://0.0.0.0:8443;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-for $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    ssl_protocols TLSv1.2;
    ssl_certificate /etc/letsencrypt/live/xx.it/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/xx.it/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
Run Code Online (Sandbox Code Playgroud)

古尼康服务

Description=gunicorn
After=network.target


[Service]
PIDFile=/run/gunicorn/pid
User=django
Group=www-data
WorkingDirectory=/home/xxx/xxx
Environment="DJANGO_SETTINGS_MODULE=xxx.settings"
ExecStart=/home/django/xxx/venv/bin/gunicorn xxx.wsgi --bind 0.0.0.0:8000 --log-level error --log-file=- --workers 5 --preload --access-logfile /home/django/xx/logs/gunicorn/output.log --error-logfile /home/django/xx/logs/gunicorn/error.log
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
Restart=on-abort
PrivateTmp=true
#StandardOutput=append:/home/xxx/xxx/logs/gunicorn/output.log
#StandardError=append:/home/xxx/xxx/logs/gunicorn/error.log

[Install]
WantedBy=multi-user.target
Run Code Online (Sandbox Code Playgroud)

达芙妮服务

[Unit]
Description=daphne daemon
After=network.target

[Service]
PIDFile=/run/daphne/pid
User=django
Group=www-data
WorkingDirectory=/home/django/xxx
Environment="DJANGO_SETTINGS_MODULE=xxx.settings"
ExecStart=/home/django/xxx/venv/bin/daphne --bind 0.0.0.0 --port 8443 --verbosity 3 --access-log /home/django/xx/logs/daphne/access.log xxx.asgi:application
#ExecStart=/home/django/xxx/venv/bin/daphne -e ssl:8443:privateKey=privkey.pem:certKey=fullchain.pem -v 3 --access-log /home/django/xx/logs/daphne/access.log xxx.asgi:application 
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
Restart=on-abort
PrivateTmp=true
StandardOutput=append:/home/django/xxx/logs/daphne/access.log
StandardError=append:/home/django/xxx/logs/daphne/error.log

[Install]
WantedBy=multi-user.target
Run Code Online (Sandbox Code Playgroud)

在本地,websocket 工作得很好,例如,如果我订阅一个带有“pk = 1”的模型,如果它已更新,我会收到 websocket 连接的生产中的更改,但当我更新签名模型时,我不会收到任何内容。

var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";
    var conn = new ReconnectingWebSocket(ws_scheme + '://'
        + window.location.host
        + '/ws/observer/azienda/?token=9e1058f3d0539b51ff7350ae6f0b096a6553b240', null, {
        debug: true,
        reconnectInterval: 3000
    });
    conn.onmessage = function (e) {
        console.log("connesso")
        console.log(e.data);
    };

    conn.onopen = () => conn.send(
        (JSON.stringify({
                "action": "subscribe_instance",
                'pk': 1,
                "request_id": 34,
            })
        ));
    conn.onclose = function (e) {
        console.log(e)
        console.error('Error websocket');
    };
Run Code Online (Sandbox Code Playgroud)

在本地,我通过 websocket 接收更改

connesso
(index):26 {"errors": [], "data": null, "action": "subscribe_instance", "response_status": 201, "request_id": 34}
(index):25 connesso
(index):26 {"errors": [], "data": {"id": 1}, "action": "update", "response_status": 200, "request_id": 34}
Run Code Online (Sandbox Code Playgroud)

在 websocket 连接后的生产中,如果我修改 pk = 1 的公司,我不会收到任何更新

connesso
(index):26 {"errors": [], "data": null, "action": "subscribe_instance", "response_status": 201, "request_id": 34}
Run Code Online (Sandbox Code Playgroud)

记录达芙妮产品:

127.0.0.1:55674 - - [31/Jul/2020:11:00:29] "WSCONNECTING /ws/observer/azienda/" - -
127.0.0.1:55674 - - [31/Jul/2020:11:00:29] "WSCONNECT /ws/observer/azienda/" - -
Run Code Online (Sandbox Code Playgroud)

记录达芙妮本地

WebSocket HANDSHAKING /ws/observer/azienda/ [172.27.0.1:60296]
WebSocket CONNECT /ws/observer/azienda/ [172.27.0.1:60296]
Run Code Online (Sandbox Code Playgroud)

使用 nginx 工作 Gunicorn/daphne 的最终解决方案!

map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}

server {
    listen xxx.it:443 ssl http2;
    server_name xx.it www.xx.it;
    tcp_nodelay on;

    client_max_body_size 20M;
    access_log /var/log/nginx/xx.it.access.log;
    error_log /var/log/nginx/xx.it.com.error.log;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/django/xxx/;
        expires 1M;
        access_log off;
        add_header Pragma public;
        add_header Cache-Control "public, must-revalidate, proxy-revalidate";
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_redirect off;
        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_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_set_header Connection "";
        server_tokens off;
        proxy_buffering on;
        if (!-f $request_filename) {
          proxy_pass http://127.0.0.1:8443;
          break;
        }
    }

    location /ws {
         proxy_pass http://127.0.0.1:8443;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-for $proxy_add_x_forwarded_for;
         proxy_set_header Host $http_host;
         proxy_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection "upgrade";
    }

    ssl_certificate /etc/letsencrypt/live/xxx.it/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/xx.it/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
Run Code Online (Sandbox Code Playgroud)