IIS 8 中的 Websockets 反向代理 [英] Websockets reverse proxy in IIS 8

查看:78
本文介绍了IIS 8 中的 Websockets 反向代理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试通过 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 中过于笼统的示例规则:

<规则><规则名称="WS反向代理" stopProcessing="true"><match url="(.*)"/><条件><add input="{CACHE_URL}" pattern="^(.+)://"/></条件><action type="Rewrite" url="{C:1}://10.2.1.10:5901/websockify"/></规则></规则></rewrite>

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

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

解决方案

我一直试图在带有 ARR 3.0 的 IIS 8.5 上完成同样的事情,最终发现了问题.根据微软的 Erez Benari,这是可能的:

<块引用>

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

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

<前>const WebSocketServer = require('ws');const wss = new WebSocketServer({ port: 3011 });函数 sendWSMessage(msg) {wss.clients.forEach((client) => {客户端发送(味精);});}设置间隔(函数(){sendWSMessage('你好客户端');}, 3000);

连同一个简单的测试页:

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

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

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

Chrome 发送:

<前>获取 ws://localhost/wstest HTTP/1.1主机:本地主机连接:升级编译指示:无缓存缓存控制:无缓存升级: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.8Sec-WebSocket-Key:4ufu8nAOj7cKndASs4EX9w==Sec-WebSocket-Extensions:permessage-deflate;client_max_window_bits

Node.js 服务器接收:

<前>缓存控制:无缓存连接:升级pragma: 无缓存升级: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-version: 13sec-websocket-key: fBkTwAS9d/unXYKDE3+Jjg==sec-websocket-extensions: permessage-deflate;client_max_window_bitsx-原始网址:/wstestx-forwarded-for: [::1]:54499x-arr-log-id:a0b27458-9231-491d-b74b-07ae5a01c300

Node.js 服务器响应:

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

最后 Chrome 收到:

<前>HTTP/1.1 101 交换协议升级:WebsocketSec-WebSocket-接受:CBSM8dzuDoDG0OrJC28nIqaw/sI=Sec-WebSocket-Extensions: permessage-deflateX-Powered-By:ARR/3.0连接:升级X-Powered-By: ASP.NET日期:2016 年 6 月 10 日星期五 21:16:16 GMT结束时间: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 标头,因此它允许标头简单地通过.但是让这个头在客户端和服务器之间协商是错误的,因为ARR没有参与协商,也没有办法告诉双方它不支持传递压缩消息.希望有一天,ARR 能够通过在自身和客户端之间进行协商,然后在自身和服务器之间进行单独协商来正确处理扩展.就目前而言,它只是让客户端和服务器相互协商,从而导致此错误.

I'm attempting to connect to a websockets server (websockify) through a reverse proxy on IIS. The IIS and websockets server reside on the same physical server (Windows Server 2012 R2, IIS 8.5, ARR 3, Websockets enabled). I've seen a few questions about this and it's suggested this should work with IIS 8 and ARR 3, but no actual solutions as yet. I have some experience with http/https reverse proxies in IIS, but this is my first attempt working with websockets.

For example:

The original url: ws://10.2.1.10/websockify

The reverse proxy needs to translate this to: ws://10.2.1.10:5901/websockify

Overly general sample rule in 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>

Per the Failed Request Trace, the url appears to be translated, but for some reason it doesn't reach the websocket server at 10.2.1.10:5901.

The end goal is to incorporate noVNC/websockify to provide browser based client access to multiple VNC servers on the network. Any help understanding how to reverse proxy the websockets is appreciated.

解决方案

I had been trying to accomplish this same thing on IIS 8.5 with ARR 3.0, and eventually found the problem. According to Microsoft's Erez Benari, this is possible:

WebSocket support requires the WebSocket feature to be installed on IIS, but does not require any other configuration or action. Install the feature using the Server Manager Add Roles and Features, and once that is complete, ARR 3.0 will handle the requests appropriately.

As a test, I set up a Node.js server for WebSocket:

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);

Along with a simple test page:

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

Then, I set up an ARR reverse proxy on my local machine, with the following in a web.config file of a "wstest" directory on localhost:

<?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>

This should forward all traffic for //localhost/wstest to a Node.js server on port 3011. The Node server works when I directly connect to it via ws://localhost:3011. When I try to connect through the proxy via ws://localhost/wstest, the request makes it through to the Node.js server, the upgrade occurs, and the connection is made.

Chrome sends:

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

The Node.js server receives:

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

The Node.js server responds with:

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

And finally Chrome receives:

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

So now they are connected. This all looks good, the only noticeable difference being that the Sec-WebSocket-Key and Sec-WebSocket-Accept is changed in both directions by either IIS or the ARR proxy.

But... no WebSocket frames ever make it through the proxy! When Chrome receives positive feedback on its upgrade request, it sends its WebSocket message frame, and it is then sitting and waiting for messages from the server. The Node.js server sends its frames, and no error occurs, but they are never received by Chrome. The message that Chrome sent is never received by Node.js. It appears that ARR/IIS is dropping the WebSocket frames in both directions.

Notice how Chrome is telling the server that it supports the permessage-deflate extension, which is a WebSocket extension for per-message compression. The server is responding that it also supports permessage-deflate, so when they browser and server send their messages to each other, they use this compression extension. HOWEVER, the guy in the middle, ARR, apparently does NOT support this compression! By turning off support for permessage-deflate on the server, the actual WebSocket frames can now pass through the proxy flawlessly:

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

I think the issue is that ARR 3.0 does not support the Sec-Websocket-Extensions header, so it is allowing the header to simply pass through. But allowing this header to be negotiated between the client and the server is wrong, because ARR is not involved in the negotiation and has no way of telling the two parties that it does not support passing compressed messages. Hopefully someday, ARR will be able to properly handle extensions by negotiating between itself and the client, and then doing a separate negotiation between itself and the server. As it stands now, it simply has the client and server negotiating with each other, which results in this error.

这篇关于IIS 8 中的 Websockets 反向代理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆