如何在 Javascript 中检测 XMLHttpRequest()的交叉起源(CORS)错误与其他类型错误

我试图检测 XMLHttpRequest ()何时由于交叉起源错误而不是错误请求而失败。例如:

    ajaxObj=new XMLHttpRequest()
ajaxObj.open("GET", url, true);
ajaxObj.send(null);

考虑4种情况下的 url:

案例1: url 是一个正确设置了访问控制允许源的有效地址

  • 示例: http://192.168.8.35,其中我有一个在头部设置了 Access-Control-Allow-Origin: *的服务器
  • 这很容易检测为 ajaxObjec.readyState = = 4和 ajaxObjec.status = = 200

案例2: url 是现有服务器上的无效地址

  • 示例: http://xyz.google.com,其中服务器响应但不是有效请求
  • 这导致 ajaxObjec.readyState = = 4和 ajaxObjec.status = = 0

案例3: url 指向一个不存在的服务器 ip 地址

  • 示例: http://192.168.8.6在我的本地网络中没有任何响应
  • 这导致 ajaxObjec.readyState = = 4和 ajaxObjec.status = = 0

案例4: url 是一个有效的地址,其中 access-control-allow-source 是 没有

  • 示例: http://192.168.8.247,其中在头部设置了一个服务器 没有Access-Control-Allow-Origin: *
  • 这导致 ajaxObjec.readyState = = 4和 ajaxObjec.status = = 0

问题是: 如何区分案例4(访问控制允许起源错误)和案例2和案例3?

在案例4中,Chrome 调试控制台显示错误:

XMLHttpRequest cannot load http://192.168.8.247/. Origin http://localhost is not allowed by Access-Control-Allow-Origin.

如何在 Javascript 中使该错误为人所知?

我试图找到一些迹象在 ajaxObj,但似乎没有什么不同的情况下2和3。

下面是我使用的一个简单测试:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>CORS Test</title>
<script type="text/javascript">
function PgBoot()
{
//  doCORS("http://192.168.8.35");   // Case 1
//  doCORS("http://xyz.google.com"); // Case 2
doCORS("http://192.168.8.6");    // Case 3
//  doCORS("http://192.168.8.247");  // Case 4
}


function doCORS(url)
{
document.getElementById("statusDiv").innerHTML+="Processing url="+url+"<br>";
var ajaxObj=new XMLHttpRequest();
ajaxObj.overrideMimeType('text/xml');
ajaxObj.onreadystatechange = function()
{
var stat=document.getElementById("statusDiv");
stat.innerHTML+="readyState="+ajaxObj.readyState;
if(ajaxObj.readyState==4)
stat.innerHTML+=", status="+ajaxObj.status;
stat.innerHTML+="<br>";
}
ajaxObj.open("GET", url, true);
ajaxObj.send(null);
}
</script>
</head>
<body onload="PgBoot()">
<div id="statusDiv"></div>
</body>
</html>

使用铬的结果:

Processing url=http://192.168.8.35
readyState=1
readyState=2
readyState=3
readyState=4, status=200

Processing url=http://xyz.google.com
readyState=1
readyState=4, status=0

Processing url=http://192.168.8.6
readyState=1
readyState=4, status=0

Processing url=http://192.168.8.247
readyState=1
readyState=4, status=0
54032 次浏览

不,根据 W3C 规范,没有办法区分。

以下是 CORS 规范如何规定 简单的跨原产地要求程序:

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

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

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

如果存在网络错误: 在 DNS 错误、 TLS 协商失败或其他类型的网络错误的情况下,应用网络错误步骤。不要要求任何类型的终端用户交互..。

否则: 执行资源共享检查。

在网络连接失败或 CORS 交换失败的情况下,将应用 网络错误步骤,因此实际上无法区分这两种情况。

为什么?其中一个好处是,它可以防止攻击者检查局域网的网络拓扑。例如,一个恶意的网页脚本可以通过请求路由器的 HTTP 接口来找到路由器的 IP 地址,从而了解你的网络拓扑(例如,你的私有 IP 块有多大,/8/16)。因为您的路由器不(或不应该)发送 CORS 头,所以脚本绝对不会学到任何东西。

为了区分 CORS 冲突和其他失败的 AJAX 请求,可以使用 服务器端代码服务器端代码检查 HEAD 请求的响应头,并将结果传递回客户端页面。例如,如果 AJAX 请求失败(状态0) ,您可以调用这个脚本(我们称之为 cors.php) ,并确切地知道响应头是否包含 Access-Control-*头。

例子:

译自: 美国《科学》杂志网站(http://ip.jsontest.com)
译自: 美国《科学》杂志网站(http://www.google.com)
Url = http://10.0.0.1

报税表

HTTP/1.1200 OK 访问控制-允许-起源: *
发现 HTTP/1.1302
无效请求


Php -根据需要进行定制

<?php /* cors.php */
$url = $_GET["url"];
if(isset($url)) {
$headers = getHeaders($url);
header("Access-Control-Allow-Origin: *");


if(count($headers) == 0) {
die("Invalid request"); // cURL returns no headers on bad urls
} else {
echo $headers[0];       // echo the HTTP status code
}


// Include any CORS headers
foreach($headers as $header) {
if(strpos($header, "Access-Control") !== false) {
echo " " . $header;
}
}
}


function getHeaders($url, $needle = false) {
$headers = array();
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 4);        // Timeout in seconds
curl_setopt($ch, CURLOPT_TIMEOUT, 4);               // Timeout in seconds
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD');    // HEAD request only
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use(&$headers) {
array_push($headers, $header);
return strlen($header);
});
curl_exec($ch);
return $headers;
} /* Drakes, 2015 */

客户端测试工具:

function testCORS(url, $elem) {
$.ajax({
url: url,
timeout: 4000
})
.fail(function(jqXHR, textStatus) {
if(jqXHR.status === 0) {
// Determine if this was a CORS violation or not
$.ajax({
context: url,
url: "http://myserver.com/cors.php?url=" + escape(this.url),
})
.done(function(msg) {
if(msg.indexOf("HTTP") < 0) {
$elem.text(url + " - doesn't exist or timed out");
} else if(msg.indexOf("Access-Control-Allow-Origin") >= 0) {
$elem.text(url + " - CORS violation because '" + msg + "'");
} else {
$elem.text(url + " - no Access-Control-Allow-Origin header set");
}
});
} else {
// Some other failure (e.g. 404), but not CORS-related
$elem.text(url + " - failed because '" + responseText + "'");
}
})
.done(function(msg) {
// Successful ajax request
$elem.text(this.url + " - OK");
}); /* Drakes, 2015 */
}

安全带驾驶员:

// Create a div and append the results of the URL calls
$div = $("<div>");
$("body").append($div);


var urls = ["http://ip.jsontest.com", "http://google.com", "http://10.0.0.1"];
urls.map( function(url) {
testCORS(url, $div.append("<h4>").children().last());
});

结果:

Http://ip.jsontest.com-OK

Http & # 58;//google.com-没有访问控制-允许-起源头集

Http://10.0.0.1-不存在或超时

也许以防万一它帮助任何人... 另一种方法处理之间的差异和网络错误... 可以与铬或火狐... (虽然不是完美的解决方案)

var external = 'your url';


if (window.fetch) {
// must be chrome or firefox which have native fetch
fetch(external, {'mode':'no-cors'})
.then(function () {
// external is reachable; but failed due to cors
// fetch will pass though if it's a cors error
})
.catch(function () {
// external is _not_ reachable
});
} else {
// must be non-updated safari or older IE...
// I don't know how to find error type in this case
}

令人惊讶的是,您可以对图像这样做(也许对于其他特定的内容类型也有类似的解决方案)。窍门是显然不考虑 CORS,因此语句 “ url 未能通过 XHR 加载 & url 成功通过 img src 加载”暗示 URL 可以工作,但 CORS 被阻塞。

这当然不是在所有情况下都有帮助,但是在某些情况下(例如,如果正确传递 CORS,实用程序检测)它可能会起作用。例如,如果我的应用程序(单独的前端和后端)安装了正确配置的后端 URL 并且在服务器上错误配置了 CORS,我想在控制台中触发一个警告,所以这对我来说已经足够完美了,因为我可以提供一个图像来测试它。

function testCors(url) {
var myRequest = new XMLHttpRequest();
myRequest.open('GET', url, true);
myRequest.onreadystatechange = () => {
if (myRequest.readyState !== 4) {
return;
}
if (myRequest.status === 200) {
console.log(url, 'ok');
} else {
var myImage = document.createElement('img');
myImage.onerror = (...args) => {console.log(url, 'other type of error', args);}
myImage.onload = () => {console.log(url, 'image exists but cors blocked');}
myImage.src = url;
console.log(url, 'Image not found');
}
};
myRequest.send();
}

下面是我所做的——尽管它确实需要您对远程服务器进行更改(向其添加页面)。

  1. 我创建了一个 probe.html 页面,在 iframe 中提供服务(在您试图使用 CORS 联系的远程原点上)
  2. 我在页面中注册了一个 window.onmessage 处理程序
  3. 我使用一个安全的 iframe 将 probe.html 页面加载到页面中
  4. 在 iframe onload 事件的页面中,我使用 window.postMessage ()向 iframe 发送一条消息
  5. Probe.html 页面探测 url (来自 iframe 中的相同原点)
  6. Probe.html 页面使用 window.postMessage ()向我发送 xhr.status 和 xhr.statusText 响应细节
  7. 如果状态是错误的,我将状态和 statusText 记录到我们的日志服务

但是,要注意,如果以任何一种方式传递参数(任何人都可以嵌入您的 iframe,其他各方都可能导致向页面或 iframe 发送 postMessage) ,那么很难保证它的安全性。

而且它并不完美(它只检测非短暂的一次性错误)。

然而,尽管工作量很大,但是您可以获得有关错误原因的更多信息。当您不能直接访问用户的浏览器时,这非常有用。

PS: 这完全不同于 PHP 的答案,PHP 建议您的服务器应该与远程服务器通信(a) ,这不是很多用于诊断通信故障的原因,和(b)使用 curl 从 PHP 只是要求您的服务器严重受损!