XMLHttpRequest error(CORS)

FlutterはiOS,Androidだけでなく、Web ,Windows,Macのアプリも共通の環境で開発できることが、特徴です。

今回、iOSの音声チャットボットアプリをベースに、webアプリに改変した場合に遭遇したXMLHttpRequest errorについて調査しました。

現象

音声チャットボットは、応答生成をサーバサイドで行なっています。iOSアプリでは問題なく動作していた、_request()にて、XMLHttpRequest errorが発生しました。

Future<void> _request(String input_word) async {
    String url = "<応答生成サーバURL>";
    Map<String, String> headers = {"Content-Type": "application/json"};
    String body = json.encode({
      'inputs': input_word,
    });

    try {
      http.Response resp = await http.post(Uri.parse(url), headers: headers, body: body);
      if (resp.statusCode != 200) {
        setState(() {
          int statusCode = resp.statusCode;
          botWords = '''Failed to post $statusCode''';
        });
      }

      setState(() {
       < 省略 >
      });
    } catch (e) {
      setState(() {
        botWords = e.toString();
      });
    }
  }

わかったこと

同様のエラーに関しての質問が以下のサイトで見つかりました。

FlutterからAPIアクセスする際に、XMLHttpRequest errorのエラーが出る

いくつかの解決方法が示されていますが、根本的な原因がCORSにあるということなので、こちらを参考にしました。

How to solve flutter web api cors error only with dart code?

対策

以下の通り実行することで、エラーは回避されました。

1- Go to flutter\bin\cache and remove a file named: flutter_tools.stamp

2- Go to flutter\packages\flutter_tools\lib\src\web and open the file chrome.dart.

3- Find '--disable-extensions'

4- Add '--disable-web-security'

オリジン間リソース共有 (CORS)とは

以下のサイトにわかりやすく記載されています。

オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。ウェブアプリケーションは、自分とは異なるオリジン (ドメイン、プロトコル、ポート番号) にあるリソースをリクエストするとき、オリジン間 HTTP リクエストを実行します。

オリジン間リクエストとは、例えば https://domain-a.com で提供されているウェブアプリケーションのフロントエンド JavaScript コードが XMLHttpRequest を使用して https://domain-b.com/data.json へリクエストを行うような場合です。

セキュリティ上の理由から、ブラウザーは、スクリプトによって開始されるオリジン間 HTTP リクエストを制限しています。例えば、 XMLHttpRequestや Fetch API は同一オリジンポリシー (same-origin policy) に従います。つまり、これらの API を使用するウェブアプリケーションは、そのアプリケーションが読み込まれたのと同じオリジンに対してのみリソースのリクエストを行うことができ、それ以外のオリジンからの場合は正しい CORS ヘッダーを含んでいることが必要です。

https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

本質的には、サーバ側でCORSに対応し、リクエスト側からはCORSヘッダをつけてリクエストを送る必要があるようです。

(追記)サーバ対応

サーバ側でCORSに対応しました。

応答生成サーバはFlask (python)で動作させています。Flask アプリのCORS対応は以下の通りです。

$ pip install flask_cors
from flask_cors import CORS   // を追加

app = Flask(__name__)

ーー 以下を追加 ーー
CORS(
    app,
    supports_credentials=True,    // cookieを使う場合のみ。今回は不要
   origins=["https://****.app/"]  // アクセスを許可するオリジンを指定。今回は不要
) 

以上を指定した結果、上記対策を元に戻してもローカルのwebアプリは問題なく動作したことを確認しました。

#次の記事に続く。。