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

查看:2317
本文介绍了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中的一般样本规则:

 <重写> 
< rules>
< rule name =WS reverse proxystopProcessing =true>
< match url =(。*)/>
< conditions> < add input ={CACHE_URL}pattern =^(。+):///>
< / conditions>
< action type =Rewriteurl ={C:1}:// 10.2.1.10:5901 / websockify/>
< / rule>
< / rules>
< / rewrite>

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



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

解决方案

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


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


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

 
const WebSocketServer = require('ws');
const wss = new WebSocketServer({port:3011});
函数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反向代理,其中包含以下内容的web.config文件wstestlocalhost上的目录:

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

这应该转发 // localhost / wstest 到端口3011上的Node.js服务器。当我通过 ws:// localhost:3011 直接连接到它时,节点服务器工作。当我尝试通过 ws:// localhost / wstest 连接代理时,请求通过Node.js服务器,进行升级,连接是k。发送:

 
GET ws:// localhost / wstest HTTP / 1.1
主机:localhost
连接:升级
Pragma:no-cache
Cache-Control:no-cache
升级:websocket
来源: file://
Sec-WebSocket-Version:13
User-Agent:Mozilla / 5.0(Windows NT 6.3; WOW64)AppleWebKit / 537.36(KHTML,与Gecko一样)Chrome / 51.0.2704.84 Safari / 537.36
接受编码:gzip,deflate,sdch
接受语言:en-US,en; q = 0.8
Sec-WebSocket-Key:4ufu8nAOj7cKndASs4EX9w ==
Sec-WebSocket - 扩展:渗透 - 放气; 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,与Gecko一样)Chrome / 51.0.2704.84 Safari / 537.36
来源: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交换协议
升级: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 GMT
结束时间: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能够通过在自身和客户端之间进行协商来正确处理扩展,然后在自身和服务器之间进行单独的协商。就目前而言,它只是让客户端和服务器相互协商,从而导致此错误。


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天全站免登陆