web-dev-qa-db-fra.com

Proxy inverse Websockets dans IIS 8

J'essaie de me connecter à un serveur websockets (websockify) via un proxy inverse sur IIS. Le IIS et serveur websockets résident sur le même serveur physique (Windows Server 2012 R2, IIS 8.5, ARR 3, Websockets activés). J'ai vu un quelques questions à ce sujet et il est suggéré que cela devrait fonctionner avec IIS 8 et ARR 3, mais aucune solution réelle pour le moment. J'ai une certaine expérience avec les proxy inverses http/https dans IIS, mais c'est mon première tentative de travail avec les websockets.

Par exemple:

L'URL d'origine: ws: //10.2.1.10/websockify

Le proxy inverse doit traduire ceci en: ws: //10.2.1.10: 5901/websockify

Exemple de règle trop générale dans 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>

Selon la trace de la demande ayant échoué, l'URL semble être traduite, mais pour une raison quelconque, elle n'atteint pas le serveur Websocket à 10.2.1.10:5901.

L'objectif final est d'incorporer noVNC/websockify pour fournir un accès client basé sur un navigateur à plusieurs serveurs VNC sur le réseau. Toute aide pour comprendre comment inverser le proxy des websockets est appréciée.

14
cydc

J'avais essayé d'accomplir la même chose sur IIS 8.5 avec ARR 3.0, et j'ai finalement trouvé le problème. Selon Erez Benari de Microsoft, c'est possible :

La prise en charge de WebSocket nécessite que la fonctionnalité WebSocket soit installée sur IIS, mais ne nécessite aucune autre configuration ou action. Installez la fonctionnalité à l'aide du Gestionnaire de serveur Ajouter des rôles et des fonctionnalités, et une fois cela terminé, ARR 3.0 traitera les demandes de manière appropriée.

À titre de test, j'ai configuré un serveur Node.js pour WebSocket:

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

Avec une simple page de test:

 var websock = new WebSocket ('ws: // localhost: 3011'); 
 websock.onmessage = function (event) {
 console.log (event.data); 
}; 
 websock.onopen = fonction (événement) {
 websock.send ("bonjour le serveur"); 
};

Ensuite, j'ai configuré un proxy inverse ARR sur ma machine locale, avec les éléments suivants dans un fichier web.config d'un répertoire "wstest" sur 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>

Cela devrait transférer tout le trafic pour //localhost/wstest à un serveur Node.js sur le port 3011. Le serveur Node fonctionne lorsque je me connecte directement via ws://localhost:3011. Lorsque j'essaie de me connecter via le proxy via ws://localhost/wstest, la demande parvient au serveur Node.js, la mise à niveau se produit et la connexion est établie.

Chrome envoie:

 GET ws: // localhost/wstest HTTP/1.1 
 Hôte: localhost 
 Connexion: mise à niveau 
 Pragma: no-cache 
 Cache-Control : no-cache 
 Mise à niveau: websocket 
 Origine: fichier: // 
 Sec-WebSocket-Version: 13 
 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, comme 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-dégonflage; client_max_window_bits 

Le serveur Node.js reçoit:

 cache-control: no-cache 
 connexion: 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, comme Gecko) Chrome/51.0.2704.84 Safari/537.36 
 Origine: fichier: // 
 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 

Le serveur Node.js répond avec:

 HTTP/1.1 101 Protocoles de commutation 
 Mise à niveau: websocket 
 Connexion: mise à niveau 
 Sec-Websocket-Accept: yep8mgQACAc93oGIk8Azde4WSXk = 
 Sec-WebSocket-Extensions : permessage-dégonfler 

Et enfin Chrome reçoit:

 HTTP/1.1 101 Protocoles de commutation 
 Mise à niveau: Websocket 
 Sec-WebSocket-Accept: CBSM8dzuDoDG0OrJC28nIqaw/sI = 
 Sec-WebSocket-Extensions: permessage-dégonfler 
 X-Powered-By: ARR/3.0 
 Connexion: Mettre à niveau 
 X-Powered-By: ASP.NET 
 Date: ven. 10 juin 2016 21:16: 16 GMT 
 EndTime: 17: 16: 16.148 
 ReceivedBytes: 0 
 SentBytes: 0 

Alors maintenant, ils sont connectés. Tout cela semble bon, la seule différence notable étant que Sec-WebSocket-Key et Sec-WebSocket-Accept sont modifiés dans les deux sens par IIS ou le proxy ARR.

Mais ... aucun cadre WebSocket ne passe jamais par le proxy! Lorsque Chrome reçoit des commentaires positifs sur sa demande de mise à niveau, il envoie son cadre de message WebSocket, puis il attend et attend les messages du serveur. Le serveur Node.js envoie ses cadres, et aucun erreur se produit, mais ils ne sont jamais reçus par Chrome. Le message que Chrome envoyé n'est jamais reçu par Node.js. Il semble que ARR/IIS supprime les cadres WebSocket dans les deux directions.

Remarquez comment Chrome indique au serveur qu'il prend en charge l'extension permessage-deflate, qui est une extension WebSocket pour la compression par message. Le serveur répond qu'il prend également en charge permessage-deflate, donc quand ils naviguent et le serveur s’envoient leurs messages, ils utilisent cette extension de compression. CEPENDANT, le gars du milieu, ARR, ne supporte apparemment PAS cette compression! peut désormais passer sans problème par le proxy:

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

Je pense que le problème est que ARR 3.0 ne prend pas en charge le Sec-Websocket-Extensions header, ce qui permet à l'en-tête de simplement passer. Mais permettre à cet en-tête d'être négocié entre le client et le serveur est faux, car ARR n'est pas impliqué dans la négociation et n'a aucun moyen de dire aux deux parties qu'il ne prend pas en charge la transmission de messages compressés. Espérons qu'un jour, ARR sera en mesure de gérer correctement les extensions en négociant entre lui-même et le client, puis en effectuant une négociation distincte entre lui-même et le serveur. Dans l'état actuel des choses, le client et le serveur négocient simplement entre eux, ce qui entraîne cette erreur.

23
jowo

Comme indiqué par @jowo, IIS8/8.5 ne semble pas prendre en charge les extensions Sec-Websocket. Cela étant dit, la solution de contournement que j'ai appliquée consiste à simplement réécrire la variable serveur en question. Ajoutez d'abord HTTP_SEC_WEBSOCKET_EXTENSIONS à la liste des variables de serveur autorisées, puis ajoutez-le à votre règle:

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

De cette façon, la destination ne recevra pas le dégonflage permessage gênant :)

6
emboss64

Le TLDR consiste à:

  1. uRL rediriger correctement les protocoles ws et wss
  2. désactiver la compression dans le socket du serveur de noeud io, en passant l'option perMessageDeflate. désactiver la compression comme APR 3 In IIS Proxy ne le prend pas en charge
const io = require("socket.io")(server, {
  transports: ["websocket", "polling"],
  perMessageDeflate: false 
});
0
kabapy