web-dev-qa-db-fra.com

Déterminer si un appel ajax a échoué en raison d'une réponse non sécurisée ou d'une connexion refusée

J'ai fait beaucoup de recherches et je n'ai pas trouvé le moyen de gérer cela. J'essaie d'effectuer un appel ajax jQuery à partir d'un serveur https vers un serveur https locahost exécutant une jetée avec un certificat auto-signé personnalisé. Mon problème est que je ne peux pas déterminer si la réponse est une connexion refusée ou une réponse non sécurisée (en raison de l'absence d'acceptation du certificat). Existe-t-il un moyen de déterminer la différence entre les deux scénarios? Les responseText et statusCode sont toujours les mêmes dans les deux cas, même si dans la console chrome), je peux voir une différence:

net::ERR_INSECURE_RESPONSE
net::ERR_CONNECTION_REFUSED

responseText est toujours "" et statusCode est toujours "0" dans les deux cas.

Ma question est, comment puis-je déterminer si un appel jQuery ajax a échoué en raison de ERR_INSECURE_RESPONSE ou en raison de ERR_CONNECTION_REFUSED?

Une fois le certificat accepté, tout fonctionne correctement, mais je veux savoir si le serveur localhost est arrêté ou s'il est opérationnel, mais le certificat n'a pas encore été accepté.

$.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.
    }
});

enter image description here

Même en effectuant manuellement la demande, j'obtiens le même résultat:

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();
114
taxicala

Il n'y a aucun moyen de le différencier des navigateurs Web les plus récents.

Spécification W3C:

Les étapes ci-dessous décrivent ce que les agents utilisateurs doivent faire pour une requête simple d'origine croisée :

Appliquez les étapes de création de requête et observez les règles de requête ci-dessous lors de la requête.

Si l'indicateur de redirection manuelle n'est pas défini et que la réponse a un code d'état HTTP de 301, 302, 303, 307 ou 308 Appliquez les étapes de redirection.

Si l'utilisateur final annule la demande Appliquez les étapes d'abandon.

En cas d'erreur réseau En cas d'erreur DNS, d'échec de la négociation TLS ou d'un autre type d'erreur réseau, appliquez les étapes erreur réseau). . Ne demandez aucun type d'interaction avec l'utilisateur final.

Remarque: ceci n'inclut pas les réponses HTTP qui indiquent un type d'erreur, tel que le code d'état HTTP 410.

Sinon Effectuez une vérification du partage des ressources. S'il échoue, appliquez les étapes d'erreur réseau. Sinon, s'il retourne pass, mettez fin à cet algorithme et définissez le statut de la demande d'origine multiple sur succès. Ne terminez pas réellement la demande.

Comme vous pouvez le lire, les erreurs réseau n'incluent pas les réponses HTTP, c'est pourquoi vous obtiendrez toujours 0 comme code d'état et "" comme erreur.

Source


Remarque : Les exemples suivants ont été réalisés avec Google Chrome version 43.0.2357.130 et dans un environnement que j'ai créé). pour émuler OP 1. Le code pour le configurer est au bas de la réponse.


Je pensais qu'une approche pour contourner ce problème serait de faire une requête secondaire via HTTP au lieu de HTTPS sous la forme cette réponse mais je me suis rappelé que cela n’est pas possible car les versions plus récentes des navigateurs bloquent le contenu mixte.

Cela signifie que le navigateur Web n'autorisera pas de requête via HTTP si vous utilisez HTTPS et inversement.

C’est ce qui se passe depuis quelques années, mais les anciennes versions du navigateur Web telles que Mozilla Firefox, en dessous des versions 23, le permettent.

Preuve à ce sujet:

Effectuer une requête HTTP à partir de HTTPS sur 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();

entraînera l'erreur suivante:

Contenu mixte: La page ' https: // localhost: 8000 / ' a été chargée via HTTPS, mais elle a demandé un noeud final XMLHttpRequest non sécurisé ' http: // localhost: 8001 / '. Cette demande a été bloquée. le contenu doit être servi sur HTTPS.

La même erreur apparaîtra dans la console du navigateur si vous essayez de le faire autrement que si vous ajoutiez un Iframe.

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

Utiliser la connexion Socket était aussi Posté comme une réponse , j'étais à peu près sûr que le résultat serait le même/similaire, mais je l'ai essayé.

Si vous essayez d'ouvrir une connexion de socket à partir de Web Broswer à l'aide du protocole HTTPS vers un point de terminaison de socket non sécurisé, les erreurs de contenu sont différentes.

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

1) Contenu mixte: La page située sous ' https: // localhost: 8000 / ' a été chargée via HTTPS, mais a tenté de se connecter au point de terminaison non sécurisé de WebSocket 'ws: // localhost: 8001 /'. Cette demande a été bloquée. ce noeud final doit être disponible sur WSS.

2) Exception DOMException non réussie: impossible de construire 'WebSocket': une connexion WebSocket non sécurisée ne peut pas être initiée à partir d'une page chargée via HTTPS.

Ensuite, j’ai également essayé de me connecter à un point de terminaison wss, voir Si je pouvais lire des informations sur les erreurs de connexion réseau:

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

L'exécution du fragment ci-dessus avec le serveur désactivé entraîne les résultats suivants:

La connexion WebSocket à 'wss: // localhost: 8001 /' a échoué: erreur lors de l'établissement de la connexion: net :: ERR_CONNECTION_REFUSED

Exécution du fragment ci-dessus avec le serveur activé

La connexion WebSocket à 'wss: // localhost: 8001 /' a échoué: l'établissement de la liaison d'ouverture de WebSocket a été annulé.

Mais encore une fois, l’erreur que la "fonction onerror" transmise à la console n’a aucune astuce pour différencier une erreur de l’autre.


L'utilisation d'un proxy en tant que cette réponse suggère pourrait fonctionner, mais uniquement si le serveur "cible" dispose d'un accès public.

Ce n'était pas le cas ici, donc essayer d'implémenter un proxy dans ce scénario nous mènera au même problème.

Code pour créer le serveur HTTPS Node.js :

J'ai créé deux serveurs HTTPS Nodejs, qui utilisent des certificats auto-signés:

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);

Pour que cela fonctionne, vous devez installer Nodejs, créer des certificats séparés pour chaque serveur et les stocker dans les dossiers certs et certs2 en conséquence.

Pour l'exécuter, exécutez simplement node applicationServer.js et node targetServer.js dans un terminal (exemple ubuntu).

66
ecarrizo

Pour l'instant: Il n'y a aucun moyen de différencier cet événement entre les souffleurs. Les navigateurs ne fournissant pas d’événement aux développeurs. (juillet 2015)

Cette réponse cherche simplement à donner des idées pour une solution potentielle, albiet hacky et incomplète.


Déni de responsabilité: cette réponse est incomplète car elle ne le fait pas complètement résolvez les problèmes de OP (en raison des règles relatives à l'origine croisée). Cependant, l'idée elle-même a un mérite qui est encore développé par: @ artur grzesiak here, en utilisant un proxy et ajax.


Après de nombreuses recherches, il ne semble exister aucune forme d'erreur permettant de vérifier la différence entre une connexion refusée et une réponse peu sûre, du moins en ce qui concerne le javascript fournissant une réponse à la différence entre les deux.

Le consensus général de mes recherches étant que les certificats SSL sont gérés par le navigateur. Par conséquent, tant que l'utilisateur n'accepte pas un certificat auto-signé, le navigateur verrouille toutes les demandes, y compris celles relatives à un code d'état. Le navigateur peut (s'il est codé sur) renvoyer son propre code d'état pour une réponse non sécurisée, mais cela n'aide en rien, et même dans ce cas, vous rencontreriez des problèmes de compatibilité de navigateur (chrome/firefox/IE ayant des normes différentes. .. encore une fois)

Étant donné que votre question initiale visait à vérifier si le statut de votre serveur était actif par rapport à un certificat non accepté, ne pourriez-vous pas envoyer une requête HTTP standard de la sorte?

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();

Cela nécessite une requête supplémentaire pour vérifier la connectivité du serveur, mais le travail d'élimination doit être effectué. Je me sens toujours hacky, et je ne suis pas un grand fan de la demande doublée.

31
acupajoe

La réponse de @ Schultzie est assez proche, mais il est clair que http - en général - ne fonctionnera pas à partir de https dans l'environnement du navigateur.

Toutefois, vous pouvez utiliser un serveur intermédiaire (proxy) pour effectuer la demande en votre nom. Le proxy doit permettre soit de transférer http demande de https origine soit de charger du contenu d’origines auto-signées .

Avoir votre propre serveur avec le bon certificat est probablement une overkill dans votre cas - comme vous pouvez utiliser ce paramètre au lieu de la machine avec un certificat auto-signé - mais il y a beaucoup de anonymes ouverts services de proxy .

Donc, deux approches me viennent à l’esprit:

  1. demande ajax - dans ce cas, le proxy doit utiliser les paramètres CORS appropriés
  2. tilisation d'un iframe - vous chargez votre script (probablement encapsulé au format HTML) dans un iframe via le proxy. Une fois le script chargé, il envoie un message à son .parentWindow. Si votre fenêtre a reçu un message, vous pouvez être sûr que le serveur est en cours d'exécution (ou plus précisément fonctionnait une fraction de seconde auparavant).

Si vous n’êtes intéressé que par votre environnement local, vous pouvez essayer de lancer chrome avec --disable-web-security drapeau.


Autre suggestion: avez-vous essayé de charger une image par programme pour savoir si plus d’informations y sont présentes?

11
artur grzesiak

Je ne pense pas qu’il existe actuellement un moyen de détecter ces messages d’erreur, mais vous pouvez le faire en utilisant un serveur tel que nginx devant votre serveur d’applications. Ainsi, si le serveur d’applications est en panne, vous obtiendrez un mauvais erreur de passerelle de nginx avec 502 code de statut que vous pouvez détecter dans JS. Sinon, si le certificat est invalide, vous obtenez toujours la même erreur générique avec statusCode = 0.

1
Gaafar

Départ jQuery.ajaxError () Référence prise à partir de: jQuery AJAX Traitement des erreurs (codes de statut HTTP) Il capture les erreurs Ajax globales que vous peut gérer de nombreuses manières via HTTP ou HTTPS:

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