【问题标题】:Websockets reverse proxy in IIS 8IIS 8 中的 Websockets 反向代理
【发布时间】:2016-03-22 20:56:55
【问题描述】:

我正在尝试通过 IIS 上的反向代理连接到 websockets 服务器 (websockify)。 IIS 和 websockets 服务器驻留在同一物理服务器上(Windows Server 2012 R2、IIS 8.5、ARR 3、启用了 Websockets)。我已经看到了一些关于此的问题,并且建议这应该与 IIS 8 和 ARR 3 一起使用,但目前还没有实际的解决方案。我对 IIS 中的 http/https 反向代理有一些经验,但这是我第一次尝试使用 websockets。

例如:

原网址: ws://10.2.1.10/websockify

反向代理需要将其转换为: ws://10.2.1.10:5901/websockify

web.config 中过于笼统的示例规则:

<rewrite>
     <rules>               
        <rule name="WS reverse proxy" stopProcessing="true"> 
          <match url="(.*)" />
          <conditions> <add input="{CACHE_URL}" pattern="^(.+)://" /> 
          </conditions> 
          <action type="Rewrite" url="{C:1}://10.2.1.10:5901/websockify"/>       
        </rule>
     </rules>
</rewrite>

根据失败的请求跟踪,该 url 似乎已被翻译,但由于某种原因,它没有到达位于 10.2.1.10:5901 的 websocket 服务器。

最终目标是结合 noVNC/websockify 以提供基于浏览器的客户端访问网络上的多个 VNC 服务器。任何帮助了解如何反向代理 websockets 表示赞赏。

【问题讨论】:

    标签: iis websocket reverse-proxy websockify novnc


    【解决方案1】:

    TLDR 是:

    1. url 正确重定向 ws 和 wss 协议
    2. 禁用节点服务器套接字io中的压缩,传递选项perMessageDeflate。在 IIS 代理中禁用压缩,因为 APR 3 不支持它
    const io = require("socket.io")(server, {
      transports: ["websocket", "polling"],
      perMessageDeflate: false 
    });
    

    【讨论】:

    • 想详细说明#1 中的“正确”是什么意思?
    【解决方案2】:

    正如@jowo 所说,IIS8/8.5 似乎不支持 Sec-Websocket-Extensions。话虽如此,我应用的解决方法是简单地重写有问题的服务器变量。首先将 HTTP_SEC_WEBSOCKET_EXTENSIONS 添加到允许的服务器变量列表中,然后将其添加到您的规则中:

    &lt;serverVariables&gt;&lt;set name="HTTP_SEC_WEBSOCKET_EXTENSIONS" value="" /&gt;&lt;/serverVariables&gt;

    这样,目的地就不会收到麻烦的 permessage-deflate :)

    【讨论】:

      【解决方案3】:

      我一直试图在带有 ARR 3.0 的 IIS 8.5 上完成同样的事情,但最终发现了问题。据微软的 Erez Benari 说,this is possible

      WebSocket 支持需要在 IIS 上安装 WebSocket 功能,但不需要任何其他配置或操作。使用服务器管理器添加角色和功能安装该功能,一旦完成,ARR 3.0 将适当地处理请求。

      作为测试,我为 WebSocket 设置了一个 Node.js 服务器:

      常量 WebSocketServer = 要求('ws'); const wss = new WebSocketServer({ 端口: 3011 }); 函数发送WSMessage(消息){ wss.clients.forEach((client) => { 客户端.send(msg); }); } 设置间隔(函数(){ sendWSMessage('你好客户端'); }, 3000);

      连同一个简单的测试页:

      var websock = new WebSocket('ws://localhost:3011'); websock.onmessage = 功能(事件){ 控制台日志(事件数据); }; websock.onopen = 功能(事件){ websock.send("你好服务器"); };

      然后,我在本地机器上设置了一个 ARR 反向代理,在 localhost 上“wstest”目录的 web.config 文件中包含以下内容:

      <?xml version="1.0" encoding="UTF-8"?>
      <configuration>
      <system.webServer>
          <rewrite>
              <rules>
                  <rule name="WebSocketTestRule" stopProcessing="true">
                      <match url=".*" />
                      <conditions>
                          <add input="{CACHE_URL}" pattern="^(.+)://" />
                      </conditions>
                      <action type="Rewrite" url="{C:1}://localhost:3011/" />
                  </rule>
              </rules>
          </rewrite>
      </system.webServer>
      </configuration>
      

      这应该将 //localhost/wstest 的所有流量转发到端口 3011 上的 Node.js 服务器。当我通过 ws://localhost:3011 直接连接到它时,Node 服务器工作。当我尝试通过代理通过ws://localhost/wstest 连接时,请求会通过 Node.js 服务器,进行升级并建立连接。

      Chrome 发送:

      获取 ws://localhost/wstest HTTP/1.1 主机:本地主机 连接:升级 Pragma:无缓存 缓存控制:无缓存 升级:websocket 来源:文件:// Sec-WebSocket-版本:13 用户代理:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36 接受编码:gzip、deflate、sdch 接受语言:en-US,en;q=0.8 Sec-WebSocket-Key:4ufu8nAOj7cKndASs4EX9w== Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits

      Node.js 服务器接收:

      缓存控制:无缓存 连接:升级 杂注:无缓存 升级:Websocket 接受编码:gzip、deflate、sdch 接受语言:en-US,en;q=0.8 主机:本地主机:3011 最大前锋:10 用户代理:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36 来源:文件:// sec-websocket 版本:13 sec-websocket-key: fBkTwAS9d/unXYKDE3+Jjg== sec-websocket-extensions:permessage-deflate; client_max_window_bits x-原始网址:/wstest x-转发-for: [::1]:54499 x-arr-log-id:a0b27458-9231-491d-b74b-07ae5a01c300

      Node.js 服务器响应:

      HTTP/1.1 101 交换协议 升级:websocket 连接:升级 Sec-Websocket-Accept: yep8mgQACAc93oGIk8Azde4WSXk= Sec-WebSocket-Extensions:permessage-deflate

      Chrome 终于收到了:

      HTTP/1.1 101 交换协议 升级:Websocket Sec-WebSocket-Accept: CBSM8dzuDoDG0OrJC28nIqaw/sI= Sec-WebSocket-Extensions:permessage-deflate X-Powered-By: ARR/3.0 连接:升级 X-Powered-By: ASP.NET 日期:格林威治标准时间 2016 年 6 月 10 日星期五 21:16:16 结束时间:17:16:16.148 接收字节数:0 发送字节数:0

      所以现在它们已连接。这一切看起来都不错,唯一明显的区别是 Sec-WebSocket-Key 和 Sec-WebSocket-Accept 被 IIS 或 ARR 代理双向更改。

      但是...没有任何 WebSocket 帧能够通过代理!当 Chrome 收到对其升级请求的肯定反馈时,它会发送其 WebSocket 消息帧,然后它会坐下来等待来自服务器的消息。 Node.js 服务器发送它的帧,没有发生错误,但 Chrome 永远不会收到它们。 Node.js 永远不会收到 Chrome 发送的消息。似乎 ARR/IIS 正在双向丢弃 WebSocket 帧。

      请注意 Chrome 如何告诉服务器它支持 permessage-deflate 扩展,这是一个用于按消息压缩的 WebSocket 扩展。服务器响应它也支持 permessage-deflate,所以当他们浏览器和服务器互相发送消息时,他们使用这个压缩扩展。然而,中间的那个人,ARR,显然不支持这种压缩!通过在服务器上关闭对 permessage-deflate 的支持,实际的 WebSocket 帧现在可以完美地通过代理:

      const wss = new WebSocketServer({ port: 3011, perMessageDeflate: false });

      我认为问题在于 ARR 3.0 不支持 Sec-Websocket-Extensions 标头,因此它允许标头简单地通过。但是允许客户端和服务端协商这个header是错误的,因为ARR不参与协商,也没有办法告诉双方它不支持传递压缩消息。希望有一天,ARR 能够通过自身与客户端之间的协商来正确处理扩展,然后在自身与服务器之间进行单独的协商。就目前而言,它只是让客户端和服务器相互协商,这导致了这个错误。

      【讨论】:

      • 我在 Server 2016 上使用带有 ARR 3.0 的 IIS 10,并且(一旦得知我需要安装 Websockets 功能)我正在使用 https 跨代理到 chrome 进行两种 websocket 通信(其中包括 Sec-Websocket-Extentions 中的 permessage-deflate 扩展)。我没有完全尝试您的测试(因为我正在使用自己的应用程序进行测试),但它似乎有效。也许在 IIS 10 中已修复?
      • @wojtow 感谢您的体验。我同意:如果在浏览器和代理之间压缩流量,那么 IIS 10 上的 ARR 3.0 必须支持 permessage-deflate 扩展。
      • @wojtow - 你的评论让我的生活变得更好了。我安装了 ARR 2.5 并且在升级请求时遇到了各种问题......我安装了 ARR 3.0 并且一切都按预期工作。
      • 我正在使用这个配置,但你没有提到的是你的套接字关闭得有多好。对我来说,添加 IIS 会导致服务器应用程序中的 ECONNRESET。不知何故,亲密的握手没有通过? stackoverflow.com/questions/64789997/…
      猜你喜欢
      • 2019-09-12
      • 2011-08-09
      • 2013-02-18
      • 1970-01-01
      • 1970-01-01
      • 2021-05-25
      • 1970-01-01
      • 1970-01-01
      • 2018-02-22
      相关资源
      最近更新 更多