WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

CORS 非対応の場合のエラーメッセージ(その2)

by WebSurfer 4. June 2024 12:47

先の記事「CORS 非対応の場合のエラーメッセージ」の続きで、fetch メソッドで指定した URL が間違っていた場合どうなるかを調べたので、備忘録として書いておきます。

URL が間違っているとサーバーからは HTTP 404 Not Found 応答が返ってきますが、それがどのように表示されるかが書いておきたかったことです。

ちなみに、404 応答というのは、(1) URL のホスト名は正しく名前解決されブラウザからの要求は Web サーバーに届いた、(2) 要求を受け取った Web サーバーは URL に指定されたリソースを探したが見つからなかった、(3) Web サーバーは見つからなかったという応答を返した・・・ということです。

検証に使ったのは、先の記事「Web API に CORS 実装 (CORE)」に書いた ASP.NET Core Web API アプリで、Web API の CORS を無効にして、ドメインが異なる MVC アプリから fetch API を使って Web API に要求を出します。その際、正しい URL は https://localhost:44371/api/values とすべきところを、values ⇒ valuesx としました。

実は、CORS を無効にしたら 404 応答は返ってこないと思い違いをしていたのですが、そうではなかったです。バックエンドが CORS 非対応でもフロントエンドから要求は出て、URL が間違っているため指定されたリソースが見つからない場合は 404 応答が返ってきます。

以下に、「CORS 非対応 URL 誤」「CORS 非対応 URL 正」「CORS 有効 URL 誤」の 3 つのケースで、Chrome のディベロッパーツールの Console に表示されるエラーメッセージと、Fiddler で要求・応答をキャプチャした画像を貼っておきます。

(1) CORS 非対応 URL 誤

CORS 非対応でもブラウザから要求は出ます。URL が間違っているので Web サーバーは 404 応答を返します。それを受けたブラウザでは、まず応答ヘッダに Access-Control-Allow-Origin が無いということで CORS 関係のエラーメッセージを出しています。次に、404 応答なのでそのエラーメッセージ、最後に Failed to fetch (fetch 失敗) というエラーメッセージを出しています。

CORS 非対応 URL 誤

Fiddler の画像です。404 応答が返ってきています。バックエンドが CORS 非対応なので応答ヘッダには Access-Control-Allow-Origin は含まれていません。

CORS 非対応 URL 誤


(2) CORS 非対応 URL 正

上の (1) のケースから URL を正しいものにした場合の結果です。URL は正しいので当然 404 応答ではなく 200 応答が返ってきます。JSON 文字列も正しく応答コンテンツに含まれて返ってきます。しかし、応答ヘッダに Access-Control-Allow-Origin が無いので fetch メソッドでのデータの取得に失敗しています。

CORS 非対応 URL 正

Fiddler の画像です。URL は正しいので 200 応答が返ってきています。応答の TextView (コンテンツ) を見ると期待通り JSON 文字列が返ってきています。しかし、バックエンドが CORS 非対応なので応答ヘッダには Access-Control-Allow-Origin は含まれていません。

CORS 非対応 URL 正


(3) CORS 有効 URL 誤

上の (1) のケースからバックエンドの CORS 対応を有効にした結果です。URL は間違ったままなので 404 応答が返ってきています。上の (1), (2) と違って fetch 失敗とはみなされないようです。

CORS 有効 URL 誤

CORS を有効にしたので応答ヘッダには Access-Control-Allow-Origin が含まれています。

CORS 有効 URL 誤

Chrome のディベロッパーツールの Source タブの画面です。上の (1), (2) のケースでは fetch(url) の実行で例外がスローされて処理が終わってしまいますが、(3) のケースでは if(response.ok) 以降に処理が進みます。

CORS 有効 URL 誤

Tags: , , , ,

JavaScript

CORS 非対応の場合のエラーメッセージ

by WebSurfer 2. April 2024 16:33

ブラウザの fetch API を使ってドメインが異なる Web API などに要求を出した時、Web API 側が CORS に対応していない場合のブラウザのエラーメッセージや Fiddler で見た時の要求・応答がどのようになるかを書きます。

検証に使ったのは、先の記事「Web API に CORS 実装 (CORE)」に書いた ASP.NET Core Web API アプリで、Web API の CORS を無効にして、ドメインが異なる MVC アプリから fetch API を使って Web API に要求を出しました。ブラウザは Windows 10 の Chrome 123.0.6312.86 と Edge 123.0.2420.65 です。

ブラウザのエラーメッセージは、ディベロッパーツールの Console タブを開くと見ることができ、Web API 側が CORS 非対応では以下のようになるはずです。赤枠が「単純リクエスト」の場合で、青枠が「プリフライトリクエスト」の場合です。(注: 「単純リクエスト」というのは古い CORS 仕様書の用語で、現在 CORS を定義している Fetch 仕様書 ではその用語を使用していないそうです)

ブラウザのエラーメッセージ

エラーメッセージを以下にコピペしておきます。文章中の 'https://localhost:44371/api/values' が Web API 側で 'https://localhost:44343' がブラウザ側です。

「単純リクエスト」の場合

Access to fetch at 'https://localhost:44371/api/values' from origin 'https://localhost:44343' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

「プリフライトリクエスト」の場合

Access to fetch at 'https://localhost:44371/api/values' from origin 'https://localhost:44343' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

「プリフライトリクエスト」の場合は "Response to preflight request doesn't pass access control check:" という文が追加されています。その文の有無で「プリフライトリクエスト」か否かが分かるはずです。

蛇足ですが、エラーメッセージに含まれる "If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled." を見て mode を no-cors に設定してもほとんどの場合解決にはなりませんので注意してください。詳しくは先の記事「fetch API の mode:"no-cors"」を見てください。

「単純リクエスト」と「プリフライトリクエスト」が必要になる場合の違いについて簡単に書いておきます。「単純リクエスト」になるのは、メソッドが GET, HEAD, POST のいずれかで、かつ、要求ヘッダが「CORS-safelisted request header (CORS セーフリストリクエストヘッダー)」の場合です。それ以外は「プリフライトリクエスト」が必要になります。(詳しくは、MDN のドキュメント「オリジン間リソース共有 (CORS)」を見てください)

例えば、JSON 文字列をコンテンツに含めて POST 送信する場合は要求ヘッダに Content-Type: application/json を含めると思いますが、その場合は「プリフライトリクエスト」が必要になります。


Fiddler で見た時の要求・応答は以下のようになります。CORS の要求側はブラウザの仕事で、プリフライトが必要か否かもブラウザが判断して、下の画像の赤枠で示した CORS に必要な要求を出してくれます。

「単純リクエスト」の場合

ブラウザは Origin を要求ヘッダに含めて送信しますが、応答ヘッダに Access-Control-Allow-Origin が含まれないということででエラーとなっています。

「単純リクエスト」の場合

「プリフライトリクエスト」の場合

OPTIONS メソッドを使って CORS に必要なヘッダを要求に含めて出しています。下の画像の赤枠部分を見てください。しかし、応答ヘッダに Access-Control-Allow-Origin が含まれないということでエラーとなっています。

「プリフライトリクエスト」の場合

上の「プリフライトリクエスト」の場合の画像で、応答が 405 Method Not Allowed、Allow: GET, POST となっています。これは ASP.NET Core Web API による応答と思われますが、なぜ Method Not Allowed となるのか、GET, POST でないとダメと言われるのかは調べ切れてません。想像ですが、CORS を有効にしないと OPTIONS メソッドが許可されないということではないかと思われます。


参考に、Web API 側が CORS に対応していて、ブラウザ側でデータの取得に成功する場合の要求・応答を Fiddler で見た画像も下に貼っておきます。

「単純リクエスト」の場合

単純リクエスト

「プリフライトリクエスト」の場合

Web API が CORS に対応しているので「プリフライトリクエスト」の応答ヘッダに Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin が含まれて返ってきます。

プリフライトリクエスト

ブラウザはそれを見て再度要求を出しデータを取得します。

データの取得

Tags: , , ,

JavaScript

JavaScript の関数内での this (その 2)

by WebSurfer 12. February 2024 18:20

JavaScript の関数内での this」の続きです。先の記事ではクラス内に定義した関数の話を書きましたが、この記事ではオブジェクト内に定義した関数について書きます。

クラス内でもオブジェクト内でも同じかと思っていたのですが、実はアロー関数の場合は this に設定されるものが違ってくるということを知ったので、調べたことをまとめて備忘録として書いておくことにした次第です。

下の画像のように Person オブジェクトをグローバルコンテキストで定義した場合、Visual Studio 2022 のインテリセンスで f2 に設定したアロー関数内の this を見ると this: typeof globalThis となっています。

オブジェクトの場合

MDN のドキュメント「globalThis」によると "globalThis はグローバルプロパティで、グローバルオブジェクトと同等であるグローバルな this が格納されています" とのことで、ブラウザでは window になるそうです。実際 Person.f2() を実行するとコンソールには window と出力されました。

MDN のドキュメント「this」に以下の説明があり、上の画像の (1), (2) はその通りの結果になっています。

(1) 「オブジェクトのメソッドとして」のセクション: 関数がオブジェクトのメソッドとして呼び出されるとき、その this にはメソッドが呼び出されたオブジェクトが設定されます。

(2) 「アロー関数」のセクション: アロー関数では、this はそれを囲む構文上のコンテキストの this の値が設定されます。グローバルコードでは、グローバルオブジェクトが設定されます。

アロー関数で this を使うことのメリットについては、MDN のドキュメント「アロー関数式」の「例」のセクションに以下の説明があり、setTimeout を使ったコード例が載っています。

"おそらくアロー関数を使う最大の利点は、 DOM レベルのメソッド(setTimeout() や EventTarget.prototype.addEventListener())で、通常は何らかのクロージャ、call()、apply()、bind() を使用して、関数が適切なスコープで実行されることを確認する必要があることです。"

addEventListener() を使った場合はどういう利点があるか、自分が考えた話なのでイマイチかもしれませんが、以下に例を書きます。

<body>
    <button type="button" id="button1">button</button>
</body>
<script>
    document.getElementById("button1")
            .addEventListener("click", listener);

    function listener() {
        const Person1 = {
            name: "Person1",
            f1: function () { console.log(this); },
            f2: () => { console.log(this); }
        }

        Person1.f1();  // Object
        Person1.f2();  // <button type="button" id="button1">button</button>
}
</script>

関数がイベントハンドラとして使用された場合 this はリスナーがアタッチされている DOM に設定されることを期待するはずです。上のコード例ではアロー関数の this は期待通りとなりますが、function() { ... } 内の this は Person1 オブジェクトが設定されます。

もう一つ、Promise オブジェクトのメソッド then() の中に設定され、非同期で実行されるコールバックの場合もアロー関数を使う利点があると思います。その例を以下に書きます。

const WeatherData = {
    result: "",
    setData: function (data) { this.result = data; },        
    fetchData: function() {
        fetch('jsonSample.json')
            .then(function (response) {
                return response.json();
            })
            //.then(function (data) {
            //    this.setData(data[0].name); // this は window
            //});
            .then(data => {
                this.setData(data[0].name);  // this は WeatherData
                console.log("fetchData:", this);
            });
    }
}

WeatherData.fetchData();

上のコードで、コメントアウトした方の this には window が設定されるので this.setData(data[0].name); で "Uncaught (in promise) TypeError: this.setData is not a function" というエラーになります。

一方、その下のアロー関数を使った場合、this には WeatherData オブジェクトが設定され、期待通り this.setData(data[0].name); で WeatherData オブジェクトの result プロパティが書き換えられます。

ちなみにですが、クラスの場合はアロー関数内の this は下の画像のようになり、クラス本体の this すなわちクラスのインスタンスが設定されるようです。

クラスの場合

これに関しては、MDN のドキュメント「アロー関数式」の「メソッドとしては使用不可」のセクションに以下の説明があります。

"クラスの本体は this コンテキストを持っているので、クラスフィールドのようなアロー関数はクラスの this コンテキストを閉じ、アロー関数の本体の中の this はインスタンス(または静的フィールドの場合はクラス自体)を正しく参照します。しかし、これは関数自身のバインディングではなく、クロージャであるため、 this の値が実行コンテキストによって変わることはありません。"

以下に検証に使ったコードを載せておきます。

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <button type="button" id="button1">button</button>
</body>
<script>
    // グローバルコンテキストでオブジェクトを定義
    const Person = {
        name: "Person",
        f1: function () { console.log(this); },
        f2: () => { console.log(this); }
    }

    Person.f1();    // Object -------- (1)
    Person.f2();    // Window -------- (2)

    // addEventListener で設定するリスナでアロー関数を使う
    document.getElementById("button1")
            .addEventListener("click", listener);

    function listener() {
        const Person1 = {
            name: "Person1",
            f1: function () { console.log(this); },
            f2: () => { console.log(this); }
        }

        Person1.f1();    // Object
        Person1.f2();    // <button type="button" id="button1">button</button>
    }

    // 以下は蛇足でクロージャーとクラスの例

    // クロージャー
    const Person2 = function () {
        this.Name = "クロージャー";
        this.f1 = function () { console.log(this); }
        this.f2 = () => { console.log(this); }
    }

    let p2 = new Person2();
    p2.f1();    // Person2
    p2.f2();    // Person2

    // クラス
    class Example {
        Name = "クラス";
        f1 = function () { console.log(this); }
        f2 = () => { console.log(this); }
    }    

    let e = new Example();
    e.f1();    // Example -------- (3)
    e.f2();    // Example -------- (4)

    // then() の中に設定するコールバックでアロー関数を使う
    const WeatherData = {
        result: "",
        setData: function (data) { this.result = data; },        
        fetchData: function() {
            fetch('jsonSample.json')
                .then(function (response) {
                    return response.json();
                })
                //.then(function (data) {
                //    this.setData(data[0].name); // this は Window
                //});
                .then(data => {
                    this.setData(data[0].name);  // this は WeatherData
                    console.log("fetchData:", this);
                });
        },

        fetchData2: async function () {
            const response = await fetch('jsonSample.json');
            const data = await response.json();
            this.setData(data[0].name);
            console.log("fetchData2:", this);
        }
    }

    WeatherData.fetchData();

    WeatherData.fetchData2();
</script>
</html>

上のコードを実行したコンソール出力は以下の通りです。最後の 2 つは button をクリックして listener() を起動して出力させたものです。

コンソール出力

Tags: , , ,

JavaScript

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  July 2024  >>
MoTuWeThFrSaSu
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar