Determine if ajax call failed due to insecure response or connection refused

时间:2015-06-25 19:07:19

标签: javascript jquery ajax https cors

I've been doing a lot of research and could not find a way to handle this. I'm trying to perform a jQuery ajax call from an https server to a locahost https server running jetty with a custom self signed certificate. My problem is that I cannot determine whether the response is a connection refused or a insecure response (due to the lack of the certificate acceptance). Is there a way to determine the difference between both scenarios? The responseText, and statusCode are always the same in both cases, even though in the chrome console I can see a difference: net::ERR_INSECURE_RESPONSE net::ERR_CONNECTION_REFUSED responseText is always "" and statusCode is always "0" for both cases. My question is, how can I determine if a jQuery ajax call failed due to ERR_INSECURE_RESPONSE or due to ERR_CONNECTION_REFUSED? Once the certificate is accepted everything works fine, but I want to know whether the localhost server is shut down, or its up and running but the certificate has not yet been accepted. $.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. } }); Even performing manually the request I get the same result: 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 个答案:

答案 0 :(得分:63)

无法将其与最新的Web浏览器区分开来。

W3C规范:

  

以下步骤描述了用户代理必须为简单的跨源请求执行的操作:      

在提出请求时应用make a request步骤并遵守下面的请求规则。

     

如果未设置手动重定向标志且响应的HTTP状态代码为301,302,303,307或308   应用重定向步骤。

     

如果最终用户取消请求   应用中止步骤。

     

如果出现网络错误   如果出现DNS错误,TLS协商失败或其他类型的网络错误,请应用network error steps。不要求任何类型的最终用户互动。

     

注意:这不包括指示某种类型错误的HTTP响应,例如HTTP状态代码410.

     

,否则   执行资源共享检查。如果它返回失败,请应用网络错误步骤。否则,如果它返回pass,则终止此算法并将跨源请求状态设置为success。实际上不要终止请求。

正如您所读到的,网络错误不包括包含错误的HTTP响应,因此您将始终将0作为状态代码,并将“”作为错误。

Source

注意:以下示例是使用Google Chrome版本43.0.2357.130以及我创建的用于模拟OP的环境。设置的代码位于答案的底部。

我认为解决这个问题的方法是通过HTTP而不是HTTPS作为This answer的辅助请求,但我记得由于较新版本的浏览器阻止了混合内容,因此无法做到这一点。

这意味着如果您使用HTTPS,Web浏览器将不允许通过HTTP发出请求,反之亦然。

几年前就是这样的,但是像版本23下面的Mozilla Firefox这样的旧版Web浏览器允许它。

关于它的证据:

从HTTPS发出HTTP请求,使用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();

将导致以下错误:

  

混合内容:“https://localhost:8000/”页面是通过HTTPS加载的,但是请求了一个不安全的XMLHttpRequest端点“http://localhost:8001/”。此请求已被阻止;内容必须通过HTTPS提供。

如果您尝试以添加Iframe的其他方式执行此操作,则会在浏览器控制台中显示相同的错误。

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

使用Socket连接也是Posted as an answer,我很确定结果会相同/相似,但我试一试。

尝试使用HTTPS打开从Web Broswer到非安全套接字端点的套接字连接将以混合内容错误结束。

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

1)混合内容:“https://localhost:8000/”页面是通过HTTPS加载的,但是尝试连接到不安全的WebSocket端点“ws:// localhost:8001 /”。此请求已被阻止;此端点必须通过WSS提供。

     

2)未捕获的DOMException:无法构造“WebSocket”:可能无法从通过HTTPS加载的页面启动不安全的WebSocket连接。

然后我也尝试连接到wss端点,看看我是否可以阅读有关网络连接错误的一些信息:

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

在关闭服务器的情况下执行上面的代码段会导致:

  

与'wss:// localhost:8001 /'的WebSocket连接失败:连接建立错误:net :: ERR_CONNECTION_REFUSED

在启用服务器的情况下执行上面的代码段

  

与'wss:// localhost:8001 /'的WebSocket连接失败:WebSocket开放握手被取消

但同样,“onerror function”输出到控制台的错误没有任何提示来区分另一个错误。

使用this answer suggest代理可以正常工作,但前提是“目标”服务器具有公共访问权限。

这不是这种情况,因此在这种情况下尝试实现代理会导致我们遇到同样的问题。

创建Node.js HTTPS服务器的代码

我创建了两个使用自签名证书的Nodejs HTTPS服务器:

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

要使其工作,您需要安装Nodej,需要为每个服务器生成单独的证书,并相应地将其存储在文件夹certs和certs2中。

要运行它,只需在终端中执行node applicationServer.jsnode targetServer.js(ubuntu示例)。

答案 1 :(得分:30)

截至目前:无法在浏览器之间区分此事件。由于浏览器不为开发人员提供访问权限。(2015年7月)

这个答案仅仅是为潜在的,混合的黑客和不完整的解决方案提供想法。

免责声明:此答案不完整,因为它没有完全解决OP的问题(由于跨源政策)。然而,这个想法本身确实有一些优点,可以通过以下方式进一步扩展: @artur grzesiak here ,使用代理和ajax。

经过我自己的大量研究后,似乎没有任何形式的错误检查连接被拒绝和不安全的响应之间的区别,至少就javascript提供了对之间差异的响应而言2。

我的研究普遍认为SSL证书是由浏览器处理的,因此在用户接受自签名证书之前,浏览器会锁定所有请求,包括状态码的请求。浏览器可以(如果编码)发送回自己的状态代码以获得不安全的响应,但这对任何事情都没有帮助,即使这样,您也会遇到浏览器兼容性问题(Chrome / firefox / IE有不同的标准......再一次)

由于您的原始问题是用于检查服务器的状态与正常服务器之间的状态,因此您是否可以制作标准的HTTP请求?

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

当然,这需要额外的请求来验证服务器连接,但它应该通过消除过程完成工作。尽管如此,仍然觉得很讨厌,而且我并不是双倍请求的忠实粉丝。

答案 2 :(得分:11)

@ Schultzie的答案非常接近,但显然http - 一般情况下 - 在浏览器环境中无法使用https

您可以做的是使用中间服务器(代理)代表您提出请求。代理应允许从http来源转发https请求或从自签名来源加载内容。

拥有适当证书的自己的服务器在您的情况下可能有点过分 - 因为您可以使用此设置而不是具有自签名证书的计算机 - 但是有大量的匿名开放代理服务< / em>在那里。

因此我想到了两种方法:

  1. ajax请求 - 在这种情况下,代理必须使用适当的CORS设置
  2. 使用iframe - 您可以通过代理在iframe中加载脚本(可能包含在html中)。加载脚本后,它会向其.parentWindow发送一条消息。如果您的窗口收到一条消息,您可以确定服务器正在运行(或者更确切地说,之前运行的时间只有几分之一)。
  3. 如果您只对本地环境感兴趣,可以尝试使用--disable-web-security标记运行chrome。

    另一个建议:您是否尝试以编程方式加载图像以查明是否存在更多信息?

答案 3 :(得分:1)

结帐jQuery.ajaxError() 摘自:jQuery AJAX Error Handling (HTTP Status Codes) 它捕获全局Ajax错误,您可以通过HTTP或HTTPS以多种方式处理这些错误:

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

答案 4 :(得分:1)

不幸的是,现在的浏览器XHR API并没有明确指示浏览器何时因为不安全的响应而拒绝连接,以及当它不信任网站的时候#l; HTTP / SSL证书。

但是有办法解决这个问题。

我想出一个确定浏览器何时不信任HTTP / SSL证书的解决方案是首先检测是否发生了XHR错误(例如使用jQuery error()回调),然后检查是否XHR电话是指&#39; https://&#39; URL,然后检查XHR readyState是否为0,这意味着甚至没有打开XHR连接(当浏览器不喜欢证书时会发生这种情况)。

这是我执行此操作的代码: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js#L88

答案 5 :(得分:1)

我认为目前没有办法检测这些错误消息,但您可以做的就是在应用服务器前面使用像nginx这样的服务器,这样如果应用程序服务器已关闭你将从nginx获得一个错误的网关错误,其中包含502状态代码,你可以在JS中检测到。否则,如果证书无效,您仍会与statusCode = 0获得相同的一般性错误。