Timeout de conectare WebSocket (Programare, Javascript, Websocket)

Oleg a intrebat.
a intrebat.

Încerc să implementez un wrapper websocket failsafe. Și problema pe care o am este să mă ocup de erorile de timeout. Logica ar trebui să fie: dacă socket-ul nu este deschis în timpul $timeoutInMilisecunde – trebuie să fie închis și redeschis de $N ori.

Scriu ceva de genul acesta.

var maxReconects = 0;
var ws = new WebSocket(url);
var onCloseHandler = function() {
    if ( maxReconects < N ) {
        maxReconects++;
        // construct new Websocket 
        ....
    }
};
ws.onclose = onCloseHandler;
var timeout = setTimeout(function() {
                console.log("Socket connection timeout",ws.readyState);
                timedOut = true;
                ws.close();  <--- ws.readyState is 0 here 
                timedOut = false;
},timeoutInMiliseconds); 

Dar problema este gestionarea corectă a site-urilor web cu timeout – dacă încerc să închid un socket neconectat, primesc un avertisment în chrome :

„Conexiunea WebSocket la ‘ws://127.0.0.1:9010/timeout’ a eșuat: WebSocket este închis înainte ca conexiunea să fie stabilită.”

Și nu am nici o idee cum să o evit – interfața ws nu are o funcție de anulare .

Cealaltă abordare pe care am încercat-o este să nu închid socket-ul la timeout dacă nu este conectat, ci doar să-l marchez ca nefolosit și să-l închid dacă primește mai mult de un readyState – dar poate produce posibile scurgeri de informații, și este prea complicat pentru o sarcină atât de simplă.

2 răspunsuri
klues

Am scris următorul cod pentru deschiderea unui websocket failsafe cu timeout și reintrări, , vezi comentariile din cod pentru mai multe detalii.

Utilizare – deschiderea unui websocket cu 5000ms timeout și 10 reintrări (maxim):

initWebsocket('ws:\localhost:8090', null, 5000, 10).then(function(socket){
    console.log('socket initialized!');
    //do something with socket...

    //if you want to use the socket later again and assure that it is still open:
    initWebsocket('ws:\localhost:8090', socket, 5000, 10).then(function(socket){
        //if socket is still open, you are using the same "socket" object here
        //if socket was closed, you are using a new opened "socket" object
    }

}, function(){
    console.log('init of socket failed!');
});

Metoda initWebsocket() definită undeva într-o bibliotecă sau similar:

/**
 * inits a websocket by a given url, returned promise resolves with initialized websocket, rejects after failure/timeout.
 *
 * @param url the websocket url to init
 * @param existingWebsocket if passed and this passed websocket is already open, this existingWebsocket is resolved, no additional websocket is opened
 * @param timeoutMs the timeout in milliseconds for opening the websocket
 * @param numberOfRetries the number of times initializing the socket should be retried, if not specified or 0, no retries are made
 *        and a failure/timeout causes rejection of the returned promise
 * @return {Promise}
 */
function initWebsocket(url, existingWebsocket, timeoutMs, numberOfRetries) {
    timeoutMs = timeoutMs ? timeoutMs : 1500;
    numberOfRetries = numberOfRetries ? numberOfRetries : 0;
    var hasReturned = false;
    var promise = new Promise((resolve, reject) => {
        setTimeout(function () {
            if(!hasReturned) {
                console.info('opening websocket timed out: ' + url);
                rejectInternal();
            }
        }, timeoutMs);
        if (!existingWebsocket || existingWebsocket.readyState != existingWebsocket.OPEN) {
            if (existingWebsocket) {
                existingWebsocket.close();
            }
            var websocket = new WebSocket(url);
            websocket.onopen = function () {
                if(hasReturned) {
                    websocket.close();
                } else {
                    console.info('websocket to opened! url: ' + url);
                    resolve(websocket);
                }
            };
            websocket.onclose = function () {
                console.info('websocket closed! url: ' + url);
                rejectInternal();
            };
            websocket.onerror = function () {
                console.info('websocket error! url: ' + url);
                rejectInternal();
            };
        } else {
            resolve(existingWebsocket);
        }

        function rejectInternal() {
            if(numberOfRetries <= 0) {
                reject();
            } else if(!hasReturned) {
                hasReturned = true;
                console.info('retrying connection to websocket! url: ' + url + ', remaining retries: ' + (numberOfRetries-1));
                initWebsocket(url, null, timeoutMs, numberOfRetries-1).then(resolve, reject);
            }
        }
    });
    promise.then(function () {hasReturned = true;}, function () {hasReturned = true;});
    return promise;
};

o soluție mai bună ar fi încapsularea funcționalității într-o clasă proprie FailsafeWebsocket sau ceva de genul acesta. Oricum, această soluție este suficientă în proiectul meu – poate că ajută și pe altcineva.

Comentarii

  • Unde se găsește documentația în acest sens? Documentația actuală a mozilla spune url, […protocoale] developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket nu timeout sau reintrări –  > Por quantumpotato.
  • metoda initWebsocket este o metodă proprie care utilizează API WebSocket cu new Websocket(url). Documentația pentru metoda initWebsocket este comentariul de deasupra metodei din această postare. –  > Por klues.
  • deci acel timeout de 5000 ms, care va închide conexiunea după 5 secunde chiar dacă este deschisă? –  > Por quantumpotato.
  • nu, timeout este pentru fiecare încercare. Deci, apelarea initWebsocket() cu timeout = 5000 și numberOfRetries = 5 înseamnă că se încearcă deschiderea unui websocket și se așteaptă până la maxim. 5 secunde și dacă deschiderea websocket-ului nu reușește în această perioadă de timp, încercarea este anulată și se începe o nouă încercare. Acest lucru se repetă până la max. numberOfRetries ori. În cazul în care deschiderea websocket-ului reușește oriunde în acest proces, promisiunea se rezolvă cu websocket-ul deschis și întregul proces de reluare a încercării este oprit. –  > Por klues.
amarpatel

timeout este atribuită variabilei setTimeout(... care este invocată lexical. În momentul în care codul dvs. ajunge la var onCloseHander = ... dumneavoastră timeout funcția a fost deja invocată.

O soluție în acest caz este de a crea o funcție makeTimeout funcție:

var makeTimeout = function (timeoutInMiliseconds) {
    return window.setTimeout(function() {
        // do stuff
    }, timeoutInMiliseconds)
};

var timeoutId = makeTimeout(100) va invoca funcția setTimeout și va seta funcția timeoutId ca valoare a ID-ului de timeout. Dacă aveți nevoie să anulați acest timeout, acest lucru poate fi făcut prin invocarea funcției window.clearTimeout(timeoutId).