Determine si la llamada ajax falló debido a una respuesta insegura o una conexión rechazada

115

He estado investigando mucho y no pude encontrar una manera de manejar esto. Estoy tratando de realizar una llamada jQuery ajax desde un servidor https a un servidor https de locahost que ejecuta un embarcadero con un certificado autofirmado personalizado. Mi problema es que no puedo determinar si la respuesta es una conexión rechazada o una respuesta insegura (debido a la falta de aceptación del certificado). ¿Hay alguna forma de determinar la diferencia entre ambos escenarios? Los responseText, y statusCodesiempre son iguales en ambos casos, aunque en la consola de Chrome puedo ver una diferencia:

net::ERR_INSECURE_RESPONSE
net::ERR_CONNECTION_REFUSED

responseTextes siempre "" y statusCodesiempre es "0" para ambos casos.

Mi pregunta es, ¿cómo puedo determinar si una llamada ajax de jQuery falló debido a ERR_INSECURE_RESPONSEo debido a ERR_CONNECTION_REFUSED?

Una vez que se acepta el certificado, todo funciona bien, pero quiero saber si el servidor localhost está apagado o si está en funcionamiento, pero el certificado aún no ha sido aceptado.

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

ingrese la descripción de la imagen aquí

Incluso realizando manualmente la solicitud obtengo el mismo resultado:

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
fuente
6
Publicaré mi código, pero no es un error de código javascript. Lea atentamente mi pregunta.
Taxi:
1
¿Los otros dos argumentos de devolución de llamada de error ofrecen información adicional? function (xhr, status, msg) {...Dudo que lo hagan, pero vale la pena intentarlo.
Kevin B
2
No. De un servidor a otro servidor. El servidor del embarcadero (que se ejecuta en locahost) ha configurado correctamente los encabezados CORS porque una vez que acepto el certificado, todo funciona como se esperaba. Quiero determinar si el certificado debe ser aceptado o si el embarcadero está caído.
taxi:
5
Es muy posible que la falta de información del navegador sea completamente intencional. Simplemente un "error" proporcionaría cierta cantidad de información a un pirata informático propuesto, por ejemplo. "Este sistema tiene algo escuchando en el puerto, etc."
Katana314
1
no es algo que quiero, es algo que necesito por diseño y naturaleza de lo que estoy desarrollando. El lado del servidor no es una opción.
Taxi del

Respuestas:

67

No hay forma de diferenciarlo de los navegadores web más nuevos.

Especificación W3C:

Los pasos siguientes describen lo que deben hacer los agentes de usuario para una solicitud de origen cruzado simple :

Aplique los pasos de hacer una solicitud y observe las reglas de solicitud a continuación mientras realiza la solicitud.

Si el indicador de redireccionamiento manual no está configurado y la respuesta tiene un código de estado HTTP de 301, 302, 303, 307 o 308, aplique los pasos de redireccionamiento.

Si el usuario final cancela la solicitud, aplique los pasos de cancelación .

Si hay un error de red En caso de errores de DNS, falla de negociación de TLS u otro tipo de errores de red, aplique los pasos de error de red . No solicite ningún tipo de interacción con el usuario final.

Nota: Esto no incluye las respuestas HTTP que indican algún tipo de error, como el código de estado HTTP 410.

De lo contrario, realice una verificación de uso compartido de recursos. Si vuelve a fallar, aplique los pasos de error de red. De lo contrario, si devuelve aprobado, finalice este algoritmo y establezca el estado de la solicitud de origen cruzado en éxito. En realidad, no cancele la solicitud.

Como puede leer, los errores de red no incluyen la respuesta HTTP que incluye errores, es por eso que siempre obtendrá 0 como código de estado y "" como error.

Fuente


Nota : Los siguientes ejemplos se realizaron con la versión 43.0.2357.130 de Google Chrome y en un entorno que he creado para emular OP one. El código para configurarlo está al final de la respuesta.


Pensé que un enfoque para solucionar esto sería realizar una solicitud secundaria a través de HTTP en lugar de HTTPS como Esta respuesta, pero he recordado que no es posible debido a que las versiones más nuevas de los navegadores bloquean el contenido mixto.

Eso significa que el navegador web no permitirá una solicitud a través de HTTP si está utilizando HTTPS y viceversa.

Esto ha sido así desde hace unos años, pero las versiones anteriores del navegador web como Mozilla Firefox debajo de las versiones 23 lo permiten.

Evidencia al respecto:

Hacer una solicitud HTTP desde HTTPS usando la consola 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();

resultará en el siguiente error:

Contenido mixto: la página en ' https: // localhost: 8000 / ' se cargó a través de HTTPS, pero solicitó un extremo XMLHttpRequest inseguro ' http: // localhost: 8001 / '. Esta solicitud ha sido bloqueada; el contenido debe publicarse a través de HTTPS.

El mismo error aparecerá en la consola del navegador si intenta hacer esto de otras formas como agregar un Iframe.

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

El uso de la conexión Socket también se publicó como respuesta , estaba bastante seguro de que el resultado sería el mismo / similar, pero lo he intentado.

Intentar abrir una conexión de socket desde Web Broswer usando HTTPS a un extremo de socket no seguro terminará en errores de contenido mixto.

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

1) Contenido mixto: la página en ' https: // localhost: 8000 / ' se cargó a través de HTTPS, pero intentó conectarse al extremo inseguro de WebSocket 'ws: // localhost: 8001 /'. Esta solicitud ha sido bloqueada; este punto final debe estar disponible a través de WSS.

2) DOMException no detectada: no se pudo construir 'WebSocket': es posible que no se inicie una conexión WebSocket insegura desde una página cargada a través de HTTPS.

Luego intenté conectarme a un punto final wss también, consulte Si pudiera leer información sobre errores de conexión de red:

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

La ejecución del fragmento anterior con el servidor desactivado da como resultado:

La conexión de WebSocket a 'wss: // localhost: 8001 /' falló: Error en el establecimiento de la conexión: net :: ERR_CONNECTION_REFUSED

Ejecutando el fragmento de arriba con el servidor encendido

La conexión de WebSocket a 'wss: // localhost: 8001 /' falló: se canceló el protocolo de enlace de apertura de WebSocket

Pero nuevamente, el error que la salida de la "función onerror" a la consola no tiene ningún consejo para diferenciar un error del otro.


El uso de un proxy como sugiere esta respuesta podría funcionar, pero solo si el servidor "objetivo" tiene acceso público.

Este no fue el caso aquí, por lo que intentar implementar un proxy en este escenario nos llevará al mismo problema.

Código para crear el servidor HTTPS Node.js :

Creé dos servidores HTTPS de Nodejs, que usan certificados autofirmados:

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

Para que funcione, debe tener Nodejs instalado, debe generar certificados separados para cada servidor y almacenarlos en las carpetas certs y certs2 en consecuencia.

Para ejecutarlo, simplemente ejecute node applicationServer.jsy node targetServer.jsen una terminal (ejemplo de ubuntu).

ecarrizo
fuente
6
Esta es la respuesta más completa hasta ahora. Demuestra que realmente ha realizado algunos trabajos de investigación y pruebas. Veo que ha intentado todas las formas posibles de realizar esto y todos los escenarios están muy bien explicados. ¡También proporcionó un código de ejemplo para configurar rápidamente un entorno de prueba! ¡Muy buen trabajo! ¡Gracias por la información!
taxi
¡Excelente respuesta! Sin embargo, me gustaría señalar que los certificados autofirmados aún generarían errores en un navegador si son de servidores separados. @ecarrizo
FabricioG
Si estamos hablando de un error de red, probablemente puedas ver los errores en la consola, manualmente. pero no tiene acceso a él desde el código en un entorno seguro.
ecarrizo
32

A partir de ahora: no hay forma de diferenciar este evento entre navegadores. Como los navegadores no proporcionan un evento para que los desarrolladores accedan. (Julio de 2015)

Esta respuesta simplemente busca proporcionar ideas para una solución potencial, albiet hacky e incompleta.


Descargo de responsabilidad: esta respuesta está incompleta ya que no resuelve por completo los problemas de OP (debido a las políticas de origen cruzado). Sin embargo, la idea en sí tiene algún mérito que se amplía aún más con: @artur grzesiak aquí , usando un proxy y ajax.


Después de un poco de investigación, no parece haber ninguna forma de verificación de errores para la diferencia entre la conexión rechazada y una respuesta insegura, al menos en la medida en que javascript proporciona una respuesta para la diferencia entre los dos.

El consenso general de mi investigación es que los certificados SSL son manejados por el navegador, por lo que hasta que el usuario acepta un certificado autofirmado, el navegador bloquea todas las solicitudes, incluidas las de un código de estado. El navegador podría (si está codificado) enviar su propio código de estado para una respuesta insegura, pero eso realmente no ayuda en nada, e incluso entonces, tendría problemas con la compatibilidad del navegador (Chrome / Firefox / IE tiene diferentes estándares. .. Una vez más)

Dado que su pregunta original era verificar el estado de su servidor entre estar activo y tener un certificado no aceptado, ¿no podría hacer una solicitud HTTP estándar como esa?

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

Por supuesto, esto requiere una solicitud adicional para verificar la conectividad del servidor, pero debería hacer el trabajo mediante el proceso de eliminación. Sin embargo, todavía se siente hacky, y no soy un gran admirador de la solicitud duplicada.

acupofjose
fuente
7
He leído la respuesta completa y no creo que esto funcione. Mis dos servidores ejecutan HTTPS, lo que significa que en el momento en que envío una solicitud a un servidor HTTP, el navegador la cancelará de inmediato, porque no puede hacer solicitudes ajax desde un servidor HTTPS a un servidor HTTP
taxicala
11

La respuesta de @ Schultzie es bastante cercana, pero claramente http, en general, no funcionará httpsen el entorno del navegador.

Sin embargo, lo que puede hacer es utilizar un servidor intermedio (proxy) para realizar la solicitud en su nombre. El proxy debería permitir reenviar httpsolicitudes desde el httpsorigen o cargar contenido desde orígenes autofirmados .

Tener su propio servidor con el certificado adecuado es probablemente una exageración en su caso, ya que podría usar esta configuración en lugar de la máquina con certificado autofirmado, pero hay muchos servicios de proxy abiertos anónimos por ahí.

Entonces, dos enfoques que me vienen a la mente son:

  1. solicitud ajax : en tal caso, el proxy debe usar la configuración CORS adecuada
  2. uso de un iframe : carga su script (probablemente envuelto en html) dentro de un iframe a través del proxy. Una vez cargado el script, envía un mensaje a su .parentWindow. Si su ventana recibió un mensaje, puede estar seguro de que el servidor está funcionando (o, más precisamente, estaba funcionando una fracción de segundo antes).

Si solo está interesado en su entorno local, puede intentar ejecutar Chrome con --disable-web-securityflag.


Otra sugerencia: ¿intentó cargar una imagen mediante programación para averiguar si hay más información allí?

artur grzesiak
fuente
1

Echa un vistazo a jQuery.ajaxError () Referencia tomada de: jQuery AJAX Error Handling (códigos de estado HTTP) Detecta errores globales de Ajax que puedes manejar de varias formas a través de HTTP o HTTPS:

if (jqXHR.status == 501) {
//insecure response
} else if (jqXHR.status == 102) {
//connection refused
}
Varshaan
fuente
Esto es lo mismo que hacer el ajax de la forma en que lo estoy haciendo, pero centralizando las respuestas de error en un solo lugar.
taxi
1
El punto es que el certificado no se carga cuando se realiza la llamada ajax, ya que parece ser el problema. de cualquier manera gl con encontrar respuestas :)
Varshaan
2
No, el problema es que quiero determinar la diferencia entre tener que aceptar el certificado y saber si el servidor está apagado.
taxi
1
Tanto cuando se rechaza la conexión y el certificado no es de confianza, obtengo el mismo jqXHR.status = 0 (y jqXHR.readyState = 0), no qXHR.status == 102 o 501 como se describe en esta respuesta.
2017
1

Desafortunadamente, la API XHR del navegador actual no proporciona una indicación explícita de cuándo el navegador se niega a conectarse debido a una "respuesta insegura", y también cuando no confía en el certificado HTTP / SSL del sitio web.

Pero hay formas de solucionar este problema.

Una solución que se me ocurrió para determinar cuándo el navegador no confía en el certificado HTTP / SSL es detectar primero si se ha producido un error XHR (usando la error()devolución de llamada jQuery, por ejemplo), luego verificar si la llamada XHR es a un 'https : // 'URL, y luego verifique si el XHR readyStatees 0, lo que significa que la conexión XHR ni siquiera se ha abierto (que es lo que sucede cuando al navegador no le gusta el certificado).

Aquí está el código donde hago esto: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js#L88

maratbn
fuente
1

No creo que actualmente haya una forma de detectar estos mensajes de error, pero un truco que puede hacer es usar un servidor como nginx frente a su servidor de aplicaciones, de modo que si el servidor de aplicaciones no funciona, obtendrá un error error de puerta de enlace de nginx con 502código de estado que puede detectar en JS. De lo contrario, si el certificado no es válido, seguirá recibiendo el mismo error genérico con statusCode = 0.

gafi
fuente