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.
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.
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 :)
Le TLDR consiste à:
perMessageDeflate
. désactiver la compression comme APR 3 In IIS Proxy ne le prend pas en chargeconst io = require("socket.io")(server, {
transports: ["websocket", "polling"],
perMessageDeflate: false
});