使用Apache指导流量的生产环境中服务Django Channels

6
我花了一点时间尝试使用Django Channels,但我卡在如何使其在本地开发服务器环境之外工作。在有人给我贴上文档页面之前,我已经耗尽了所有Django Channels文档和其他任何地方的搜索资源。我可以让本地设置正常工作,但无法在外部进行连接。我的哲学多年来一直是永远不要在任何情况下使用Django开发服务器进行开发,正是因为这种情况。
所以,这就是问题:我有一个django网站,已经由apache服务多年,并使用LDAP用户身份验证(此事超出了我的控制范围和薪资等级)。我安装了Django Channels、asgi_redis、redis-server和与Django Channels自动关联的接口服务器Daphne。我还在CentOS 6/7上工作。
到目前为止,我已经弄清楚需要使用apache作为反向代理来与ASGI/Daphne进行通信,但我就是找不到需要的信息或自己弄清楚,显然如此。
以下是我能够找到的最接近的配置。我将我的apache配置文件设置为(URL是外部的,因为我的开发服务器是远程的;敏感信息当然已编辑):
< 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>

我通过浏览器访问网站根目录的方式是:http://192.168.xx.xx/dashboard_jnett/

在我的项目代码中,我有一个名为asgi.py的文件:

import os
import channels.asgi

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings_jnett")
channel_layer = channels.asgi.get_channel_layer()

在ASGI文件中引用的设置文件settings_jnett.py中,我有以下内容:

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

除此之外,还需要添加到INSTALLED_APPS中的适当包,这一点我没有提到。

这确实指向了routing.py文件,其中包含:

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

这个文件会从 consumers.py 文件中导入。

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)

我在终端中使用以下命令运行了Daphne:

[jnett@dev03.nlv ~/dashboard_jnett ]$(airview_env)[jnett@dev03.nlv ~/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>

我在另一个终端上运行了一个工作程序,命令如下:

[jnett@dev03.nlv ~/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

我在另一个终端上运行了Redis服务器,命令如下:

[jnett@dev03.nlv ~/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
我现在甚至没有尝试使用WebSocket-我只是试图先处理正常的HTTP流量,但是我只能从apache收到代理错误:

代理错误

代理服务器从上游服务器接收到无效的响应。代理服务器无法处理请求GET/dashboard_jnett/channeltest/。

原因:无法从远程服务器读取错误

其中apache错误日志给出了很多行,例如:

[Wed Jun 14 21:39:52.718388 2017] [proxy_http:error] [pid 13123] (70007)指定的超时时间已过期:[client 192.168.xx.xx:51814] AH01102:从远程服务器192.168.xx.xx:80读取状态行时发生错误 [Wed Jun 14 21:39:52.718426 2017] [proxy:error] [pid 13123] [client 192.168.xx.xx:51814] AH00898:从/dashboard_jnett/channeltest/远程服务器读取时发生错误

是否有人成功将此设置投入生产?如果我能弄清楚如何使apache正确地将流量引导到Daphne以进行正常的HTTP,则我可以从那里找到方法。


4
真的吗?没人曾成功地将Django Channels投入到生产环境中吗? - roninveracity
1
你能找到解决方案吗?我也在处理类似的问题。 - Parth Sharma
1个回答

1
我也曾努力让它在我的树莓派上运行,但最终成功了。
https://mikesmithers.wordpress.com/2017/02/21/configuring-django-with-apache-on-a-raspberry-pi/中得到了很好的建议。
Apache需要一些额外的软件包来提供Django应用程序的页面服务。
sudo apt-get install apache2-dev
sudo apt-get install libapache2-mod-wsgi-py3

同时需要设置MPM(多进程模块)。

a2dismod mpm_prefork
a2enmod mpm_worker
service apache2 restart

创建一个来自Django Channels文档的asgi.py示例。

https://channels.readthedocs.io/en/latest/deploying.html#run-protocol-servers

在我的情况下,我还需要将sys路径添加到我的项目中。
"""
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()

现在Daphne不应该抱怨了。
daphne -p 8001 WeatherStation.asgi:application

配置ASGI和Daphne以在Apache中使用Websockets,使用Apache处理HTTP请求。Apache充当反向代理,将所有Websocket请求重定向到在不同端口上运行的Daphne服务器。
leafpad /etc/apache2/sites-available/000-default.conf

和内容

...
        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>

确保Apache可以访问您的数据库和其他内容

chmod g+w ~/dvds/db.sqlite3
chmod g+w ~/dvds
sudo chown :www-data db.sqlite3
sudo chown :www-data ~/dvds

重新启动Apache以使这些更改生效:

sudo service apache2 restart

现在您已经在Apache中运行了一个WSGI服务器和一个用于Websockets的Daphne服务器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接