Channels: Proper way of configuring Daphne and runworker on production

Created on 17 Jul 2016  路  5Comments  路  Source: django/channels

I'm currenly trying to deploy a small project using Django, channels and redis as backend. In order to do that, I'm using Openshift and executing daphne and runworker in a post_deploy script that looks like this:


(python manage.py runworker &) && 
(daphne myapp.asgi:channel_layer -p 8443 -b $OPENSHIFT_PYTHON_IP &)

To test it, I'm connecting via ssh with two terminals and executing both separately and as foreground processes using -v2 to see the outputs.

PROBLEM:
I cannot bind daphne to 0.0.0.0 (or localhost) because permissions problems so I don't know if I should keep binding daphne to $OPENSHIFT_PYTHON_IP or use the same ip as redis ($OPENSHIFT_REDIS_HOST)?
I've tried both, it seems to work but I got no response from server-side (I've set the receive route to get an echo as response).
I have the same problem with -p parameter, Openshift's documentation says that websockets that use HTTPS, should be created to ask for port 8443. When I do it, the browser show me this:

captura de pantalla de 2016-07-16 16-54-08

captura de pantalla de 2016-07-16 16-54-13

captura de pantalla de 2016-07-16 16-54-22

I expected to see an output in the terminal or an alert in the browser (like in the channels example), but I got none of them. What I'm doing wrong?

What is the proper way to configure daphne and runworker in Openshift platform for production?

Additional info:

_settings.py_:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "asgi_redis.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("redis://:{}@{}:{}/0").format(
                OPENSHIFT_REDIS_PASSWORD,
                OPENSHIFT_REDIS_HOST,
                OPENSHIFT_REDIS_PORT
                )],
        },
        "ROUTING": "myapp.routing.channel_routing",
    },
}

Where the OPENSHIFT_REDIS_ are environment variables that the redis cartridge has set for me.

_routing.py_:

ws_routing = [
    routing.route("websocket.connect", ws_connect),
    routing.route("websocket.receive", ws_receive),
    routing.route("websocket.disconnect", ws_disconnect),
]

channel_routing = [
    include(ws_routing, path=r"^/sync"),
]

_consumers.py_:

...
def ws_connect(message):
    Group("notifications").add(message.reply_channel)

def ws_disconnect(message):
    Group("notifications").discard(message.reply_channel)

def ws_receive(message):
    #Echo test
    print "Receiving: '%s' from %s" % (message.content['text'], message.content['reply_channel'])

Client-side WebSocket creation:

var ws = new WebSocket('wss://'+window.location.host + ':8443/sync');
ws.onmessage = function(message) {
    console.log(message.data);
}
ws.onopen = function() {
    this.send('WS Connecting to receive updates!');
}

Thanks in advance!

question

Most helpful comment

Good news! I managed to make it work, thanks again for your answer.

1) I found a cartridge that allows me to use nginx, uwsgi and django instead of the default, apache. (btw, with the default cartridge for django projects I was unable to modify apache conf file and even update apache to install the mod_proxy_wstunnel so I decided to change everything).

Before pushing my code, I tweaked the action hooks a bit to get the lastest versions of those packages and replaced the sample django project with mine. I did the same with the requierements.txt.
2) After pushing it, I added the redis cartridge.
3) Then I proceeded to tweak uwsgi.yaml and nginx.conf that the cartridge provides to set the proper values:

uwsgi.yaml

uwsgi:
    socket: $OPENSHIFT_DIY_IP:15005
    pidfile: $OPENSHIFT_TMP_DIR/uwsgi.pid
    pythonpath: $OPENSHIFT_REPO_DIR/$APP_DIR
    module: $APP_NAME.wsgi:application
    virtualenv: $OPENSHIFT_DATA_DIR/virtualenv

nginx.conf

...
http {
   ...
    server {
        listen      $OPENSHIFT_DIY_IP:$OPENSHIFT_DIY_PORT;
        server_name localhost;

        set_real_ip_from    $OPENSHIFT_DIY_IP;
        real_ip_header      X-Forwarded-For;

        location / {
            uwsgi_pass  $OPENSHIFT_DIY_IP:15005;
            include     uwsgi_params;
        }

        location /sync {
            proxy_pass http://$OPENSHIFT_DIY_IP:8443;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
        ...
    }

}

In my post_deploy script I have the following:

...
python manage.py runworker -v2 &
daphne myapp.asgi:channel_layer -p 8443 -b $OPENSHIFT_DIY_IP -v2 &
...

So daphne is listening in $OPENSHIFT_DIY_IP:8443 and when nginx receives a request from websockets like this:

        var path = 'wss://'+window.location.host + ':8443/sync';
        var ws = new WebSocket(path);
        ws.onmessage = function(message) {
            alert(message.data);
        }
        ws.onopen = function() {
            this.send('WS Connecting to receive updates!');
        }

Now I can see:
sample

And in the browser:

alert

Hope this helps someone.

All 5 comments

I'm afraid I can't help with the specifics of Openshift - I've not used it myself - but I'll try and answer what I can.

First of all, if you want to use SSL/TLS on WebSockets, you need a server in front of Daphne terminating the SSL and forwarding the WebSocket onto Daphne, as it does not have native support for them yet.

Secondly, you should try and host your WebSockets through the same port/host as your main site, using Nginx to proxy in front of both (and likely do things like serve static files). This way, cookies will carry across between them.

I don't know about PYTHON_IP versus REDIS_IP - you need to bind where Openshift is sending web traffic, that's the main thing. You also need to make sure your worker and Daphne are definitely talking to the same Redis, otherwise you get silence and nothing happening.

(It is notable that you get a valid WebSocket connection, which means Daphne, or something that speaks WebSocket, is on the other end; the upgrade and negotiation process can't happen by accident!)

Thank you very much for your help! I'll try to update this if I can make it work

Good news! I managed to make it work, thanks again for your answer.

1) I found a cartridge that allows me to use nginx, uwsgi and django instead of the default, apache. (btw, with the default cartridge for django projects I was unable to modify apache conf file and even update apache to install the mod_proxy_wstunnel so I decided to change everything).

Before pushing my code, I tweaked the action hooks a bit to get the lastest versions of those packages and replaced the sample django project with mine. I did the same with the requierements.txt.
2) After pushing it, I added the redis cartridge.
3) Then I proceeded to tweak uwsgi.yaml and nginx.conf that the cartridge provides to set the proper values:

uwsgi.yaml

uwsgi:
    socket: $OPENSHIFT_DIY_IP:15005
    pidfile: $OPENSHIFT_TMP_DIR/uwsgi.pid
    pythonpath: $OPENSHIFT_REPO_DIR/$APP_DIR
    module: $APP_NAME.wsgi:application
    virtualenv: $OPENSHIFT_DATA_DIR/virtualenv

nginx.conf

...
http {
   ...
    server {
        listen      $OPENSHIFT_DIY_IP:$OPENSHIFT_DIY_PORT;
        server_name localhost;

        set_real_ip_from    $OPENSHIFT_DIY_IP;
        real_ip_header      X-Forwarded-For;

        location / {
            uwsgi_pass  $OPENSHIFT_DIY_IP:15005;
            include     uwsgi_params;
        }

        location /sync {
            proxy_pass http://$OPENSHIFT_DIY_IP:8443;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
        ...
    }

}

In my post_deploy script I have the following:

...
python manage.py runworker -v2 &
daphne myapp.asgi:channel_layer -p 8443 -b $OPENSHIFT_DIY_IP -v2 &
...

So daphne is listening in $OPENSHIFT_DIY_IP:8443 and when nginx receives a request from websockets like this:

        var path = 'wss://'+window.location.host + ':8443/sync';
        var ws = new WebSocket(path);
        ws.onmessage = function(message) {
            alert(message.data);
        }
        ws.onopen = function() {
            this.send('WS Connecting to receive updates!');
        }

Now I can see:
sample

And in the browser:

alert

Hope this helps someone.

@Pazitos10 I tried the same method as you tried but this is not working for me. I am running Django server using WSGI and not in the foreground as yours. Can you tell me what could be the problem?

Regards,
I tried trying channels in production using the Openshift server, but I have not had success, I followed the steps indicated by @Pazitos10, but the client ws not connected. You could give me some guidance to know that I will be doing wrong.

Was this page helpful?
0 / 5 - 0 ratings