ajax 如何在 JavaScript 中检测 XMLHttpRequest() 的跨域 (CORS) 错误与其他类型的错误
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19325314/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to Detect Cross Origin (CORS) Error vs. Other Types of Errors for XMLHttpRequest() in Javascript
提问by user2871305
I'm trying to detect when an XMLHttpRequest() fails due to a Cross Origin Error as opposed to a bad request. For example:
我试图检测 XMLHttpRequest() 何时因跨源错误而不是错误请求而失败。例如:
ajaxObj=new XMLHttpRequest()
ajaxObj.open("GET", url, true);
ajaxObj.send(null);
Consider 4 cases for url:
考虑 url 的 4 种情况:
Case 1:url is a valid address where access-control-allow-origin is properly set
情况 1:url 是有效地址,其中 access-control-allow-origin 设置正确
- Example:
http://192.168.8.35where I have a server withAccess-Control-Allow-Origin: *set in the header - This is easy to detect as ajaxObj.readyState==4 and ajaxObj.status==200
- 示例:
http://192.168.8.35我Access-Control-Allow-Origin: *在标题中设置了一个服务器 - 这很容易检测为 ajaxObj.readyState==4 和 ajaxObj.status==200
Case 2:url is an invalid address at an existing server
情况 2:url 是现有服务器上的无效地址
- Example:
http://xyz.google.comwhere the server responds but it is not a valid request - This results in ajaxObj.readyState==4 and ajaxObj.status==0
- 示例:
http://xyz.google.com服务器响应但不是有效请求 - 这导致 ajaxObj.readyState==4 和 ajaxObj.status==0
Case 3:url is to a non-existing server ip address
情况 3:url 指向一个不存在的服务器 ip 地址
- Example:
http://192.168.8.6on my local network where there is nothing to respond - This results in ajaxObj.readyState==4 and ajaxObj.status==0
- 示例:
http://192.168.8.6在我没有任何响应的本地网络上 - 这导致 ajaxObj.readyState==4 和 ajaxObj.status==0
Case 4:url is a valid address where access-control-allow-origin is NOTset
案例4:URL是一个有效的地址在访问控制允许来源是不是集
- Example:
http://192.168.8.247where I have a server withoutAccess-Control-Allow-Origin: *set in the header - This results in ajaxObj.readyState==4 and ajaxObj.status==0
- 示例:
http://192.168.8.247我的服务器没有Access-Control-Allow-Origin: *在标题中设置 - 这导致 ajaxObj.readyState==4 和 ajaxObj.status==0
The problem is: How do I differentiate Case 4 (access-control-allow-origin error) and Cases 2&3?
问题是:如何区分案例 4(访问控制允许来源错误)和案例 2&3?
In Case 4, the Chrome debug console shows the error:
在案例 4 中,Chrome 调试控制台显示错误:
XMLHttpRequest cannot load http://192.168.8.247/. Origin http://localhost is not allowed by Access-Control-Allow-Origin.
XMLHttpRequest cannot load http://192.168.8.247/. Origin http://localhost is not allowed by Access-Control-Allow-Origin.
How do I make that error known in Javascript?
如何在 Javascript 中知道该错误?
I tried to find some indication in ajaxObjbut nothing there seems to be different compared to Case 2&3.
我试图在其中找到一些迹象,ajaxObj但与案例 2 和 3 相比似乎没有什么不同。
Here is a simple test I used:
这是我使用的一个简单测试:
<!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>
Results using Chrome:
使用 Chrome 的结果:
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
回答by apsillers
No, there is no way to tell the difference, according the W3C Spec.
不,根据 W3C 规范,无法区分差异。
Here's how the CORS specification specifies the simple cross-origin requestprocedure:
以下是 CORS 规范如何指定简单的跨域请求过程:
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...
Otherwise:Perform a resource sharing check. If it returns fail, apply the network error steps...
应用发出请求步骤并在发出请求时遵守以下请求规则。
如果手动重定向标志未设置且响应的 HTTP 状态代码为 301、302、303、307 或 308:应用重定向步骤。
如果最终用户取消请求:应用中止步骤。
如果出现网络错误:如果出现DNS 错误、TLS 协商失败或其他类型的网络错误,请应用网络错误步骤。不要请求任何类型的最终用户交互...
否则:执行资源共享检查。如果返回失败,请应用网络错误步骤...
In the case of either a failed network connection or a failed CORS exchange, the network error stepsare applied, so there is literally no way to distinguish between the two cases.
在网络连接失败或 CORS 交换失败的情况下,将应用网络错误步骤,因此实际上无法区分这两种情况。
Why? One benefit is that it prevents an attacker from inspecting the network topology of a LAN. For example, a malicious Web page script could find the IP address of your router by requesting its HTTP interface and therefore learn a few things about your network topology (e.g., how big your private IP block is, /8or /16). Since your router doesn't (or shouldn't) send CORS headers, the script learns absolutely nothing.
为什么?一个好处是它可以防止攻击者检查 LAN 的网络拓扑。例如,恶意网页脚本可以通过请求路由器的 HTTP 接口来找到路由器的 IP 地址,从而了解有关您的网络拓扑的一些信息(例如,您的私有 IP 块有多大,/8或/16)。由于您的路由器不会(或不应该)发送 CORS 标头,因此脚本绝对不会学到任何东西。
回答by Krishna K
Maybe in case it helps anyone... one other way to handle difference between cors and network error... can work with chrome or firefox... (not perfect solution though)
也许万一它可以帮助任何人......处理cors和网络错误之间差异的另一种方法......可以与chrome或firefox一起使用......(虽然不是完美的解决方案)
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
}
回答by Drakes
To differentiate a CORS violation from other failed AJAX requests, you can inspect the response headers of a HEAD request using server-side codeand pass the results back to your client page. For example, if the AJAX request fails (status 0), you could call this script (let's call it cors.php) and know for certain if the response headers contain Access-Control-*headers.
要将 CORS 违规与其他失败的 AJAX 请求区分开来,您可以使用服务器端代码检查 HEAD 请求的响应标头,并将结果传回您的客户端页面。例如,如果 AJAX 请求失败(状态 0),您可以调用此脚本(我们称之为cors.php)并确定响应标头是否包含Access-Control-*标头。
Examples:
例子:
cors.php?url=http://ip.jsontest.com
cors.php?url=http://www.google.com
cors.php?url=http://10.0.0.1
cors.php?url=http://ip.jsontest.com
cors.php?url=http://www.google.com
cors.php?url=http://10.0.0.1
returns
返回
HTTP/1.1 200 OK Access-Control-Allow-Origin: *
HTTP/1.1 302 Found
Invalid request
HTTP/1.1 200 OK Access-Control-Allow-Origin: *
HTTP/1.1 302 发现
无效请求
cors.php- Customize as needed
cors.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 */
Client-side test harness:
客户端测试工具:
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 */
}
Harness driver:
线束驱动:
// 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());
});
The results:
结果:
http://ip.jsontest.com - OK
http://google.com - no Access-Control-Allow-Origin header set
http://10.0.0.1 - doesn't exist or timed out
http://ip.jsontest.com - 好的
http://google.com - 没有 Access-Control-Allow-Origin 标头集
http://10.0.0.1 - 不存在或超时
回答by amik
Surprisingly you can do that for images (and maybe there is a similar solution for other particular content types). The trick is that does apparently not regard CORS, therefore statement "url fails to load by XHR && url succeeds to load by img src"implies that the URL works but is CORS blocked.
令人惊讶的是,您可以对图像执行此操作(对于其他特定内容类型,也许有类似的解决方案)。诀窍是显然不考虑 CORS,因此声明“url 无法通过 XHR 加载 && url 成功通过 img src 加载”暗示该 URL 有效但被 CORS 阻止。
This definitely does not help in all cases, but in some cases (e.g. utility detecting if you're properly passing CORS) it may do the trick. For example I wanted to fire a a warning in console if my app (separate frontend and backend) is installed with correctly configured URL of backend and wrongly configured CORS on server, so this was perfectly enough for me as I can serve an image to test it on.
这绝对不会在所有情况下都有帮助,但在某些情况下(例如,实用程序检测您是否正确传递了 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();
}
回答by robocat
Here's what I did - although it does need you to make changes to the remote server (add a page to it).
这就是我所做的 - 尽管它确实需要您对远程服务器进行更改(向其中添加一个页面)。
- I created an probe.html page to be served in an iframe (on the remote origin you are trying to use CORS to contact)
- I register a window.onmessage handler in my page
- I load the probe.html page into my page using a secure iframe
- Within my page on the iframe onload event, I send a message to the iframe using window.postMessage()
- The probe.html page probes the url (from the same origin within the iframe)
- The probe.html page sends me the xhr.status and xhr.statusText response details using window.postMessage()
- If the status is an error, I log the status and statusText to our logging service
- 我创建了一个要在 iframe 中提供的 probe.html 页面(在您尝试使用 CORS 联系的远程源上)
- 我在我的页面中注册了一个 window.onmessage 处理程序
- 我使用安全的 iframe 将probe.html 页面加载到我的页面中
- 在 iframe onload 事件的页面内,我使用 window.postMessage() 向 iframe 发送消息
- probe.html 页面探测 url(来自 iframe 内的同一来源)
- probe.html 页面使用 window.postMessage() 向我发送 xhr.status 和 xhr.statusText 响应详细信息
- 如果状态是错误,我会将状态和状态文本记录到我们的日志服务中
However, beware that it is very difficult to make it secure if passing parameters either way (anyone can embed your iframe, other parties can cause postMessage to the page or the iframe).
但是,请注意,如果以任何一种方式传递参数,都很难确保其安全(任何人都可以嵌入您的 iframe,其他方可以将 postMessage 发送到页面或 iframe)。
And it isn't perfect (it only detects errors that are not transient one-offs).
而且它并不完美(它只检测非暂时性的错误)。
However although it is a lot of work you can get more information about the cause of an error. Very useful when you can't directly access the browser of your users.
但是,尽管工作量很大,但您可以获得有关错误原因的更多信息。当您无法直接访问用户的浏览器时非常有用。
PS: this is completely different from the PHP answer that suggests your server should talk to the remote server (a) that isn't much use to diagnose communication failure causes, and (b) using curl from PHP is just asking for your server to be seriously pwned!
PS:这与建议您的服务器应该与远程服务器通信的 PHP 答案完全不同 (a) 这对于诊断通信失败原因没有多大用处,并且 (b) 使用 PHP 中的 curl 只是要求您的服务器被严重骗了!

