Apache Websocket连接:升级由保持活动状态替换

时间:2018-12-27 04:02:31

标签: apache http websocket reverse-proxy keep-alive

我正在尝试解决一个令人费解的apache问题,即请求在到达上游服务之前要经过两层apache反向代理。大多数流量似乎都可以通过。值得注意的例外是websockets。

尤其是这是一个测试请求

curl -i -H 'Connection: Upgrade' -H 'Upgrade: websocket' localhost:80/test.html

当将请求从端口80代理到端口8080时,我注意到(使用tcpdump和Wireshark)Upgrade标头已被删除,而Connection: Keep-Alive已被设置。此外,我将Connection标头重置为Upgrade和Upgrade: websocket的任何尝试都是无用的。

请注意,上游服务需要Connection: UpgradeUpgrade: websocket来启动websocket(我收到没有这些标头的404错误)。

为什么Apache在代理自身时强制使用Connection: Keep-Alive?有什么方法可以强制它传递“连接/升级”标头或手动设置这些值?不幸的是,RequestHeader和朋友没有帮助。在没有ConnectionUpgrade通过反向代理的情况下,上游服务会呕吐并在websocket端点抛出404 / Not found。

ServerRoot "/usr/local/apache2"

Listen 80
Listen 8080

LogLevel rewrite:trace8

LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule xml2enc_module modules/mod_xml2enc.so
LoadModule proxy_html_module modules/mod_proxy_html.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule auth_mellon_module modules/mod_auth_mellon.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule ssl_module modules/mod_ssl.so

<IfModule unixd_module>
User daemon
Group daemon
</IfModule>

<VirtualHost *:80>
  LogLevel rewrite:trace8
  ServerName localhost

  RewriteEngine On

    RewriteCond %{HTTP:Upgrade} =websocket
    RewriteRule /(.*) ws://localhost:8080/$1 [P,L]
    RewriteCond %{HTTP:Upgrade} !=websocket
    RewriteRule /(.*) http://localhost:8080/$1 [P,L]

  ProxyPass / http://localhost:8080/
  ProxyPassReverse / http://localhost:8080/
  ProxyRequests Off
</VirtualHost> 

<VirtualHost *:8080>
    LogLevel rewrite:trace8

    ServerName localhost
    UseCanonicalName On
    RewriteEngine On

    ProxyPass / http://proxy-debug:8080/
    ProxyPassReverse / http://proxy-debug:8080/
    ProxyRequests Off
</VirtualHost>


ServerAdmin you@example.com


ErrorLog /proc/self/fd/2

DocumentRoot "/"

1 个答案:

答案 0 :(得分:0)

我不完全了解它是如何工作的,但是我收集的智慧和我设计的解决方案如下:

  • Apache从某种意义上(即与自己交谈时)不理解websocket。结果,您需要使用mod_rewrite并将协议设置为ws://才能向上游转发websocket(似乎没有设置头会在这里帮助您)。
  • 如果将协议设置为ws://,则Apache将在代理上游请求时设置适当的标头(Connection: UpgradeUpgrade: websocket)。但是,由于某种原因,在将请求代理到自身/另一个VirtualHost时似乎并没有这样做。
  • 您可以在VirtualHost *:80(下面复制)中看到一个blurb,它试图识别websocket是否井井有条,然后相应地更改协议。此不适用于VirtualHost *:8080 (请参见上面的项目符号)。其他方法是必需的。

    RewriteCond %{HTTP:Upgrade} =websocket
    RewriteRule /(.*) ws://localhost:8080/$1 [P,L]
    RewriteCond %{HTTP:Upgrade} !=websocket
    RewriteRule /(.*) http://localhost:8080/$1 [P,L]
    
  • 将所有这些放在一起,您必须向VirtualHost *:8080传达上游需要Websocket连接的信息。幸运的是,我们可以控制VirtualHost *:80并可以识别/传递这些信息。我们必须这样做,而不要碰到ConnectionUpgrade标头,因为Apache确实对它们标明了事情。协议可以是可追溯的,但是我不确定该怎么做。结果,我使用了一个虚假的内部自定义标头进行传输。最好以不太可能发生碰撞的方式来命名它。

VirtualHost *:80中,我们添加了一个像这样的块:

SetEnvIf Upgrade ^websocket$ websock=true
RequestHeader set X-Is-Websocket %{websock}e

然后我们在VirtualHost *:8080中读取该标头,并在必要时更改协议:

SetEnvIf X-Is-Websocket ^true$ websock=true
RequestHeader unset X-Is-Websocket

# change protocol if necessary
RewriteCond %{ENV:websock} =true
RewriteRule /(.*) ws://proxy-debug:8080/$1 [P,L]
RewriteCond %{ENV:websock} !=true
RewriteRule /(.*) http://proxy-debug:8080/$1 [P,L]

希望有帮助! :)