【问题标题】:Apache websocket connection:upgrade replaced by keep-aliveApache websocket连接:升级替换为keep-alive
【发布时间】:2019-05-25 03:53:34
【问题描述】:

我试图找出一个令人麻木的 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: Upgrade 和 Upgrade: websocket 来启动 websocket(如果没有这些标头,我会收到 404 错误)。

为什么 Apache 在代理到自身时强制使用 Connection: Keep-Alive?有没有办法强制它传递连接/升级标头或手动设置这些值? RequestHeader 和朋友很遗憾没有提供帮助。如果没有 Connection 和 Upgrade 通过反向代理,上游服务会呕吐并在 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 "/"

【问题讨论】:

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


    【解决方案1】:

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

    • Apache 在某种意义上不理解 websockets(即当它自言自语时)。因此,您需要使用mod_rewrite 并将协议设置为ws:// 以便将websockets 转发到上游(似乎没有多少设置标头对您有帮助)。
    • 如果您将协议设置为ws://,那么Apache 将在上游代理请求时设置适当的标头(Connection: Upgrade 和Upgrade: websocket)。但是,由于某种原因,在将请求代理到自己/另一个 VirtualHost 时,它似乎没有这样做。
    • 您可以在VirtualHost *:80(复制如下)中看到一个试图辨别 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,并且可以识别/传递这些信息。我们必须在不触及 Connection 或 Upgrade 标头的情况下这样做,因为 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]
    

    希望对您有所帮助! :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-09-07
      • 2015-01-13
      • 1970-01-01
      • 1970-01-01
      • 2017-02-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多