Brnpzs Brnpzs - 2 months ago 21
Python Question

Daphne server cannot connect with websockets on HTTPS

I'm deploying a Django project on the Openshift cloud. This project uses channels and Websockets to make it work asynchronously. The problem is that I can't successfully connect websockets from the browser to the Daphne Server I got running on the server side.

I'm using the django (python2.7) and redis cartridges to make it run.

The post_deploy script I'm using looks like this:

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


Here is my Django configuration. In settings.py :

...
ALLOWED_HOSTS = [
socket.gethostname(),
os.environ.get('OPENSHIFT_APP_DNS'),
]

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",
},
}
...


In 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"),
]
...


In 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):
print "Receiving: '%s' from %s" % (message.content['text'], message.content['reply_channel'])


In the client side, I'm running this code:

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


Notice that I'm using port 8443 in Daphne settings and WebSockets settings because of this documentation. Also, Daphne is bound to OPENSHIFT_HOST address because is not possible to bind it to 0.0.0.0 in Openshift (permission problem)

The output looks like this:

first

second

third

Everything looks ok in the client side but if you remember, in consumers.py I had this:

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


So in my terminal, the server should be printing out something like: "Receiving: from " but it's not. What I'm missing here?

tl;dr: Client-side websocket looks like it's connected correctly but the server is not printing out a message to confirm it.

Answer

I managed to make it work. The problem seems related to a port forwarding thing that made me impossible to connect websockets through the apache server on openshift cloud to my daphne server.

To solve this problem:

1) 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 to support websockets so I decided to change it. Also, mod_proxy_wstunnel works on apache 2.4 but the default cartridge uses 2.2.

The channels docs, recommend to use nginx. So I found a cartridge that allows me to use it along with uwsgi and django.

I followed the instructions in that repo but 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 requirements.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 as templates 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:

terminal

And in the browser:

alert

So I know that is working. I Hope this could help someone else but me.