Search code examples
apache2django-channelsdaphne

Using Django channels with apache2 and daphne (webbSocket connection to 'wss://MYDOMAIN/ws/main/' failed:)


I have been playing around with django channels and everything seemed to be working in my development environment, but now I am having trouble deploying it to my local server.

I am aware there have been similar questions on this topic and I have tried my best to solve this using them, but have had no luck.

Im using apache2 to serve the application, and trying to redirect any websocket requests daphne. The web page loads without problem, but in the browser console I get the error

(webbSocket connection to 'wss://MYDOMAIN/ws/main/' failed:)

Here us my apache conf

<VirtualHost *:444>
        # The ServerName directive sets the request scheme, hostname and port that
        # the server uses to identify itself. This is used when creating
        # redirection URLs. In the context of virtual hosts, the ServerName
        # specifies what hostname must appear in the request's Host: header to
        # match this virtual host. For the default virtual host (this file) this
        # value is not decisive as it is used as a last resort host regardless.
        # However, you must set it for any further virtual host explicitly.
        #ServerName www.example.com
        ServerName myDomain
        ServerAdmin admin
        DocumentRoot /var/www/thomasmichaelides/public_html



       # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
       # error, crit, alert, emerg.
        # It is also possible to configure the loglevel for particular
        # modules, e.g.
        #LogLevel info ssl:warn

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        # For most configuration files from conf-available/, which are
        # enabled or disabled at a global level, it is possible to
        # include a line for only one particular virtual host. For example the
        # following line enables the CGI configuration for this host only
        # after it has been globally disabled with "a2disconf".

        # Enable Websocket connections on port 8888
        RewriteEngine on
        RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR]
        RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
        RewriteRule .* ws://127.0.0.1:8001%{REQUEST_URI} [P,QSA,L]

        Alias /static /home/atwebapps/arduinoDjango/static
       <Directory /home/atwebapps/arduinoDjango/static>
               Require all granted
       </Directory>

       <Directory /home/atwebapps/arduinoDjango/arduinoDjango>
               <Files wsgi.py>
                       Require all granted
               </Files>
       </Directory>
       WSGIScriptAlias / /home/atwebapps/arduinoDjango/arduinoDjango/wsgi.py
       WSGIDaemonProcess arduinoApp python-path=/home/atwebapps/arduinoDjango python-home=/home/atwebapps/arduinoDjango/venv
       WSGIProcessGroup arduinoApp





SSLCertificateFile /etc/letsencrypt/live/mydomain/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/mydomain/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>

and here is my daphne.service

    [Unit]
Description=WebSocket Daphne Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/home/atwebapps/arduinoDjango
ExecStart=/home/atwebapps/arduinoDjango/venv/bin/python /home/atwebapps/arduinoDjango/venv/bin/daphne -b 0.0.0.0 -p 8001 arduinoDjango.asgi:application
Restart=on-failure

[Install]
WantedBy=multi-user.target

Is there anything obvious as to why the ws connection keeps failing?

I will also put my asgi.py file as well as my settings.py

asgi.py

"""
ASGI config for arduinoDjango project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'arduinoDjango.settings')

application = get_asgi_application()

settings.py (channels related stuff)

INSTALLED_APPS = [
    'main.apps.MainConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'channels',
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

WSGI_APPLICATION = 'arduinoDjango.wsgi.application'
ASGI_APPLICATION = 'arduinoDjango.routing.application'
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("localhost", 6379)],
        },
    },
}

use_websockets = True

Any insight to this would be greatly appreciated


Solution

  • You're close, but remember that the daphne service on the server is running multiple instances or python applications asynchronously. It works between the apache code and the django server code to redirect web traffic where it needs to go.

    Therefore, you do not need to specify WSGIScriptAlias WSGIDaemonProcess or WSGIProcessGroup. You need to specify what ports we are redirecting traffic to. You seem to have handled the http traffic; but the trick with websockets here is that you have to remember that our browser must connect over the internet using wss in contrast to the websocket which is actually running locally to our server. We can solve this issue by redirecting all wss web traffic to the local port on ws where the websocket is running.

    Consider the following Apache conf with SSL which is required at production level.

        ...
        RewriteEngine on
        RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC,OR]
        RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
        RewriteRule .* ws://127.0.0.1:8001%{REQUEST_URI} [P,QSA,L]
        ProxyPass /wss/ wss://127.0.0.1:8001/
        ProxyPassReverse /wss/ wss://127.0.0.1:8001/
        ...
        SSLEngine on
        SSLCertificateFile /etc/ssl/certificate.crt
        SSLCertificateKeyFile /etc/ssl/private/private.key
        SSLCertificateChainFile /etc/ssl/ca_bundle.crt
        ...
    

    Then you have to start the server, and so at production level you are going to need a Docker container or some methodology to keep the code running. Make sure to modify the port in production level to match your apache conf.

    daphne -b 0.0.0.0 -p 8001 django_project.asgi:application // Local Development Level
    daphne -e ssl:443:privateKey=key.pem:certKey=crt.pem django_project.asgi:application // Production Level