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

43,830

Solution 1

There is no way to differentiate it from newest Web Browsers.

W3C Specification:

The steps below describe what user agents must do for a simple cross-origin request:

Apply the make a request steps and observe the request rules below while making the request.

If the manual redirect flag is unset and the response has an HTTP status code of 301, 302, 303, 307, or 308 Apply the redirect steps.

If the end user cancels the request Apply the abort steps.

If there is a network error In case of DNS errors, TLS negotiation failure, or other type of network errors, apply the network error steps. Do not request any kind of end user interaction.

Note: This does not include HTTP responses that indicate some type of error, such as HTTP status code 410.

Otherwise Perform a resource sharing check. If it returns fail, apply the network error steps. Otherwise, if it returns pass, terminate this algorithm and set the cross-origin request status to success. Do not actually terminate the request.

As you can read, network errors does not include HTTP response that include errors, that is why you will get always 0 as status code, and "" as error.

Source


Note: The following examples were made using Google Chrome Version 43.0.2357.130 and against an environment that I've created to emulate OP one. Code to the set it up is at the bottom of the answer.


I though that an approach To work around this would be make a secondary request over HTTP instead of HTTPS as This answer but I've remembered that is not possible due that newer versions of browsers block mixed content.

That means that the Web Browser will not allow a request over HTTP if you are using HTTPS and vice versa.

This has been like this since few years ago but older Web Browser versions like Mozilla Firefox below it versions 23 allow it.

Evidence about it:

Making a HTTP request from HTTPS usign Web Broser console

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

will result in the following error:

Mixed Content: The page at 'https://localhost:8000/' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://localhost:8001/'. This request has been blocked; the content must be served over HTTPS.

Same error will appear in the browser console if you try to do this in other ways as adding an Iframe.

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

Using Socket connection was also Posted as an answer, I was pretty sure that the result will be the same / similar but I've give it a try.

Trying to Open a socket connection from the Web Broswer using HTTPS to a non Secure socket endpoint will end in mixed content errors.

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

1) Mixed Content: The page at 'https://localhost:8000/' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://localhost:8001/'. This request has been blocked; this endpoint must be available over WSS.

2) Uncaught DOMException: Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.

Then I've tried to connect to a wss endpoint too see If I could read some information about network connection errors:

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

Executing snippet above with Server turned off results in:

WebSocket connection to 'wss://localhost:8001/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED

Executing snippet above with Server turned On

WebSocket connection to 'wss://localhost:8001/' failed: WebSocket opening handshake was canceled

But again, the error that the "onerror function" output to the console have not any tip to differentiate one error of the other.


Using a proxy as this answer suggest could work but only if the "target" server has public access.

This was not the case here, so trying to implement a proxy in this scenario will lead Us to the same problem.

Code to create Node.js HTTPS server:

I've created two Nodejs HTTPS servers, that use self signed certificates:

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

To make it work you need to have Nodejs Installed, Need to generate separated certificates for each server and store it in the folders certs and certs2 accordingly.

To Run it just execute node applicationServer.js and node targetServer.js in a terminal (ubuntu example).

Solution 2

As of now: There is no way to differentiate this event between browers. As the browsers do not provide an event for developers to access. (July 2015)

This answer merely seeks to provide ideas for a potential, albiet hacky and incomplete, solution.


Disclaimer: this answer is incomplete as it doesn't completely solve OP's issues (due to cross-origin policies). However the idea itself does has some merit that is further expanded upon by: @artur grzesiak here, using a proxy and ajax.


After quite a bit of research myself, there doesn't seem to be any form of error checking for the difference between connection refused and an insecure response, at least as far as javascript providing a response for the difference between the two.

The general consensus of my research being that SSL certificates are handled by the browser, so until a self-signed certificate is accepted by the user, the browser locks down all requests, including those for a status code. The browser could (if coded to) send back it's own status code for an insecure response, but that doesn't really help anything, and even then, you'd have issues with browser compatibility (chrome/firefox/IE having different standards... once again)

Since your original question was for checking the status of your server between being up versus having an unaccepted certificate, could you not make a standard HTTP request like so?

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

Granted this does require an extra request to verify server connectivity, but it should get the job done by process of elimination. Still feels hacky though, and I'm not a big fan of the doubled request.

Solution 3

@Schultzie's answer is pretty close, but clearly http - in general - will not work from https in the browser environment.

What you can do though is to use an intermediate server (proxy) to make the request on your behalf. The proxy should allow either to forward http request from https origin or to load content from self-signed origins.

Having your own server with proper certificate is probably an overkill in your case -- as you could use this setting instead of the machine with self-signed certificate -- but there is a plenty of anonymous open proxy services out there.

So two approaches that come to my mind are:

  1. ajax request -- in such a case the proxy has to use appropriate CORS settings
  2. use of an iframe -- you load your script (probably wrapped in html) inside an iframe via the proxy. Once the script loaded it sends a message to its .parentWindow. If your window received a message you can be sure the server is running (or more precisely was running a fraction of second before).

If you are only interested in your local environment you can try to run chrome with --disable-web-security flag.


Another suggestion: did you try to load an image programatically to find out if more info is present there?

Solution 4

I don't think there's currently a way to detect these error messages, but a hack that you can do is to use a server like nginx in front of your application server, so that if the application server is down you'll get a bad gateway error from nginx with 502 status code which you can detect in JS. Otherwise, if the certificate is invalid you'll still get the same generic error with statusCode = 0.

Solution 5

Check out jQuery.ajaxError() Taken reference from : jQuery AJAX Error Handling (HTTP Status Codes) It catches global Ajax errors which you can handle in any number of ways over HTTP or HTTPS:

if (jqXHR.status == 501) {
//insecure response
} else if (jqXHR.status == 102) {
//connection refused
}
Share:
43,830
taxicala
Author by

taxicala

Web Developer, Curious and Proactive is how I see myself. Every day I try to get a new challenge and solve it. Each day I learn something new. SOreadytohelp

Updated on December 29, 2020

Comments

  • taxicala
    taxicala over 3 years

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

    enter image description here

    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();
    
  • taxicala
    taxicala almost 9 years
    I've read the entire answer and I don't quite think this will work. Both my servers are running HTTPS, meaning that at the moment I fire a request to an HTTP server, the browser will cancel it right away, because You cannot make ajax requests from a HTTPS server to a HTTP server
  • taxicala
    taxicala almost 9 years
    This is the same as doing the ajax the way i am doing it, but centralizing error responses in one place.
  • Varshaan
    Varshaan almost 9 years
    The point is that the certificate is not loaded when the ajax call is made as it seems to be the problem. any ways gl with finding answers :)
  • taxicala
    taxicala almost 9 years
    No, the problem is that I want to determine the difference between having to accept the certificate, and knowing if the server is shut down.
  • taxicala
    taxicala almost 9 years
    This is the most complete answer so far. It shows that you have actually done some research work and testing. I see you have tried every possible way to perform this and all scenarios are really well explained. You have also provided example code to quickly set up a test environment! Really good work! Thanks for the insight!
  • anre
    anre about 7 years
    Both when connection is refused and the certificate is not trusted, I get the same jqXHR.status = 0 (and jqXHR.readyState = 0), not qXHR.status == 102 or 501 as described in this answer.
  • FabricioG
    FabricioG over 5 years
    Excellent answer! However I'd like to point out that the self signed certificates would still create errors from a browser if they're from separate servers. @ecarrizo
  • ecarrizo
    ecarrizo over 5 years
    If we are talking about a network error, Probably You are able to see the errors in the console, Manually. but you have not access to it from the code in a secure environment.
  • Scott Clark
    Scott Clark about 3 years
    Is the ready state any different for ERR CONNECTION TIMEOUT? or do these two errors appear the same?