Determine se a chamada ajax falhou devido a uma resposta insegura ou conexão recusada


114

Tenho feito muitas pesquisas e não consegui encontrar uma maneira de lidar com isso. Estou tentando realizar uma chamada jQuery ajax de um servidor https para um servidor https locahost executando jetty com um certificado autoassinado personalizado. Meu problema é que não consigo determinar se a resposta é uma conexão recusada ou uma resposta insegura (devido à falta de aceitação do certificado). Existe uma maneira de determinar a diferença entre os dois cenários? O responseTexte statusCodesão sempre os mesmos em ambos os casos, embora no console do Chrome eu possa ver a diferença:

net::ERR_INSECURE_RESPONSE
net::ERR_CONNECTION_REFUSED

responseTexté sempre "" e statusCodeé sempre "0" para ambos os casos.

Minha pergunta é: como posso determinar se uma chamada jQuery ajax falhou devido a ERR_INSECURE_RESPONSEou devido a ERR_CONNECTION_REFUSED?

Depois que o certificado é aceito, tudo funciona bem, mas eu quero saber se o servidor localhost está desligado ou está funcionando, mas o certificado ainda não foi aceito.

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

insira a descrição da imagem aqui

Mesmo realizando a solicitação manualmente, obtenho o mesmo 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();

Respostas:


67

Não há como diferenciá-lo dos navegadores da web mais recentes.

Especificação W3C:

As etapas abaixo descrevem o que os agentes do usuário devem fazer para uma solicitação simples de origem cruzada :

Aplique as etapas de fazer uma solicitação e observe as regras de solicitação abaixo ao fazer a solicitação.

Se o sinalizador de redirecionamento manual não estiver definido e a resposta tiver um código de status HTTP de 301, 302, 303, 307 ou 308, aplique as etapas de redirecionamento.

Se o usuário final cancelar a solicitação, aplique as etapas de anulação.

Se houver um erro de rede No caso de erros de DNS, falha de negociação TLS ou outro tipo de erro de rede, aplique as etapas de erro de rede . Não solicite nenhum tipo de interação do usuário final.

Observação: isso não inclui respostas HTTP que indicam algum tipo de erro, como o código de status HTTP 410.

Caso contrário, execute uma verificação de compartilhamento de recursos. Se retornar falha, aplique as etapas de erro de rede. Caso contrário, se ele retornar pass, termine este algoritmo e defina o status da solicitação de origem cruzada para sucesso. Na verdade, não encerre a solicitação.

Como você pode ler, os erros de rede não incluem respostas HTTP que incluem erros, por isso você obterá sempre 0 como código de status e "" como erro.

Fonte


Nota : Os exemplos a seguir foram feitos usando o Google Chrome versão 43.0.2357.130 e em um ambiente que criei para emular o OP one. O código para configurá-lo está na parte inferior da resposta.


I embora que uma abordagem para contornar este seria fazer um pedido secundário através de HTTP em vez de HTTPS como Essa resposta , mas eu lembrei que não é possível devido que as versões mais recentes de navegadores bloquear conteúdo misto.

Isso significa que o navegador da Web não permitirá uma solicitação por HTTP se você estiver usando HTTPS e vice-versa.

Isso tem sido assim desde alguns anos atrás, mas versões mais antigas do navegador da Web, como o Mozilla Firefox, abaixo das versões 23, permitem isso.

Provas sobre isso:

Fazendo uma solicitação HTTP de HTTPS usign 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();

resultará no seguinte erro:

Conteúdo misto: a página em ' https: // localhost: 8000 / ' foi carregada por HTTPS, mas solicitou um ponto de extremidade XMLHttpRequest inseguro ' http: // localhost: 8001 / '. Esta solicitação foi bloqueada; o conteúdo deve ser servido por HTTPS.

O mesmo erro aparecerá no console do navegador se você tentar fazer isso de outras maneiras, como adicionar um Iframe.

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

O uso de conexão Socket também foi postado como uma resposta , eu tinha certeza de que o resultado será o mesmo / semelhante, mas tentei.

Tentar abrir uma conexão de soquete do Web Broswer usando HTTPS para um endpoint de soquete não seguro resultará em erros de conteúdo misto.

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

1) Conteúdo misto: A página em ' https: // localhost: 8000 / ' foi carregada por HTTPS, mas tentou se conectar ao ponto de extremidade WebSocket inseguro 'ws: // localhost: 8001 /'. Esta solicitação foi bloqueada; este terminal deve estar disponível no WSS.

2) DOMException não capturada: Falha ao construir 'WebSocket': Uma conexão WebSocket insegura não pode ser iniciada a partir de uma página carregada em HTTPS.

Em seguida, tentei me conectar a um endpoint wss também, veja se eu pudesse ler algumas informações sobre erros de conexão de rede:

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

Executar o snippet acima com o servidor desativado resulta em:

Falha na conexão do WebSocket com 'wss: // localhost: 8001 /': Erro no estabelecimento da conexão: net :: ERR_CONNECTION_REFUSED

Executando o snippet acima com o servidor ativado

Falha na conexão do WebSocket com 'wss: // localhost: 8001 /': o handshake de abertura do WebSocket foi cancelado

Mas, novamente, o erro de que a saída da "função onerror" para o console não tem dica para diferenciar um erro do outro.


Usar um proxy como essa resposta sugere pode funcionar, mas apenas se o servidor "destino" tiver acesso público.

Este não foi o caso aqui, então tentar implementar um proxy neste cenário nos levará ao mesmo problema.

Código para criar servidor HTTPS Node.js :

Criei dois servidores HTTPS Nodejs, que usam certificados autoassinados:

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, você precisa ter o Nodejs instalado, precisa gerar certificados separados para cada servidor e armazená-los nas pastas certs e certs2 de acordo.

Para executá-lo basta executar node applicationServer.jse node targetServer.jsem um terminal (exemplo do ubuntu).


32

A partir de agora: Não há como diferenciar este evento entre os navegadores. Como os navegadores não fornecem um evento para os desenvolvedores acessarem. (Julho de 2015)

Esta resposta busca apenas fornecer ideias para uma solução potencial, ainda que incompleta e incompleta.


Isenção de responsabilidade: esta resposta está incompleta, pois não resolve completamente os problemas do OP (devido às políticas de origem cruzada). No entanto, a ideia em si tem algum mérito que é expandido por: @artur grzesiak aqui , usando um proxy e ajax.


Depois de um pouco de pesquisa, não parece haver qualquer forma de verificação de erro para a diferença entre conexão recusada e uma resposta insegura, pelo menos no que diz respeito ao javascript fornecer uma resposta para a diferença entre as duas.

O consenso geral de minha pesquisa é que os certificados SSL são gerenciados pelo navegador, então, até que um certificado autoassinado seja aceito pelo usuário, o navegador bloqueia todas as solicitações, incluindo aquelas para um código de status. O navegador poderia (se codificado) enviar de volta seu próprio código de status para uma resposta insegura, mas isso realmente não ajuda em nada e, mesmo assim, você teria problemas com a compatibilidade do navegador (chrome / firefox / IE tendo padrões diferentes. .. de novo)

Visto que sua pergunta original era para verificar o status do seu servidor entre estar ativo e ter um certificado não aceito, você não poderia fazer uma solicitação HTTP padrão como esta?

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

Concedido, isso exige uma solicitação extra para verificar a conectividade do servidor, mas deve fazer o trabalho por meio do processo de eliminação. Ainda me sinto maluco, porém, e não sou um grande fã do pedido duplo.


11

A resposta de @Schultzie é bem parecida, mas claramente http- em geral - não funcionará httpsno ambiente do navegador.

O que você pode fazer, porém, é usar um servidor intermediário (proxy) para fazer a solicitação em seu nome. O proxy deve permitir encaminhar a httpsolicitação da httpsorigem ou carregar conteúdo de origens autoassinadas .

Ter seu próprio servidor com o certificado adequado é provavelmente um exagero no seu caso - já que você poderia usar essa configuração em vez da máquina com certificado autoassinado - mas há uma abundância de serviços de proxy aberto anônimos por aí.

Portanto, duas abordagens que vêm à minha mente são:

  1. solicitação ajax - nesse caso, o proxy deve usar as configurações CORS apropriadas
  2. uso de um iframe - você carrega seu script (provavelmente embrulhado em html) dentro de um iframe por meio do proxy. Assim que o script é carregado, ele envia uma mensagem ao seu .parentWindow. Se sua janela recebeu uma mensagem, você pode ter certeza de que o servidor está funcionando (ou mais precisamente estava funcionando uma fração de segundo antes).

Se você está interessado apenas em seu ambiente local, pode tentar executar o Chrome com o --disable-web-securitysinalizador.


Outra sugestão: você tentou carregar uma imagem programaticamente para descobrir se há mais informações lá?



1

Infelizmente, a API XHR do navegador atual não fornece uma indicação explícita de quando o navegador se recusa a se conectar devido a uma "resposta insegura" e também quando não confia no certificado HTTP / SSL do site.

Mas existem maneiras de contornar esse problema.

Uma solução que encontrei para determinar quando o navegador não confia no certificado HTTP / SSL é primeiro detectar se ocorreu um erro XHR (usando o error()retorno de chamada jQuery, por exemplo) e, em seguida, verificar se a chamada XHR é para um 'https : // 'URL, e então verifique se o XHR readyStateé 0, o que significa que a conexão XHR nem sequer foi aberta (que é o que acontece quando o navegador não gosta do certificado).

Aqui está o código em que faço isso: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js#L88__transaction_details.js#L88


1

Não acho que haja atualmente uma maneira de detectar essas mensagens de erro, mas um hack que você pode fazer é usar um servidor como o nginx na frente do seu servidor de aplicativos, de modo que se o servidor de aplicativos estiver inativo, você obterá um erro erro de gateway do nginx com 502código de status que você pode detectar em JS. Caso contrário, se o certificado for inválido, você ainda obterá o mesmo erro genérico com statusCode = 0.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.