IIS 8中的Websockets反向代理

时间:2015-12-16 16:10:50

标签: iis websocket reverse-proxy websockify novnc

我正在尝试通过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>

根据失败的请求跟踪,网址似乎已被翻译,但由于某种原因,它无法到达10.2.1.10:5901的websocket服务器。

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

3 个答案:

答案 0 :(得分:17)

我一直在尝试使用ARR 3.0在IIS 8.5上完成同样的事情,并最终找到了问题。根据微软的Erez Benari,this is possible

  

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

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

const WebSocketServer = require('ws');
const wss = new WebSocketServer({ port: 3011 });
function sendWSMessage(msg) {
    wss.clients.forEach((client) => {
        client.send(msg);
    });
}
setInterval(function() {
    sendWSMessage('hello client');
}, 3000);

除了一个简单的测试页面:

var websock = new WebSocket('ws://localhost:3011');
websock.onmessage = function (event) {
    console.log(event.data);
};
websock.onopen = function (event) {
  websock.send("hello server"); 
};

然后,我在本地计算机上设置了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直接连接到它时工作。当我尝试通过ws://localhost/wstest通过代理连接时,请求通过Node.js服务器,进行升级,并建立连接。

Chrome发送:

GET ws://localhost/wstest HTTP/1.1
Host: localhost
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: file://
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Sec-WebSocket-Key: 4ufu8nAOj7cKndASs4EX9w==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Node.js服务器收到:

cache-control: no-cache
connection: upgrade
pragma: no-cache
upgrade: Websocket
accept-encoding: gzip, deflate, sdch
accept-language: en-US,en;q=0.8
host: localhost:3011
max-forwards: 10
user-agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36
origin: file://
sec-websocket-version: 13
sec-websocket-key: fBkTwAS9d/unXYKDE3+Jjg==
sec-websocket-extensions: permessage-deflate; client_max_window_bits
x-original-url: /wstest
x-forwarded-for: [::1]:54499
x-arr-log-id: a0b27458-9231-491d-b74b-07ae5a01c300

Node.js服务器响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-Websocket-Accept: yep8mgQACAc93oGIk8Azde4WSXk=
Sec-WebSocket-Extensions: permessage-deflate

最后Chrome收到了:

HTTP/1.1 101 Switching Protocols
Upgrade: Websocket
Sec-WebSocket-Accept: CBSM8dzuDoDG0OrJC28nIqaw/sI=
Sec-WebSocket-Extensions: permessage-deflate
X-Powered-By: ARR/3.0
Connection: Upgrade
X-Powered-By: ASP.NET
Date: Fri, 10 Jun 2016 21:16:16 GMT
EndTime: 17:16:16.148
ReceivedBytes: 0
SentBytes: 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标头,所以它允许标头简单地通过。但允许在客户端和服务器之间协商此标头是错误的,因为ARR不参与协商,并且无法告诉双方它不支持传递压缩消息。希望有一天,ARR能够通过在自身和客户端之间进行协商来正确处理扩展,然后在自身和服务器之间进行单独的协商。就目前而言,它只是让客户端和服务器相互协商,从而导致此错误。

答案 1 :(得分:4)

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

<serverVariables><set name="HTTP_SEC_WEBSOCKET_EXTENSIONS" value="" /></serverVariables>

这样,目的地就不会受到麻烦的通货膨胀的影响:)

答案 2 :(得分:-1)

TLDR将:

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