我在 Ubuntu 14.04 LTS 上运行 Apache 2.4.7 作为反向代理。此 Apache 服务器充当许多不同后端应用程序的入口点,这些应用程序可通过<Location>
块中的不同 mod_proxy 配置进行访问
我需要为使用 WebSockets 的应用程序提供反向代理访问。该应用程序是一个 Java Spring 应用程序,它通过 HTTP 提供 HTML 和其他静态文件,然后在页面加载后使用 WebSocket 提供动态数据。
Nginx
我使用以下配置来运行应用程序:
location /newapp/ {
proxy_pass http://newapp.example.com:8080/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
不幸的是,由于需要 Nginx 上没有的 Apache 身份验证模块,我无法在生产中使用它。
我想要在伪 Apache 配置中执行的操作是:
<Location /newapp/>
if not WebSockets:
ProxyPass http://newapp.example.com:8080/
ProxyPassReverse /
else
ProxyPass ws://newapp.example.com:8080/
ProxyPassReverse /
</Location>
Apachemod_proxy_wstunnel
模块让我认为这应该是可能的。 WebSocket 是通过 URL 访问的/api/socket/...
,因此我尝试ProxyPass
使用单独的<Location>
块来分离这两种类型:
<Location /newapp/>
ProxyPass http://newapp.example.com:8080/ disablereuse=on
ProxyPassReverse /
ProxyPassReverseCookieDomain newapp.example.com apps.example.com
ProxyPassReverseCookiePath http://newapp.example.com:8080/ /newapp/
</Location>
<Location /newapp/api/socket/>
ProxyPass ws://newapp.example.com:8080/api/socket/
ProxyPassReverse /
</Location>
这最初是有效的 - 浏览器请求http://apps.example.com/newapp/,页面通过 HTTP 加载,静态资产已加载,JavaScript 代码连接到 websocket,一切都很棒。
但是,当通过 HTTP 发出新请求时(例如,对于GET /newapp/static/someimage.png
),就会出错。此请求与 WebSocket Location 不匹配,因此我希望它GET /static/someimage.png
通过代理转到http://newapp.example.com:8080/
。
相反,应用服务器收到请求后GET /newapp/static/someimage.png
返回 404,因为这不是它所知道的 URL。这会破坏应用程序,因为应该可以正常工作的 HTTP 请求反而会失败。
笔记:
- 这不仅仅发生在图像上 -
GET /newapp/api/ajax/someapicall
也发生在错误代理上。 - 这并不总是会发生。在测试完成这个问题时,我设法让应用程序完全正常工作。这可能是基于时间的——在发出任何新的 HTTP 请求之前,我让应用程序运行了几分钟而没有与它交互。当我发出新的 HTTP 请求时,它们正确通过了。
- 禁用该
<Location /newapp/api/socket/>
部分会导致两件事发生 - WebSocket 连接失败,并且 HTTP 请求继续工作。 - 我通过浏览器的刷新按钮刷新页面后发现了这个问题。页面并没有再次加载,而是出现了应用程序的 404 屏幕。
我认为正在发生的情况是:
我认为mod_proxy_wstunnel
,一旦被第一个匹配的请求激活/newapp/api/socket/
,就会接管来自客户端的所有后续入站请求,无论它们是否匹配Location
。我通过RequestHeader set Test "some_identifying_value"
向每个请求添加一个指令来测试这一点Location
- 静态文件和的 HTTP 请求带有/api/socket/info
标Test
头,但错误代理的 HTTP 请求没有标Test
头,这表明它们直接通过而没有经过 Apache 指令的处理。
最终,我的问题是:是否可以将任何版本的 Apache (我很乐意升级!) 配置为反向代理基于 WebSocket 的应用程序,以便在 WebSocket 连接后也能正确地反向代理 HTTP 请求?如果是这样,如何配置?
答案1
安德斯的回答帮助我解决了 95% 的问题。
基本场景:
- 我们有一个服务器
newapp.example.com
- 端口 8080 同时运行 HTTP 和 WebSockets
- 响应 WebSockets 请求的 URL 是
/api/socket/
- 我们正在将此应用程序反向代理为
http://apps.example.com/newapp/
以下是如何在一个块中为上述场景配置 WebSocket 和 HTTP 反向代理的方法<Location>
:
<Location /newapp/>
ProxyPass http://newapp.example.com:8080/
ProxyPassReverse /
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule /api/(.*) ws://newapp.example.com:8080/api/$1 [P]
</Location>
最后的重写规则至关重要——没有它,我们会将请求传递/newapp/api/socket
到 WebSocket 服务器——但它会拒绝。
正则表达式正在解析后面的所有内容api
- 可能有更好的方法来捕获该块,但这种方法有效。然后我们必须记住重新添加/api/
到最终的重定向 URL。
最重要的是,WebSocket 连接建立后,HTTP 请求仍然有效!
答案2
我在 Spring Boot 应用程序前面使用 apache 2.4 作为代理,此应用程序提供一些 rest api 调用、websocket(sockjs)和一些静态页面。我在让 websocket 工作时遇到了一些问题,秘诀是添加重写规则你看到下面,现在它对我有用了,我的 apache 虚拟主机配置如下所示:
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/httpd/ssl/my.crt
SSLCertificateKeyFile /etc/httpd/ssl/my.key
SSLCertificateChainFile /etc/httpd/ssl/intermediate.crt
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
ProxyRequests Off
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule .* ws://localhost:6868%{REQUEST_URI} [P]
ServerName my.dnsname.com
</VirtualHost>