web-dev-qa-db-ja.com

JavascriptでXMLHttpRequest()のクロスオリジン(CORS)エラーとその他のタイプのエラーを検出する方法

不正なリクエストではなく、クロスオリジンエラーが原因で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(access-control-allow-Originエラー)とケース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
50
user2871305

いいえ、W3C仕様によると、違いを見分ける方法はありません。

以下に、CORS仕様で 単純なクロスオリジンリクエスト プロシージャを指定する方法を示します。

リクエストの作成手順を適用し、リクエストの作成中に以下のリクエストルールを遵守します。

手動リダイレクトフラグが設定されておらず、応答のHTTPステータスコードが301、302、303、307、または308の場合:リダイレクト手順を適用します。

エンドユーザーがリクエストをキャンセルした場合:中止ステップを適用します。

ネットワークエラーがある場合:DNSエラー、TLSネゴシエーション失敗、またはその他のタイプの場合ネットワークエラーの場合、ネットワークエラーステップを適用します。エンドユーザーとの対話を要求しないでください...

それ以外の場合:リソース共有チェックを実行します。 失敗が返された場合、ネットワークエラー手順を適用します...

失敗したネットワーク接続または失敗したCORS交換の場合、 network error steps が適用されるため、文字通り2つのケースを区別する方法はありません。

どうして? 1つの利点は、攻撃者がLANのネットワークトポロジを検査するのを防ぐことです。たとえば、悪意のあるWebページスクリプトは、HTTPインターフェースをリクエストすることでルーターのIPアドレスを見つけることができるため、ネットワークトポロジに関するいくつかのこと(たとえば、プライベートIPブロックの大きさ、/8または/16)。ルーターはCORSヘッダーを送信しない(または送信しない)ため、スクリプトはまったく何も学習しません。

39
apsillers

たぶんそれが誰かを助ける場合... corsとネットワークエラーの違いを処理するもう1つの方法... 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
}
7
Krishna K

CORS違反を他の失敗したAJAXリクエストと区別するために、)サーバーを使用してHEADリクエストのレスポンスヘッダーを調べることができます-side codeを実行し、クライアントページに結果を返します。たとえば、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が見つかりました
無効なリクエスト


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-OK

http://google.com-Access-Control-Allow-Originヘッダーセットなし

http://10.0.0.1-存在しないか、タイムアウトしていません

5
Drakes

驚くべきことに、あなたは画像に対してそれを行うことができます(そして、おそらく他の特定のコンテンツタイプにも同様の解決策があります)。トリックは、明らかに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();
}
2
amik

これが私がしたことです-ただし、リモートサーバーに変更を加える必要があります(ページを追加します)。

  1. Iframeで提供されるprobe.htmlページを作成しました(CORSを使用して接続しようとしているリモートOriginで)
  2. Window.onmessageハンドラーをページに登録します
  3. 安全なiframeを使用してprobe.htmlページをページに読み込みます
  4. Iframe onloadイベントのページ内で、window.postMessage()を使用してiframeにメッセージを送信します
  5. Probe.htmlページは、URLをプローブします(iframe内の同じOriginから)
  6. Probe.htmlページは、window.postMessage()を使用してxhr.statusおよびxhr.statusText応答の詳細を送信します
  7. ステータスがエラーの場合、ステータスとstatusTextをログサービスに記録します

ただし、どちらの方法でもパラメーターを渡す場合、安全にすることは非常に難しいことに注意してください(だれでもiframeを埋め込むことができます。

また、完全ではありません(一時的な一時的ではないエラーのみを検出します)。

ただし、多くの作業がありますが、エラーの原因に関する詳細情報を取得できます。ユーザーのブラウザに直接アクセスできない場合に非常に便利です。

PS:これは、PHP回答とはまったく異なります。回答は、サーバーがリモートサーバーと通信する必要があることを示唆しています。(a)通信障害の原因の診断にはあまり使用されません。 from PHPは、サーバーが真剣に使用されていることを要求しているだけです!

0
robocat