Monacaアプリを使っていて、常に悩まされるのがネットワークの使い方です。ネイティブアプリやサーバサイドのプログラミング言語では問題にならないCORSは、Webアプリケーションやハイブリッドアプリでは問題になります。今回はこのCORS問題を解決できるcordova-plugin-advanced-httpを紹介します。

CORSとは?

そもそものCORSについて簡単に説明します。
CORSはCross-Origin Resource Sharingの略になります。Webサイトは、個別のURLを持っているのは知っているかと思います。https://monaca.io/ であったり、https://www.google.com/ といった具合です。このURLは幾つかのパターンに分けることができます。

  1. ベースになるドメイン:https://ja.monaca.io/
  2. パスを追加:https://ja.monaca.io/login
  3. サブドメインを追加:https://en.monaca.io/
  4. 異なるトップレベルドメイン:https://monaca.com/
  5. 異なるドメイン:https://www.google.com/

1と2は、同じドメインとして見なされますが、3〜5については基本的にすべて別なドメインとして判断されます。別なドメインの場合、CORSの設定によって許可されていないとアクセスできません。

このアクセス制御は、サーバ側によって行われるもので、クライアント(WebブラウザやMonacaアプリのJavaScript)で制御できるものではありません。

なぜCORSのような仕組みがあるかというと、WebブラウザでURLを表示した時に、悪意を持った開発者によって、異なるWebサイトのデータが盗み取られないようにするためです。

例えば、どこかのWebサイトを見ただけでFacebookの情報を盗み取られたり、Instagramに写真をアップロードされたりするのは怖いことです。悪意を持った開発者であれば任意のツイートをさせたり、YouTube動画を再生させたりできることになります。こうした勝手な操作を防止するのが、CORSになります。

異なるWebサイトのデータを取得する際には、セッション情報を使います。この情報はCookieによって管理されていますが、開発者は外部サイトのCookieは読み取れないようになっています。その代わり、Webサイトにアクセスする際にCookieなどを意識せずにWebブラウザが自動的に設定してくれます。この時、CORSによって許可されている操作かどうかを判定できるようになっています。

CORSの制御について

CORSはサーバ側で細かく操作できます。制御しようと思えばユーザ単位で指定することもできます。しかし、多くの場合はHTTPメソッド単位での制御になるようです。GET(主にデータ取得系)は許可しながら、POST(データ作成)やPUT/PATCH(どちらもデータ修正)DELETE(データ削除)メソッドは許可しないといった具合です。また、アクセス元ドメインも指定可能です。サブドメインからは許可するといった指定もできます。

Monacaアプリの場合、アクセス元ドメインがローカルファイルになるため、CORS制限に引っかかってしまうことが多々あります。一般的にWebブラウザはhttp://またはhttps://からアクセスしますが、Monacaアプリはfile://になります。最近ではhttpsでないと許可しないといったセキュリティ上の制限を設けるケースも少なくありません。file://の場合、さらに制限がかかってしまいます。

そこで使えるのがcordova-plugin-advanced-httpになります。

cordova-plugin-advanced-httpの仕組み

通常のMonacaアプリでは、Webブラウザに備わっているネットワーク機能を使います。つまり、XMLHTTPやfetch APIです。

cordova-plugin-advanced-httpは、iOSやAndroidのネイティブネットワーク機能を使います。こちらを使う場合、CORS制限はありません。

しかし、Webブラウザ側であらかじめ持っているセッション情報は使えませんので、あらためてログインしてCookieを取得する必要があるでしょう。

cordova-plugin-advanced-httpの使い方

では簡単に使い方を紹介します。cordova-plugin-advanced-httpをインストールすると cordova.plugin.http というオブジェクトが使えるようになります。これは簡単には下記のメソッドを持っています。

  • GETメソッド用:cordova.plugin.http.get
  • POSTメソッド用:cordova.plugin.http.post
  • PUTメソッド用:cordova.plugin.http.put
  • PATCHメソッド用:cordova.plugin.http.patch
  • DELETEメソッド用:cordova.plugin.http.delete

これらを呼び出したいHTTPメソッドごとに使い分けます。いずれもURL、オプションパラメータ、HTTPヘッダーを指定します。

呼び出し方が、コールバック方式なので、Promiseであったりasync/awaitで使いたい場合には、次のような関数を定義すると使いやすくなります。

function promisify(cdvFunc) {
  return function() {
    return new Promise((res, rej) => {
      console.log('arguments', arguments);
      cdvFunc(...arguments, res, rej);
    });
  }
}

promisifyはコールバック方式をPromiseに変えるPromisifyというライブラリの実行部分を真似たものです。この関数を使うことで、GET処理を次のように書くことができます。

Googleからデータを取得します。

(async() => {
  const res = await promisify(cordova.plugin.http.get)(
    'https://www.google.com/', {}, {}
  );
  console.log(res.data);
})();

res.data はHTMLが入っています。

同様に、POST処理の場合は次のように書けます。

(async() => {
  const res = await promisify(cordova.plugin.http.post)(
    'https://www.hatena.ne.jp/login', {
      name: 'ID',
      password: 'PASSWORD'
    }, {}
  );
  console.log(res.data);
})();

一方、Webブラウザ標準のfetch APIで実行すると、ContentSecurityPolicyエラーになってしまいます。

[Error] Unhandled Promise Rejection: TypeError: Not allowed by ContentSecurityPolicy

consoleなどでは次のようなエラーメッセージも確認できるでしょう。

[Error] Refused to connect to https://www.hatena.ne.jp/login because it appears in neither the connect-src directive nor the default-src directive of the Content Security Policy.

その他の機能

cordova-plugin-advanced-httpには上記の他、次のようなメソッドも用意されています。

  • cordova.plugin.http.getBasicAuthHeader('user', 'password');
    HTTPのBasic認証で使えるヘッダーを生成します
  • cordova.plugin.http.setHeader('Hostname', 'Header', 'Value');
    任意のドメインに対してヘッダーを指定します
  • cordova.plugin.http.setDataSerializer('urlencoded');
    データのシリアライズを指定します。urlencoded/json/utf8/multipart/rawが指定可能です
  • cordova.plugin.http.setRequestTimeout(5.0);
    リクエストのタイムアウト時間を指定します
  • cordova.plugin.http.setFollowRedirect(true);
    リダイレクトをサポートします
  • cordova.plugin.http.getCookieString(url);
    Cookie文字列を取得します
  • cordova.plugin.http.setCookie(url, cookie, options);
    Cookie文字列を設定します
  • cordova.plugin.http.clearCookies();
    Cookieを削除します

この他、ファイルアップロードを行うcordova.plugin.http.uploadFileやダウンロードを行うcordova.plugin.http.downloadFileもありますので、多くのHTTPリクエストを処理できるでしょう。

まとめ

CORSの制約は、Monacaアプリを開発する上で欠かせない技術になります。自分で修正ができないなど、サーバ側での対応が難しい場合はcordova-plugin-advanced-httpを使ってみるといいでしょう。

silkimen/cordova-plugin-advanced-http: Cordova / Phonegap plugin for communicating with HTTP servers. Allows for SSL pinning!