WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

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

JavaScript の console.log()

by WebSurfer 31. January 2024 20:15

JavaScript の console.log() で開発者ツールの Console に JavaScript オブジェクトや html 要素 (DOM) を出力すると、Console を開いた時点での内容 (下の (1) の時点での内容ではなくて) が表示されるという話を書きます。

開発者ツールの Console 出力

Qiita の質問「javascriptとブラウザのコンソールについて」で調べたことですが、忘れそうなので自分のブログに備忘録として残しておこうと思った次第です。

上の画像の JavaScript のコードの (1) を見てください。obj = { prop: 123 } という JavaScript オブジェクトを作成してから即 console.log(obj) とし、その後で prop の値を 456 に書き換えています。しかしながら、コンソールを開いて見ると prop が書き換えられた後の 456 という結果が表示されています。

これは、MDN のドキュメント console: log() static method の「Logging objects」のセクションに書いてあるように、"Information about an object is lazily retrieved. This means that the log message shows the content of an object at the time when it's first viewed, not when it was logged." ということによります。

コード上で console.log() とした時点での JavaScript オブジェクトのログを取りたい場合は、MDN のドキュメントに書いてあるように "A common way is to JSON.stringify() and then JSON.parse() it" とするのが良さそうです。それが上の画像の JavaScript のコード (2) です。

Console を開いた時点での内容が表示されるというのは、JavaScript オブジェクトだけでなく、 html 要素 (DOM) を JavaScript で書き換えても同じことが起こります。

上の画像の (4) を見てください。JavaScript で html の p 要素を生成して id と textContent を設定してから即 console.log(p) し、その後で id と textContent を書き換えています。しかし、Console には書き換えた後の結果が表示されています。

コード上で console.log() とした時点での p 要素のログを取りたい場合は、p.outerHTML を出力するのが良さそうです。それが上の画像の (5) です。

JavaScript はブラウザ依存ですが、Windows 10 PC の Edge 121.0.2277.83, Chrome 121.0.6167.140, Firefox 122.0, Opera 106.0.4998.66 で試して同じ結果となることは確認しました。

Tags: , ,

JavaScript

About this blog

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

Calendar

<<  May 2024  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar