73

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

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

考虑 url 的 4 种情况:

案例 1: url 是正确设置 access-control-allow-origin 的有效地址

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

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

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

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

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

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

  • 示例:http://192.168.8.247我有一个没有 Access-Control-Allow-Origin: *在标题中设置的服务器
  • 这导致 ajaxObj.readyState==4 和 ajaxObj.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>

使用 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
4

5 回答 5

58

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

下面是 CORS 规范如何指定简单的跨域请求过程:

提出请求时应用提出请求的步骤并遵守以下请求规则。

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

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

如果出现网络错误:如果出现DNS 错误、TLS 协商失败或其他类型的网络错误,请应用网络错误步骤。不要请求任何类型的最终用户交互...

否则:执行资源共享检查。如果返回失败,请应用网络错误步骤...

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

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

于 2013-10-11T19:22:28.857 回答
17

也许以防万一它可以帮助任何人...处理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
}
于 2016-11-18T18:25:22.880 回答
7

要将 CORS 违规与其他失败的 AJAX 请求区分开来,您可以使用服务器端代码检查 HEAD 请求的响应标头并将结果传递回您的客户端页面。例如,如果 AJAX 请求失败(状态 0),您可以调用此脚本(让我们称之为它cors.php)并确定响应标头是否包含Access-Control-*标头。

例子:

cors.php?url=http://ip.jsontest.com
cors.php?url=http://www.google.com
cors.php?url=http://10.0.0.1

返回

HTTP/1.1 200 OK Access-Control-Allow-Origin: *
HTTP/1.1 302 Found
Invalid request


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 */

客户端测试工具:

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 - 好的

http://google.com - 没有 Access-Control-Allow-Origin 标头集

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

于 2015-05-02T15:35:10.383 回答
4

令人惊讶的是,您可以对图像执行此操作(也许对于其他特定内容类型也有类似的解决方案)。诀窍是显然不考虑 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();
}
于 2018-10-08T12:57:39.500 回答
1

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

  1. 我创建了一个要在 iframe 中提供的 probe.html 页面(在您尝试使用 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. 如果状态是错误,我将状态和状态文本记录到我们的日志服务

但是,请注意,如果以任何一种方式传递参数,都很难确保其安全(任何人都可以嵌入您的 iframe,其他方可能会导致 postMessage 到页面或 iframe)。

而且它并不完美(它只检测不是临时一次性的错误)。

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

PS:这与建议您的服务器应该与远程服务器通信的 PHP 答案完全不同(a)这对于诊断通信失败原因没有多大用处,并且(b)使用 PHP 的 curl 只是要求您的服务器认真对待!

于 2018-11-05T06:50:48.590 回答