Catching déconnecte avec Apache Load Balancer et le backend node / io.socket

REMARQUE: J’ai résolu la plupart du problème, mais je rencontre toujours un problème avec la capture des déconnexions, comme indiqué au bas de ce message dans la section Mise à jour.

NOTE 2: Comme demandé, j’ai affiché une vue plus complète de ma configuration. Voir la rubrique au bas de ce post.

J’essaie de configurer un équilibreur de charge dans Apache, mais cela ne fonctionne pas pour socket.io. Mon code Apache ressemble à ceci:

 ServerAdmin [email protected] ServerName jpl.example.com ProxyRequests off Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED  BalancerMember "http://203.0.113.22:3000" route=1 BalancerMember "http://203.0.113.23:3000" route=2  ProxyPass "/test/" "balancer://mycluster/" ProxyPassReverse "/test/" "balancer://mycluster/"  

Problèmes avec socket.io

Le problème auquel je suis confronté est que sur le backend j’ai un serveur node.js qui utilise les connexions socket.io pour une longue interrogation dans subdir1 / index.html et subdir2.index.html. Malheureusement, socket.io aime ne fonctionner que depuis le répertoire racine:

 http://203.0.113.22:3000/socket.io/ 

Il est impossible de le trouver si j’essaie de l’exécuter depuis:

 http://jpl.example.com/test/socket.io 

Le début de mon fichier index.js sur le serveur ressemble à ceci:

 // Setup basic express server var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); 

Une partie de mon /subdir1/index.html (également chargé depuis le serveur) ressemblait à ceci:

   var socket = io.connect(); socket.on('notification', function (data) { 

Mais je recevais maintenant une erreur lors de l’access via le proxy. L’erreur était:

 http://jpl.example.com/socket.io/socket.io.js 404 (Not Found) 

J’ai essayé de changer cela en:

    var refresh_value = 0; var refresh_time = 0; //var socket = io.connect(); var socket = io.connect('http://example.com/', {path: "/test/"}); socket.on('notification', function (data) { 

Il ne me donne plus d’erreur, mais rien n’indique qu’il communique avec le socket.

Qu’est-ce que je fais mal ici et comment puis-je le faire fonctionner?

Mettre à jour

J’ai maintenant principalement résolu le problème avec l’utilisation de:

 var socket = io.connect('http://example.com/', {path: "/test/socket.io"}); 

au lieu de:

 var socket = io.connect('http://example.com/', {path: "/test/"}); 

Problème final:

Les choses fonctionnent maintenant, mais je suis toujours confronté au problème suivant:

Il faut environ une minute pour qu’un client ait effectivement fermé une page. Sans un équilibreur de charge Proxy et Apache, je n’ai pas ce problème. J’ai essayé différentes choses, par exemple définir KeepAlive sur “no” et modifier le VirtualHost en haut de cette page avec les éléments suivants:

   BalancerMember "http://203.0.113.22:3000" route=1 max=128 ttl=300 retry=60 connectiontimeout=5 timeout=300 ping=2 BalancerMember "http://203.0.113.23:3000" route=2 max=128 ttl=300 retry=60 connectiontimeout=5 timeout=300 ping=2  

Mais cela prend encore environ une minute avant qu’il reconnaisse qu’un client a quitté la page. Que puis-je faire pour résoudre ce problème?

Une vue plus complète de ma configuration

Comme demandé, pour aider à diagnostiquer le problème, je publie une vue plus complète de ma configuration. J’ai éliminé autant que je pensais pouvoir tout en fournissant autant de détails que possible:

Mon fichier Apache actuel:

  # Admin email, Server Name (domain name), and any aliases ServerAdmin [email protected] ServerName jpl.example.com ProxyRequests off Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED  BalancerMember "http://203.0.113.22:3000" route=1 keepalive=On smax=1 connectiontimeout=10 retry=600 timeout=900 ttl=900 BalancerMember "http://203.0.113.23:3000" route=2 keepalive=On smax=1 connectiontimeout=10 retry=600 timeout=900 ttl=900 ProxySet stickysession=ROUTEID   BalancerMember "ws://203.0.113.22:3000" route=1 keepalive=On smax=1 connectiontimeout=10 retry=600 timeout=900 ttl=900 BalancerMember "ws://203.0.113.23:3000" route=2 keepalive=On smax=1 connectiontimeout=10 retry=600 timeout=900 ttl=900 ProxySet stickysession=ROUTEID  RewriteEngine On RewriteCond %{REQUEST_URI} ^/test/socket.io [NC] RewriteCond %{QUERY_STRING} transport=websocket [NC] RewriteRule /(.*) balancer://myws/$1 [P,L] ProxyPass "/test/" "balancer://mycluster/" ProxyPassReverse "/test/" "balancer://mycluster/"  

Sur chacun de ces serveurs, j’ai une installation de noeud. Le fichier index.js principal ressemble à ceci:

 /************ Set Variables ******************/ // Setup basic express server var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); var port = process.env.PORT || 3000; var fs = require('fs'), mysql = require('mysql'), connectionsArray = [], connection = mysql.createConnection({ host : 'localhost', user : 'myusername', password : 'mypassword', database : 'mydatabase', port : 3306 }), POLLING_INTERVAL = 5000; server.listen(port, function () { console.log("-----------------------------------"); console.log('Server listening at port %d', port); }); // Routing app.use(express.static(__dirname + "/public")); /********* Connect to DB ******************/ connection.connect(function(err) { if (err == null){ console.log("Connected to Database!"); } else { console.log( err ); process.exit(); } }); /*********************** Looping *********************/ var pollingLoop = function () { var query = connection.query('SELECT * FROM spec_table'), specs = []; query .on('error', function(err) { console.log( err ); updateSockets( err ); }) .on('result', function( spec ) { specs.push( spec ); }) .on('end',function(){ pollingLoop2(specs); }); }; var pollingLoop2 = function (specs) { // Make the database query var query = connection.query('SELECT * FROM info_table'), infos = []; // set up the query listeners query .on('error', function(err) { console.log( err ); updateSockets( err ); }) .on('result', function( info ) { infos.push( info ); }) .on('end',function(){ if(connectionsArray.length) { setTimeout( pollingLoop, POLLING_INTERVAL ); updateSockets({specs:specs, infos:infos}); } }); }; /*************** Create new websocket ****************/ //This is where I can tell who connected and who disconnected. io.sockets.on('connection', function ( socket ) { var socketId = socket.id; var clientIp = socket.request.connection.remoteAddress; var time = new Date(); console.log(time); console.log("\033[32mJOINED\033[0m: "+ clientIp + " (Socket ID: " + socketId + ")"); // start the polling loop only if at least there is one user connected if (!connectionsArray.length) { pollingLoop(); } socket.on('disconnect', function () { var socketIndex = connectionsArray.indexOf( socket ); var time = new Date(); console.log(time); console.log("\033[31mLEFT\033[0m: "+ clientIp + " (Socket ID: " + socketId + ")"); if (socketIndex >= 0) { connectionsArray.splice( socketIndex, 1 ); } console.log(' Number of connections: ' + connectionsArray.length); }); connectionsArray.push( socket ); console.log(' Number of connections: ' + connectionsArray.length); }); /********* Function updateSockets ************/ var updateSockets = function ( data ) { connectionsArray.forEach(function( tmpSocket ){ tmpSocket.volatile.emit( 'notification' , data ); }); }; 

Enfin, dans mon fichier public / dir1 / index.html, j’ai quelque chose qui ressemble à ceci:

 //HTML code here   var socket = io.connect('', {path: "/test/socket.io"}); socket.on('notification', function (data) { $.each(data.specs,function(index,spec){ //Other js code here }) })  //More HTML code here 

Avec cette configuration particulière, la connexion fonctionne, mais cela prend plus d’une minute avant que je puisse détecter qu’une page est fermée. De plus, avec cette configuration, une erreur est consignée dans la console:

 WebSocket connection to 'ws://jpl.example.com/test/socket.io/?EIO=3&transport=websocket&sid=QE5aCExz3nAGBYcZAAAA' failed: Connection closed before receiving a handshake response ws @ socket.io.js:5325 

Que fais-je mal et comment puis-je réparer mon code pour pouvoir détecter les déconnexions au moment où elles se produisent?

Note: Cela fonctionne très bien si je n’utilise pas un sous-répertoire / test /.

S’il vous plaît noter également: ce n’est qu’un sous-répertoire apparaissant dans l’URL. Il n’existe dans le système de fichiers nulle part.

Aussi, je suis ouvert aux conseils et suggestions si vous remarquez des zones dans mon code que je pourrais écrire mieux.

Donc après quelques essais et essais, j’ai pu obtenir une configuration qui fonctionne bien. Les modifications requirejses

Chemin de base sur le serveur

Vous devez également utiliser le chemin de base sur le serveur

 var io = require('socket.io')(server, { path: '/test/socket.io'}); 

Et puis ci-dessous, la configuration Apache mise à jour que j’ai utilisée

  # Admin email, Server Name (domain name), and any aliases ServerAdmin [email protected] ProxyRequests off #Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED Header add Set-Cookie "SERVERID=sticky.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED  BalancerMember "http://127.0.0.1:3001" route=1 keepalive=On smax=1 connectiontimeout=10 retry=600 timeout=900 ttl=900 BalancerMember "http://127.0.0.1:3000" route=2 keepalive=On smax=1 connectiontimeout=10 retry=600 timeout=900 ttl=900 ProxySet stickysession=SERVERID   BalancerMember "ws://127.0.0.1:3001" route=1 keepalive=On smax=1 connectiontimeout=10 retry=600 timeout=900 ttl=900 BalancerMember "ws://127.0.0.1:3000" route=2 keepalive=On smax=1 connectiontimeout=10 retry=600 timeout=900 ttl=900 ProxySet stickysession=SERVERID  RewriteEngine On RewriteCond %{HTTP:Upgrade} =websocket [NC] RewriteRule /(.*) balancer://myws/$1 [P,L] RewriteCond %{HTTP:Upgrade} !=websocket [NC] RewriteRule /(.*) balancer://mycluster/$1 [P,L] ProxyTimeout 3  

Et maintenant les déconnexions sont immédiates

Déconnecter

Je ne suis pas familier avec Apache mod_proxy, mais je pense que votre problème est lié à vos chemins.

Je mets un petit test pour voir si je peux aider (et avoir un jeu), dans mon test je vais proxy à la fois HTTP et ws le trafic vers un seul backend. Quel est ce que vous faites plus des websockets.

Serveurs (conteneurs LXD):

  • 10.158.250.99 est le proxy.
  • 10.158.250.137 est le noeud.

Tout d’abord, activez les mods apache sur le proxy:

 sudo a2enmod proxy sudo a2enmod proxy_http sudo a2enmod proxy_wstunnel sudo a2enmod proxy_balancer sudo a2enmod lbmethod_byrequests 

Puis changez 000-default.conf :

 sudo nano /etc/apache2/sites-available/000-default.conf 

C’est ce que j’ai utilisé après avoir effacé les commentaires:

  ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined  BalancerMember http://10.158.250.137:7779  ProxyPreserveHost On # web proxy - forwards to mycluser nodes ProxyPass /test/ balancer://mycluster/ ProxyPassReverse /test/ balancer://mycluster/ # ws proxy - forwards to web socket server ProxyPass /ws/ "ws://10.158.250.137:7778"  

Que fait la configuration ci-dessus:

  • Visitez le proxy http://10.158.250.99 il affichera la page Apache par défaut.
  • Visitez le proxy http://10.158.250.99/test/ il transmettra la requête HTTP à http://10.158.250.137:7779 .
  • Visitez le proxy http://10.158.250.99/ws et il établira une connexion websocket à ws://10.158.250.137:7778 et ws://10.158.250.137:7778 .

Donc, pour mon application im utilisant phptty comme elle utilise HTTP et WS, son frontend utilise xterm.js qui se connecte à https//10.158.250.99/ws websocket pour donner un tty dans le navigateur.

Voici un écran de tout cela qui fonctionne, en utilisant mon application électronique LXDui pour tout contrôler.

entrer la description de l'image ici

Donc, vérifiez vos parameters par rapport à ce que j’ai essayé et voyez si c’est différent, c’est toujours bon de faire des essais pour voir comment les choses fonctionnent avant d’essayer de les appliquer à votre idée.

J’espère que cela aide.

Je pense que votre problème de retard pour détecter que le client a fermé la page provient de la configuration par défaut de votre kernel tcp keepalive de votre noeud apache proxy. Je pense que dans votre système, si vous vérifiez la valeur de sys.net.ipv4.tcp_keepalive_time, vous pouvez avoir la valeur 60 qui devrait être les 60 secondes attendues avant que le premier paquet keepalive soit envoyé pour détecter si le client a fermé la connexion. D’après les détails de votre problème, mod_proxy semble avoir un problème car il semble ne pas transférer le paquet RST que vous gérez correctement sans le module mod_proxy. Sans résoudre ce problème de paquet RST sur mod_proxy, vous pouvez seulement réduire le délai en diminuant le paramètre tcp_keepalive_time par exemple à 5, attendre jusqu’à 5 secondes avant de commencer à vérifier si la connexion TCP est fermée. Vérifiez également le nombre de parameters de sondes keepalive ayant échoué avant de déclarer que la connexion a été fermée, cela pourrait également avoir un impact sur le délai total. C’est le paramètre tcp_keepalive_probes.