如果有人发现自己处于类似情况,这就是我如何在 MacOS Server 5.2 中通过 Apache 获得 WebSocket 连接。
而且解决方案很简单。
短版:
我使用 MacOS Server 5.2(Apache 2.4.23 附带)通过mod_wsgi 模块运行 python Django 应用程序。
我一直试图在 MacOS 10.12 和 Server 5.2 中设置 proxypass 和 wstunnel,以通过在 localhost 端口 8001 上运行的名为 Daphne 的 ASGI 接口服务器处理 websocket 连接。
我想将任何 WebSocket 连接反向代理到 wss://myapp.local/chat/stream/ 到 ws://localhost:8001/chat/stream/
根据我在所有论坛和邮件列表中阅读的内容,我在适当的虚拟主机中简单地定义了一些 proxypass 并确保加载了 mod_proxy 和 mod_proxy_wstunnel 模块并它会工作的。
长话短说 - 据我了解,所有这些问题都归结为 MacOS Server 5 和一个重大变化:
“一个 httpd 实例作为反向代理运行,称为服务代理,另外几个 httpd 实例在该代理后面运行,以支持特定的基于 HTTP 的服务,包括网站服务的一个实例。”
代理 websocket 连接我需要做的事情如下:
输入:/Library/Server/Web/Config/Proxy/apache_serviceproxy.conf
添加(在line 297附近(在关于用户网站的部分,webdav):
ProxyPass / http://localhost:8001/
ProxyPassReverse / http://localhost:8001/
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
RewriteRule .* ws://localhost:8001%{REQUEST_URI} [P]
然后我踢了服务代理:
sudo launchctl unload -w /Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons/com.apple.serviceproxy.plist
sudo launchctl load -w /Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons/com.apple.serviceproxy.plist
并且网络套接字连接立即工作!
长版:
多年来,我一直在尝试在我正在开发的应用程序中让 WebSocket 连接与 Apache 和 Andrew Godwin 的 Django Channels 项目一起运行。
Django 频道是
“一个项目使 Django 能够处理的不仅仅是普通的 HTTP 请求,包括 WebSockets 和 HTTP2,以及在发送响应后运行代码的能力,例如缩略图或背景计算。”
我对 Django Channels 的兴趣来自我对 web 应用程序中聊天系统的需求。在 youtube 上观看了几个 Andrew 的演示并阅读了文档并最终安装了 Andrew 的演示 django 频道项目后,我认为我可以在我们的 MacOS 服务器上将其投入生产。
MacOS 10.12 和 Server 5.2 的当前版本随 Apache 2.4.23 一起提供。它带有必要的 mod_proxy_wstunnel 模块,以便能够在 Apache 中代理 WebSocket 连接(ws:// 和安全 wss://),并且已经加载到服务器配置文件中:
/Library/Server/Web/Config/apache2/httpd_server_app.conf
Daphne 是 Andrew 的 ASGI 接口服务器,支持 WebSockets 和长轮询 HTTP 请求。 WSGI 没有。
由于 Daphne 在 MacOS 未占用的端口上的 localhost 上运行(我选择了8001),因此想法是让 Apache 反向代理对 Daphne 的某些请求。
Daphne 可以在指定端口(本例中为 8001)上运行,如下所示(-v2 更详细):
daphne -p 8001 yourapp.asgi:channel_layer -v2
我希望 Daphne 只处理 Web 套接字连接(因为我目前使用依赖于一些 apache 模块来提供媒体服务,例如 mod_xsendfile)。在我的例子中,websocket 连接是通过 /chat/stream/ 基于 Andrew 的演示项目。
根据我在 MacOS Server 的 Apache 实现中所读到的内容,想法是在您的“站点”的虚拟主机文件中声明这些 proxypass 命令:/Library/Server/Web/Config/apache2/sites/
在配置文件中,例如:0000_127.0.0.1_34543_.conf
我还读到,对于在 MacOS Server 上运行的 Web 应用程序的任何自定义都应在以下位置对所需 Web 应用程序的 plist 文件进行:/Library/Server/Web/Config/apache2/webapps/
在 plist 文件中,例如:com.apple.webapp.wsgi.plist
无论如何...
我编辑了0000_127.0.0.1_34543_.conf 文件添加:
ProxyPass /chat/stream/ ws://localhost:8001/
ProxyPassReverse /chat/stream/ ws://localhost:8001/
急于测试我的第一个 Web 套接字聊天连接,我刷新了页面,却发现 apache 日志中打印了一个错误:
No protocol handler was valid for the URL /chat/stream/. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule.
我读过很多人至少在 Ubuntu 上使用 Apache 或在 MacOS 上自定义安装找到了解决方案。
我什至尝试使用 Brew 安装 Apache,但当它不起作用时,我几乎开始安装 nginx。
经过无数小时/天的谷歌搜索后,我访问了 Apache 邮件列表以寻求有关此错误的帮助。 Yann Ylavic 对他的时间非常慷慨,并为我提供了各种关于如何进行的想法。尝试以下方法后:
SetEnvIf Request_URI ^/chat/stream/ is_websocket
RequestHeader set Upgrade WebSocket env=is_websocket
ProxyPass /chat/stream/ ws://myserver.local:8001/chat/stream/
我注意到端口 8001 Daphne 上的接口服务器开始接收 ws 连接!
但是在客户端浏览器中它正在记录:
"Error during WebSocket handshake: 'Upgrade' header is missing"
据我所知,mod_dumpio 记录了 "Connection: Upgrade" 和 "Upgrade: WebSocket" 标头作为 Web 套接字握手的一部分发送:
mod_dumpio: dumpio_in (data-HEAP): HTTP/1.1 101 Switching Protocols\r\nServer: AutobahnPython/0.17.1\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: 17WYrMeMS8a4ImHpU0gS3/k0+Cg=\r\n\r\n
mod_dumpio.c(164): [client 127.0.0.1:63944] mod_dumpio: dumpio_out
mod_dumpio.c(58): [client 127.0.0.1:63944] mod_dumpio: dumpio_out (data-TRANSIENT): 160 bytes
mod_dumpio.c(100): [client 127.0.0.1:63944] mod_dumpio: dumpio_out (data-TRANSIENT): HTTP/1.1 101 Switching Protocols\r\nServer: AutobahnPython/0.17.1\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: 17WYrMeMS8a4ImHpU0gS3/k0+Cg=\r\n\r\n
但是客户端浏览器在响应标头中没有显示任何内容。
我比以往任何时候都更难过。
我探索了客户端 jQuery 框架以及 Django 频道和高速公路模块,看看是否有问题,然后修改了我自己的应用程序以及关于 Apache 及其模块的各种建议组合。但没有什么对我来说很突出。
然后我重新阅读了 apache2 目录中的 ReadMe.txt:/Library/Server/Web/Config/apache2/ReadMe.txt
"关于Server应用中web代理架构的特别说明
5.0:
此版本的服务器应用程序包含所有基于 HTTP 的服务的修订架构。在以前的版本中,有一个 httpd 实例作为 Wiki、Profile 和日历/地址服务的反向代理,也作为网站服务。在这个版本中,有一个重大变化:单个httpd 的实例作为反向代理运行,称为 Service Proxy,另外几个 httpd 实例在该代理后面运行以支持特定的基于 HTTP 的服务,包括网站服务的实例。
由于网站服务的httpd 实例现在位于反向代理或Service Proxy 后面,请注意以下几点:......只有外部Service Proxy httpd 实例侦听TCP 端口80和443;它代理对网站和其他基于 HTTP 的服务的 HTTP 请求和响应。 ..."
我想知道 ServiceProxy 是否与它有关。我看了看:/Library/Server/Web/Config/Proxy/apache_serviceproxy.conf
并注意到一条评论 - "# The user websites, and webdav"。
我认为尝试添加人们在论坛上建议的proxypass 定义和rewrite 规则作为他们的解决方案不会有什么坏处。
ProxyPass / http://localhost:8001/
ProxyPassReverse / http://localhost:8001/
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
RewriteRule .* ws://localhost:8001%{REQUEST_URI} [P]
果然重启ServiceProxy后就开始工作了!
亚当