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

115

J'ai fait beaucoup de recherches et je n'ai pas trouvé de moyen de gérer cela. J'essaye d'effectuer un appel jQuery ajax d'un serveur https à 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 du manque d'acceptation du certificat). Existe-t-il un moyen de déterminer la différence entre les deux scénarios? Les responseText, et statusCodesont 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

responseTextest toujours "" et statusCodeest toujours "0" dans les deux cas.

Ma question est la suivante: comment puis-je déterminer si un appel jQuery ajax a échoué en raison ERR_INSECURE_RESPONSEou en raison de ERR_CONNECTION_REFUSED?

Une fois que le certificat est accepté, tout fonctionne bien, 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.
    }
});

entrez la description de l'image ici

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();
taxicala
la source
6
Je publierai mon code, mais ce n'est pas une erreur de code javascript. Veuillez lire attentivement ma question.
taxicala
1
Les deux autres arguments de rappel d'erreur donnent-ils des informations supplémentaires? function (xhr, status, msg) {...Je doute qu'ils le feront, mais cela vaut la peine d'essayer.
Kevin B
2
Non. D'un serveur à un autre serveur. Le serveur de jetée (fonctionnant dans locahost) a correctement défini les en-têtes CORS car une fois que j'accepte le certificat, tout fonctionne comme prévu. Je veux déterminer si le certificat doit être accepté ou si la jetée est en panne.
taxicala
5
Il est tout à fait possible que le manque d'informations du navigateur soit entièrement intentionnel. Une simple "erreur" fournirait une certaine quantité d'informations à un hacker proposé, par exemple. "Ce système a quelque chose qui écoute sur le port etc."
Katana314
1
ce n'est pas quelque chose que je veux, c'est quelque chose dont j'ai besoin de par la conception et la nature de ce que je développe. côté serveur n'est pas une option.
taxicala

Réponses:

67

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 simple requête cross-origin :

Appliquez les étapes de demande et respectez les règles de demande ci-dessous lors de la demande.

Si l'indicateur de redirection manuelle n'est pas défini et que la réponse a un code d'état HTTP 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'erreurs DNS, d'échec de la négociation TLS ou de tout autre type d'erreurs réseau, appliquez les étapes d'erreur réseau . Ne demandez aucun type d'interaction avec l'utilisateur final.

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

Sinon, effectuez une vérification du partage des ressources. S'il renvoie un échec, appliquez les étapes d'erreur réseau. Sinon, s'il renvoie pass, arrêtez cet algorithme et définissez le statut de la requête cross-origin sur succès. Ne mettez pas réellement fin à la demande.

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

La source


Remarque : les exemples suivants ont été réalisés à l'aide de la version 43.0.2357.130 de Google Chrome et dans un environnement que j'ai créé pour émuler l'OP one. Le code de la configuration est au bas de la réponse.


Je pensais qu'une approche pour contourner ce problème serait de faire une requête secondaire sur HTTP au lieu de HTTPS comme réponse, mais je me suis souvenu que ce n'est pas possible car les nouvelles versions des navigateurs bloquent le contenu mixte.

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

C'est comme ça depuis quelques années, mais les anciennes versions du navigateur Web comme Mozilla Firefox en dessous des versions 23 le permettent.

Preuve à ce sujet:

Faire une requête HTTP depuis HTTPS en utilisant 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 a demandé un point de terminaison XMLHttpRequest non sécurisé " http: // localhost: 8001 / ". Cette demande a été bloquée; le contenu doit être diffusé via HTTPS.

La même erreur apparaîtra dans la console du navigateur si vous essayez de le faire d'une autre manière que l'ajout d'un Iframe.

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

L'utilisation de la connexion Socket a également été publiée en tant que réponse , j'étais à peu près sûr que le résultat sera le même / similaire, mais j'ai essayé.

Si vous essayez d'ouvrir une connexion de socket à partir de Web Broswer à l'aide de HTTPS vers un point de terminaison de socket non sécurisé, des erreurs de contenu mixte se termineront.

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

1) Contenu mixte: la page à l' adresse ' https: // localhost: 8000 / ' a été chargée via HTTPS, mais a tenté de se connecter au point de terminaison WebSocket non sécurisé 'ws: // localhost: 8001 /'. Cette demande a été bloquée; ce point de terminaison doit être disponible via WSS.

2) Exception DOMException non interceptée: échec de la construction de 'WebSocket': une connexion WebSocket non sécurisée peut ne pas être initiée à partir d'une page chargée via HTTPS.

Ensuite, j'ai essayé de me connecter à un point de terminaison wss aussi 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 de l'extrait de code ci-dessus avec le serveur désactivé entraîne:

Échec de la connexion WebSocket à 'wss: // localhost: 8001 /': erreur lors de l'établissement de la connexion: net :: ERR_CONNECTION_REFUSED

Exécution de l'extrait de code ci-dessus avec le serveur activé

Échec de la connexion WebSocket à 'wss: // localhost: 8001 /': la négociation d'ouverture de WebSocket a été annulée

Mais encore une fois, l'erreur que la "fonction onerror" sortie vers la console n'a pas d'astuce pour différencier une erreur de l'autre.


L'utilisation d'un proxy comme le suggère cette réponse pourrait fonctionner mais seulement si le serveur "cible" a un accès public.

Ce n'était pas le cas ici, donc essayer d'implémenter un proxy dans ce scénario nous conduira 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, vous devez générer des certificats séparés pour chaque serveur et les stocker dans les dossiers certs et certs2 en conséquence.

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

écarrizo
la source
6
C'est la réponse la plus complète à ce jour. Cela montre que vous avez effectivement effectué des recherches et des tests. Je vois que vous avez essayé toutes les manières possibles de réaliser cela et tous les scénarios sont vraiment bien expliqués. Vous avez également fourni un exemple de code pour configurer rapidement un environnement de test! Vraiment du bon travail! Merci pour la perspicacité!
taxicala
Excellente réponse! Cependant, je tiens à souligner que les certificats auto-signés créeraient toujours des erreurs à partir d'un navigateur s'ils proviennent de serveurs distincts. @ecarrizo
FabricioG
Si nous parlons d'une erreur réseau, vous êtes probablement en mesure de voir les erreurs dans la console, manuellement. mais vous n'y avez pas accès à partir du code dans un environnement sécurisé.
ecarrizo
32

À partir de maintenant: il n'y a aucun moyen de différencier cet événement entre les navigateurs. Comme les navigateurs ne fournissent pas d'événement auquel les développeurs peuvent accéder. (Juillet 2015)

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


Avertissement: cette réponse est incomplète car elle ne résout pas complètement les problèmes d'OP (en raison de politiques d'origine croisée). Cependant, l'idée elle-même a un certain mérite qui est encore développé par: @artur grzesiak ici , en utilisant un proxy et ajax.


Après pas mal de recherches moi-même, il ne semble y avoir aucune forme de vérification d'erreur pour la différence entre une connexion refusée et une réponse non sécurisée, du moins en ce qui concerne le javascript fournissant une réponse pour la différence entre les deux.

Le consensus général de mes recherches est que les certificats SSL sont gérés par le navigateur, donc jusqu'à ce qu'un certificat auto-signé soit accepté par l'utilisateur, le navigateur verrouille toutes les demandes, y compris celles d'un code d'état. Le navigateur pourrait (s'il est codé) renvoyer son propre code de statut pour une réponse non sécurisée, mais cela n'aide vraiment rien, et même dans ce cas, vous auriez des problèmes de compatibilité avec le navigateur (chrome / firefox / IE ayant des normes différentes. .. encore une fois)

Étant donné que votre question initiale était de vérifier l'état de votre serveur entre la mise en service et le fait d'avoir un certificat non accepté, ne pourriez-vous pas faire une requête HTTP standard comme ça?

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

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

acupofjose
la source
7
J'ai lu toute la réponse et je ne pense pas que cela fonctionnera. Mes deux serveurs exécutent HTTPS, ce qui signifie qu'au moment où je lance une requête vers un serveur HTTP, le navigateur l'annulera immédiatement, car vous ne pouvez pas faire de requêtes ajax d'un serveur HTTPS à un serveur HTTP
taxicala
11

La réponse de @ Schultzie est assez proche, mais clairement http- en général - ne fonctionnera pas httpsdans l'environnement du navigateur.

Ce que vous pouvez faire est d'utiliser un serveur intermédiaire (proxy) pour faire la demande en votre nom. Le proxy doit permettre soit de transférer la httpdemande depuis l' httpsorigine, soit de charger le contenu depuis les origines auto-signées .

Avoir votre propre serveur avec un certificat approprié est probablement excessif dans votre cas - car vous pouvez utiliser ce paramètre au lieu de la machine avec un certificat auto-signé - mais il existe de nombreux services proxy ouverts anonymes .

Donc, deux approches qui me viennent à l'esprit sont:

  1. requête ajax - dans ce cas, le proxy doit utiliser les paramètres CORS appropriés
  2. utilisation d'un iframe - vous chargez votre script (probablement enveloppé en 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 d'exécuter chrome avec --disable-web-securityindicateur.


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

artur grzesiak
la source
1

Consultez jQuery.ajaxError () Référence tirée de: jQuery AJAX Error Handling (HTTP Status Codes) Il détecte les erreurs Ajax globales que vous pouvez gérer de plusieurs manières via HTTP ou HTTPS:

if (jqXHR.status == 501) {
//insecure response
} else if (jqXHR.status == 102) {
//connection refused
}
Varshaan
la source
C'est la même chose que de faire l'ajax comme je le fais, mais de centraliser les réponses d'erreur en un seul endroit.
taxicala
1
Le fait est que le certificat n'est pas chargé lorsque l'appel ajax est effectué car cela semble être le problème. toutes les manières de trouver des réponses :)
Varshaan
2
Non, le problème est que je veux déterminer la différence entre avoir à accepter le certificat et savoir si le serveur est arrêté.
taxicala
1
Lorsque la connexion est refusée et que le certificat n'est pas approuvé, j'obtiens le même jqXHR.status = 0 (et jqXHR.readyState = 0), pas qXHR.status == 102 ou 501 comme décrit dans cette réponse.
anre
1

Malheureusement, l'API XHR du navigateur actuel ne fournit pas d'indication explicite lorsque le navigateur refuse de se connecter en raison d'une "réponse non sécurisée", et également lorsqu'il ne fait pas confiance au certificat HTTP / SSL du site Web.

Mais il existe des moyens de contourner ce problème.

Une solution que j'ai trouvée pour déterminer quand le navigateur ne fait pas confiance au certificat HTTP / SSL, consiste d'abord à détecter si une erreur XHR s'est produite (en utilisant le error()rappel jQuery par exemple), puis à vérifier si l'appel XHR est à un 'https : // 'URL, puis vérifiez si le XHR readyStateest 0, ce qui signifie que la connexion XHR n'a même pas été ouverte (ce qui se produit lorsque le navigateur n'aime pas le certificat).

Voici le code où je fais cela: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js

maratbn
la source
1

Je ne pense pas qu'il existe actuellement un moyen de détecter ces messages d'erreur, mais un hack que vous pouvez faire est d'utiliser un serveur comme nginx devant votre serveur d'applications, de sorte que si le serveur d'applications est en panne, vous obtiendrez un mauvais erreur de passerelle de nginx avec le 502code d'état que vous pouvez détecter dans JS. Sinon, si le certificat n'est pas valide, vous obtiendrez toujours la même erreur générique avec statusCode = 0.

gafi
la source