Determina se la chiamata ajax non è riuscita a causa di una risposta non sicura o della connessione rifiutata


115

Ho fatto molte ricerche e non sono riuscito a trovare un modo per gestirlo. Sto cercando di eseguire una chiamata jQuery ajax da un server https a un server https localizzato che esegue jetty con un certificato autofirmato personalizzato. Il mio problema è che non riesco a determinare se la risposta è una connessione rifiutata o una risposta non sicura (a causa della mancanza dell'accettazione del certificato). C'è un modo per determinare la differenza tra i due scenari? Le responseTexte statusCodesono sempre le stesse in entrambi i casi, anche se nella console cromata vedo una differenza:

net::ERR_INSECURE_RESPONSE
net::ERR_CONNECTION_REFUSED

responseTextè sempre "" ed statusCodeè sempre "0" per entrambi i casi.

La mia domanda è: come posso determinare se una chiamata jQuery ajax non è riuscita a causa ERR_INSECURE_RESPONSEo a causa di ERR_CONNECTION_REFUSED?

Una volta che il certificato è stato accettato, tutto funziona correttamente, ma voglio sapere se il server localhost è spento o è attivo e funzionante ma il certificato non è stato ancora accettato.

$.ajax({
    type: 'GET',
    url: "https://localhost/custom/server/",
    dataType: "json",
    async: true,
    success: function (response) {
        //do something
    },
    error: function (xhr, textStatus, errorThrown) {
        console.log(xhr, textStatus, errorThrown); //always the same for refused and insecure responses.
    }
});

inserisci qui la descrizione dell'immagine

Anche eseguendo manualmente la richiesta ottengo lo stesso risultato:

var request = new XMLHttpRequest();
request.open('GET', "https://localhost/custom/server/", true);
request.onload = function () {
    console.log(request.responseText);
};
request.onerror = function () {
    console.log(request.responseText);
};
request.send();

6
posterò il mio codice, ma non è un errore di codice javascript. Si prega di leggere attentamente la mia domanda.
taxicala

1
Gli altri due argomenti di callback di errore forniscono ulteriori informazioni? function (xhr, status, msg) {...Dubito che lo faranno, ma vale la pena provare.
Kevin B

2
No. Da un server a un altro server. Il jetty server (in esecuzione in locahost) ha impostato correttamente le intestazioni CORS perché una volta accettato il certificato tutto funziona come previsto. Voglio determinare se il certificato deve essere accettato o il molo è inattivo.
taxicala

5
È del tutto possibile che la mancanza di informazioni dal browser sia del tutto intenzionale. Semplicemente un "errore" fornirebbe una certa quantità di informazioni a un hacker proposto, ad es. "Questo sistema ha qualcosa in ascolto sulla porta, ecc."
Katana314

1
non è qualcosa che voglio, è qualcosa di cui ho bisogno in base al design e alla natura di ciò che sto sviluppando. lato server non è un'opzione.
taxicala

Risposte:


67

Non c'è modo di differenziarlo dai browser Web più recenti.

Specifica W3C:

I passaggi seguenti descrivono cosa devono fare i programmi utente per una semplice richiesta cross-origin :

Applica la procedura di creazione di una richiesta e osserva le regole di richiesta di seguito durante la richiesta.

Se il flag di reindirizzamento manuale non è impostato e la risposta ha un codice di stato HTTP 301, 302, 303, 307 o 308, applicare i passaggi di reindirizzamento.

Se l'utente finale annulla la richiesta, applicare i passaggi di interruzione.

In caso di errore di rete In caso di errori DNS, errore di negoziazione TLS o altri tipi di errori di rete, applicare i passaggi dell'errore di rete . Non richiedere alcun tipo di interazione con l'utente finale.

Nota: questo non include le risposte HTTP che indicano qualche tipo di errore, come il codice di stato HTTP 410.

In caso contrario, eseguire un controllo della condivisione delle risorse. Se restituisce un errore, applicare i passaggi di errore di rete. Altrimenti, se restituisce pass, termina questo algoritmo e imposta lo stato della richiesta cross-origin su successo. Non terminare effettivamente la richiesta.

Come puoi leggere, gli errori di rete non includono la risposta HTTP che include errori, ecco perché otterrai sempre 0 come codice di stato e "" come errore.

fonte


Nota : i seguenti esempi sono stati realizzati utilizzando Google Chrome versione 43.0.2357.130 e su un ambiente che ho creato per emulare uno OP. Il codice per impostarlo è in fondo alla risposta.


Ho pensato che un approccio per aggirare questo sarebbe fare una richiesta secondaria su HTTP invece di HTTPS come Questa risposta, ma ho ricordato che non è possibile a causa del fatto che le versioni più recenti dei browser bloccano il contenuto misto.

Ciò significa che il browser Web non consentirà una richiesta su HTTP se si utilizza HTTPS e viceversa.

È stato così da alcuni anni fa, ma le versioni precedenti del browser Web come Mozilla Firefox sotto le versioni 23 lo consentono.

Prove a riguardo:

Effettuare una richiesta HTTP da HTTPS utilizzando la console Web Broser

var request = new XMLHttpRequest();
request.open('GET', "http://localhost:8001", true);
request.onload = function () {
    console.log(request.responseText);
};
request.onerror = function () {
    console.log(request.responseText);
};
request.send();

comporterà il seguente errore:

Contenuto misto: la pagina in " https: // localhost: 8000 / " è stata caricata su HTTPS, ma ha richiesto un endpoint XMLHttpRequest non sicuro " http: // localhost: 8001 / ". Questa richiesta è stata bloccata; il contenuto deve essere offerto tramite HTTPS.

Lo stesso errore apparirà nella console del browser se provi a farlo in altri modi come l'aggiunta di un Iframe.

<iframe src="http://localhost:8001"></iframe>

Anche l'utilizzo della connessione Socket è stato pubblicato come risposta , ero abbastanza sicuro che il risultato sarebbe stato lo stesso / simile ma ho provato.

Il tentativo di aprire una connessione socket dal browser Web utilizzando HTTPS a un endpoint socket non protetto finirà con errori di contenuto misto.

new WebSocket("ws://localhost:8001", "protocolOne");

1) Contenuto misto: la pagina su " https: // localhost: 8000 / " è stata caricata su HTTPS, ma ha tentato di connettersi all'endpoint WebSocket non sicuro "ws: // localhost: 8001 /". Questa richiesta è stata bloccata; questo endpoint deve essere disponibile su WSS.

2) DOMException non rilevata: impossibile costruire "WebSocket": una connessione WebSocket non sicura potrebbe non essere avviata da una pagina caricata su HTTPS.

Quindi ho provato a connettermi anche a un endpoint wss vedere Se potessi leggere alcune informazioni sugli errori di connessione di rete:

var exampleSocket = new WebSocket("wss://localhost:8001", "protocolOne");
exampleSocket.onerror = function(e) {
    console.log(e);
}

L'esecuzione dello snippet sopra con il server disattivato produce:

Connessione WebSocket a 'wss: // localhost: 8001 /' non riuscita: errore nella creazione della connessione: net :: ERR_CONNECTION_REFUSED

Esecuzione dello snippet precedente con il server attivato

Connessione WebSocket a 'wss: // localhost: 8001 /' non riuscita: l'handshake di apertura di WebSocket è stato annullato

Ma ancora una volta, l'errore che la "funzione onerror" restituisce alla console non ha alcun suggerimento per differenziare un errore dall'altro.


L'uso di un proxy come suggerisce questa risposta potrebbe funzionare, ma solo se il server "target" ha accesso pubblico.

Questo non era il caso qui, quindi provare a implementare un proxy in questo scenario ci porterà allo stesso problema.

Codice per creare il server HTTPS Node.js :

Ho creato due server HTTPS Nodejs, che utilizzano certificati autofirmati:

targetServer.js:

var https = require('https');
var fs = require('fs');

var options = {
    key: fs.readFileSync('./certs2/key.pem'),
    cert: fs.readFileSync('./certs2/key-cert.pem')
};

https.createServer(options, function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    res.writeHead(200);
    res.end("hello world\n");
}).listen(8001);

applicationServer.js:

var https = require('https');
var fs = require('fs');

var options = {
    key: fs.readFileSync('./certs/key.pem'),
    cert: fs.readFileSync('./certs/key-cert.pem')
};

https.createServer(options, function (req, res) {
    res.writeHead(200);
    res.end("hello world\n");
}).listen(8000);

Per farlo funzionare è necessario che Nodejs sia installato, è necessario generare certificati separati per ogni server e archiviarli nelle cartelle certs e certs2 di conseguenza.

Per eseguirlo basta eseguire node applicationServer.jse node targetServer.jsin un terminale (esempio ubuntu).


6
Questa è la risposta più completa finora. Mostra che hai effettivamente svolto un lavoro di ricerca e test. Vedo che hai provato ogni modo possibile per eseguire questo e tutti gli scenari sono davvero ben spiegati. Hai anche fornito un codice di esempio per configurare rapidamente un ambiente di test! Davvero un bel lavoro! Grazie per l'intuizione!
taxicala

Ottima risposta! Tuttavia, vorrei sottolineare che i certificati autofirmati creerebbero comunque errori da un browser se provengono da server separati. @ecarrizo
FabricioG

Se stiamo parlando di un errore di rete, probabilmente sei in grado di vedere gli errori nella console, manualmente. ma non puoi accedervi dal codice in un ambiente sicuro.
ecarrizo

32

A partire da ora: non c'è modo di differenziare questo evento tra i browser. Poiché i browser non forniscono un evento a cui gli sviluppatori possono accedere. (Luglio 2015)

Questa risposta cerca semplicemente di fornire idee per una soluzione potenziale, anche se complicata e incompleta.


Disclaimer: questa risposta è incompleta in quanto non risolve completamente i problemi di OP (a causa delle politiche cross-origin). Tuttavia l'idea stessa ha qualche merito che viene ulteriormente ampliato da: @artur grzesiak qui , usando un proxy e ajax.


Dopo un bel po 'di ricerche io stesso, non sembra esserci alcuna forma di controllo degli errori per la differenza tra connessione rifiutata e una risposta non sicura, almeno per quanto riguarda javascript che fornisce una risposta per la differenza tra i due.

Il consenso generale della mia ricerca è che i certificati SSL sono gestiti dal browser, quindi fino a quando un certificato autofirmato non viene accettato dall'utente, il browser blocca tutte le richieste, comprese quelle per un codice di stato. Il browser potrebbe (se codificato per) restituire il proprio codice di stato per una risposta insicura, ma questo non aiuta davvero nulla, e anche in questo caso, avresti problemi con la compatibilità del browser (chrome / firefox / IE con standard diversi. .. di nuovo)

Poiché la tua domanda originale era per controllare lo stato del tuo server tra essere attivo e avere un certificato non accettato, non potresti fare una richiesta HTTP standard in questo modo?

isUp = false;
isAccepted = false;

var isUpRequest = new XMLHttpRequest();
isUpRequest.open('GET', "http://localhost/custom/server/", true); //note non-ssl port
isUpRequest.onload = function() {
    isUp = true;
    var isAcceptedRequest = new XMLHttpRequest();
    isAcceptedRequest.open('GET', "https://localhost/custom/server/", true); //note ssl port
    isAcceptedRequest.onload = function() {
        console.log("Server is up and certificate accepted");
        isAccepted = true;
    }
    isAcceptedRequest.onerror = function() {
        console.log("Server is up and certificate is not accepted");
    }
    isAcceptedRequest.send();
};
isUpRequest.onerror = function() {
    console.log("Server is down");
};
isUpRequest.send();

Certo, ciò richiede una richiesta aggiuntiva per verificare la connettività del server, ma dovrebbe portare a termine il lavoro tramite il processo di eliminazione. Tuttavia, mi sento ancora hacky e non sono un grande fan della richiesta raddoppiata.


7
Ho letto l'intera risposta e non credo che funzionerà. Entrambi i miei server eseguono HTTPS, il che significa che nel momento in cui lancio una richiesta a un server HTTP, il browser la annullerà immediatamente, perché non è possibile effettuare richieste ajax da un server HTTPS a un server HTTP
taxicala

11

La risposta di @ Schultzie è abbastanza vicina, ma chiaramente http, in generale, non funzionerà httpsnell'ambiente del browser.

Quello che puoi fare però è utilizzare un server intermedio (proxy) per effettuare la richiesta per tuo conto. Il proxy dovrebbe consentire di inoltrare la httprichiesta httpsdall'origine o di caricare il contenuto da origini autofirmate .

Avere il tuo server con il certificato appropriato è probabilmente un eccessivo nel tuo caso, poiché potresti usare questa impostazione al posto della macchina con certificato auto-firmato, ma ci sono molti servizi proxy aperti anonimi là fuori.

Quindi due approcci che mi vengono in mente sono:

  1. richiesta ajax : in tal caso il proxy deve utilizzare le impostazioni CORS appropriate
  2. uso di un iframe - carichi il tuo script (probabilmente racchiuso in html) all'interno di un iframe tramite il proxy. Una volta caricato lo script, invia un messaggio al suo .parentWindow. Se la tua finestra ha ricevuto un messaggio puoi essere certo che il server è in esecuzione (o più precisamente era in esecuzione una frazione di secondo prima).

Se sei interessato solo al tuo ambiente locale puoi provare a eseguire chrome con --disable-web-securityflag.


Un altro suggerimento: hai provato a caricare un'immagine in modo programmatico per scoprire se sono presenti più informazioni?


1

Controlla jQuery.ajaxError () Riferimento preso da: jQuery AJAX Error Handling (HTTP Status Codes) Rileva gli errori Ajax globali che puoi gestire in diversi modi su HTTP o HTTPS:

if (jqXHR.status == 501) {
//insecure response
} else if (jqXHR.status == 102) {
//connection refused
}

Questo è lo stesso di fare l'ajax nel modo in cui lo sto facendo, ma centralizzando le risposte agli errori in un unico posto.
taxicala

1
Il punto è che il certificato non viene caricato quando viene effettuata la chiamata ajax poiché sembra essere il problema. qualsiasi modo gl con la ricerca di risposte :)
Varshaan

2
No, il problema è che voglio determinare la differenza tra dover accettare il certificato e sapere se il server è spento.
taxicala

1
Sia quando la connessione viene rifiutata e il certificato non è attendibile, ottengo lo stesso jqXHR.status = 0 (e jqXHR.readyState = 0), non qXHR.status == 102 o 501 come descritto in questa risposta.
anre

1

Sfortunatamente l'API XHR del browser attuale non fornisce un'indicazione esplicita per quando il browser si rifiuta di connettersi a causa di una "risposta non sicura", e anche quando non si fida del certificato HTTP / SSL del sito web.

Ma ci sono modi per aggirare questo problema.

Una soluzione che ho escogitato per determinare quando il browser non si fida del certificato HTTP / SSL, è rilevare prima se si è verificato un errore XHR (utilizzando error()ad esempio il callback jQuery ), quindi verificare se la chiamata XHR è a un 'https : // 'URL, quindi controlla se XHR readyStateè 0, il che significa che la connessione XHR non è stata nemmeno aperta (che è ciò che accade quando al browser non piace il certificato).

Ecco il codice dove lo faccio: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js#


1

Non penso che al momento ci sia un modo per rilevare questi messaggi di errore, ma un trucco che puoi fare è usare un server come nginx davanti al tuo server delle applicazioni, in modo che se il server delle applicazioni è inattivo otterrai un cattivo errore gateway da nginx con 502codice di stato che puoi rilevare in JS. In caso contrario, se il certificato non è valido riceverai comunque lo stesso errore generico con statusCode = 0.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.