I have been playing around with Django Channels for a a little bit and I am stuck on how to get it to work outside of a local development server setting. Before someone just pastes a documentation page to me, I have exhausted my search of the Django Channels documentation and everywhere else I could find. I can get the local setup to work fine, but not externally. My philosophy over years has been to never develop with the Django development server ever under any circumstances anyways because of exactly this kind of situation.
So here it is:
I have a django site that has been served by apache for years and uses LDAP user authentication (this matter is beyond my control and pay grade). I've installed Django Channels, asgi_redis
, redis-server
, and interface server Daphne comes with Django Channels automatically. I'm also working in CentOS 6/7.
I have so far worked out that I need to use apache as a reverse proxy to talk to ASGI/Daphne, but I just cannot find the info I need or figure it out myself, apparently.
Here's the closest configuration I can figure out. I have my apache configuration file setup as (the URL is external since my development server is remote; sensitive info is edited, of course):
< VirtualHost *:80 >
# Django Channels
ProxyPass "/ws/" "ws://192.168.xx.xx/"
ProxyPassReverse "/ws/" "ws://192.168.xx.xx/"
ProxyPass "/" "http://192.168.xx.xx/"
ProxyPassReverse "/" "http://192.168.xx.xx/"
WSGIDaemonProcess dashboard_jnett python-path=/home/jnett/dashboard_jnett:/home/jnett/airview_env/lib/python2.7/site-packages
WSGIScriptAlias /dashboard_jnett /home/jnett/dashboard_jnett/apache/dashboard_jnett.wsgi process-group=dashboard_jnett
<Directory /home/jnett/dashboard_jnett>
AuthType Basic
AuthName "Web Utilities"
AuthBasicProvider ldap
AuthGroupFile /dev/null
require valid-user
AuthLDAPBindDN "uid=authenticate,ou=system,dc=intranet,dc=row44,dc=com"
AuthLDAPBindPassword "xxxxxxx"
AuthLDAPURL ldap://192.168.xx.xx/ou=users,dc=intranet,dc=row44,dc=com?cn??(&(objectclass=inetOrgPerson)(member=cn=status))
Require ldap-filter objectClass=inetOrgPerson
</Directory>
Alias /static/dashboard_jnett /var/www/html/static/dashboard_jnett
<Directory /var/www/html/static/dashboard_jnett>
AllowOverride None
Require all granted
Options FollowSymLinks
</Directory>
</VirtualHost>
where I access the site root in-browser via: http://192.168.xx.xx/dashboard_jnett/
In my project code, I then have my asgi.py
file:
import os
import channels.asgi
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_jnett")
channel_layer = channels.asgi.get_channel_layer()
In that settings file settings_jnett.py
reference in the ASGI file there I have:
import asgi_redis
...
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgi_redis.RedisChannelLayer",
"CONFIG": {
"hosts": [os.environ.get('REDIS_URL', 'redis://192.168.xx.xx:6379')],
"prefix": u"dashboard_jnett",
},
"ROUTING": "routing.channel_routing",
},
}
as well as the appropriate packages added to INSTALLED_APPS
, which I left out.
That does correctly point to the routing.py
file, which contains:
from channels.routing import route
from channeltest.consumers import ws_connect, ws_message, ws_disconnect, http_consumer
channel_routing = [
route("websocket.connect", ws_connect),
route("websocket.receive", ws_message),
route("websocket.disconnect", ws_disconnect),
route("http.request", consumers.http_consumer),
#route("websocket.receive", "consumers.ws_message"),
]
which imports from this consumers.py
file
from django.http import HttpResponse
from channels.handler import AsgiHandler
from channels import Group
from channels.sessions import channel_session
# Connected to websocket.connect
@channel_session
def ws_connect(message):
# Accept connection
message.reply_channel.send({"accept": True})
# Work out room name from path (ignore slashes)
room = message.content['path'].strip("/")
# Save room in session and add us to the group
message.channel_session['room'] = room
Group("chat-%s" % room).add(message.reply_channel)
# Connected to websocket.receive
@channel_session
def ws_message(message):
Group("chat-%s" % message.channel_session['room']).send({
"text": message['text'],
})
# Connected to websocket.disconnect
@channel_session
def ws_disconnect(message):
Group("chat-%s" % message.channel_session['room']).discard(message.reply_channel)
def http_consumer(message):
response = HttpResponse("Hello world! You asked for %s" % message.content['path'])
for chunk in AsgiHandler.encode_response(response):
message.reply_channel.send(chunk)
I have Daphne running in terminal with:
[[email protected] ~/dashboard_jnett ]$(airview_env)[[email protected] ~/dashboard_jnett ]$daphne -b 192.168.xx.xx asgi:channel_layer --port 6379
2017-08-23 18:57:56,147 INFO Starting server at tcp:port=6379:interface=192.168.xx.xx, channel layer asgi:channel_layer.
2017-08-23 18:57:56,147 INFO HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2017-08-23 18:57:56,147 INFO Using busy-loop synchronous mode on channel layer
2017-08-23 18:57:56,148 INFO Listening on endpoint tcp:port=6379:interface=192.168.xx.xx
2017-08-23 18:57:56,148 INFO HTTPFactory starting on 6379
2017-08-23 18:57:56,148 INFO Starting factory <daphne.http_protocol.HTTPFactory instance at 0x54aca28>
I have a working running in another terminal with:
[[email protected] ~/dashboard_jnett ]$python manage.py runworker
2017-06-14 20:46:47,988 - INFO - runworker - Using single-threaded worker.
2017-06-14 20:46:47,988 - INFO - runworker - Running worker against channel layer default (asgi_redis.core.RedisChannelLayer)
2017-06-14 20:46:47,989 - INFO - worker - Listening on channels http.request, websocket.connect, websocket.disconnect, websocket.receive
I have redis server running in another terminal with:
[[email protected] ~/dashboard_jnett ]$~jnett/redis-stable/src/redis-server
...
10940:M 14 Jun 20:41:25.224 * The server is now ready to accept connections on port 6379
I am not even trying to utilize the web sockets yet--I am just trying to serve the normal HTTP traffic first, but I can only seem to get proxy errors from apache:
Proxy Error
The proxy server received an invalid response from an upstream server. The proxy server could not handle the request GET /dashboard_jnett/channeltest/.
Reason: Error reading from remote server
where the apache error log is giving me many lines like
[Wed Jun 14 21:39:52.718388 2017] [proxy_http:error] [pid 13123] (70007)The timeout specified has expired: [client 192.168.xx.xx:51814] AH01102: error reading status line from remote server 192.168.xx.xx:80 [Wed Jun 14 21:39:52.718426 2017] [proxy:error] [pid 13123] [client 192.168.xx.xx:51814] AH00898: Error reading from remote server returned by /dashboard_jnett/channeltest/
Has anyone successfully put such a setup into production? I'm hoping that if I can figure out how to get apache to direct traffic to Daphne properly for normal HTTP, I can find my way from there.
I also struggled to get it run on my Raspberry, but finally I made it.
from https://mikesmithers.wordpress.com/2017/02/21/configuring-django-with-apache-on-a-raspberry-pi/ I got good advices.
Apache needs some further packages to serve pages from Django application.
sudo apt-get install apache2-dev
sudo apt-get install libapache2-mod-wsgi-py3
also MPM (Multi-Processing-Module) has to be set.
a2dismod mpm_prefork
a2enmod mpm_worker
service apache2 restart
create asgi.py example from Django channels docu
https://channels.readthedocs.io/en/latest/deploying.html#run-protocol-servers
In my case I also had to add sys path to my Project
"""
ASGI entrypoint. Configures Django and then runs the
application
defined in the ASGI_APPLICATION setting.
"""
import os
import sys
import django
from channels.routing import get_default_application
sys.path.append("/home/pi/Dev/WeatherStation")
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"WeatherStation.settings")
django.setup()
application = get_default_application()
Now Daphne should not complain
daphne -p 8001 WeatherStation.asgi:application
Configure ASGI and Daphne for websockets in Apache, use Apache for HTTP requests. Apache acts like a reverse proxy, redirecting all the websocket requests to Daphne server which is running on a different port
leafpad /etc/apache2/sites-available/000-default.conf
and the content
...
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/pi/Dev/WeatherStation/static
<Directory /home/pi/Dev/WeatherStation/static>
Require all granted
</Directory>
<Directory /home/pi/Dev/WeatherStation/WeatherStation>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
WSGIDaemonProcess Dev python-path=/home/pi/Dev python-home=/home/pi/Dev/WSenv
WSGIProcessGroup Dev
WSGIScriptAlias / /home/pi/Dev/WeatherStation/WeatherStation/wsgi.py
</VirtualHost>
Make sure that Apache has access to your db and other stuff
chmod g+w ~/dvds/db.sqlite3
chmod g+w ~/dvds
sudo chown :www-data db.sqlite3
sudo chown :www-data ~/dvds
Restart Apache for these changes to take effect:
sudo service apache2 restart
Now you have a WSGI server running in Apache and a Daphne server for websockets
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With