Websockets reverse proxy in IIS 8

后端 未结 3 914
醉梦人生
醉梦人生 2020-12-03 01:59

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 Serv

相关标签:
3条回答
  • 2020-12-03 02:17

    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.

    0 讨论(0)
  • 2020-12-03 02:20

    The TLDR is to:

    1. url redirect the ws and wss protocols correctly
    2. disable the compression in the node server socket io, passing the option perMessageDeflate. disable compression as APR 3 In IIS Proxy does not support it
    const io = require("socket.io")(server, {
      transports: ["websocket", "polling"],
      perMessageDeflate: false 
    });
    
    0 讨论(0)
  • 2020-12-03 02:33

    As stated by @jowo, IIS8/8.5 don't seem to support Sec-Websocket-Extensions. That being said, the workaround I applied is to simply rewrite the server variable in question. First add HTTP_SEC_WEBSOCKET_EXTENSIONS to the allowed server variable list, then add it to your rule:

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

    That way, the destination won't receive the troublesome permessage-deflate :)

    0 讨论(0)
提交回复
热议问题