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