⚖️

CORS AnywhereをHerokuへデプロイする方法

2021/03/01に公開

Webにはあるリソースから他のリソースに対して簡単にアクセスできないようにする仕組み(同一オリジンポリシー)があります。

ただ、Web開発をしていると異なるオリジン間での通信が必要になるケースがあります。その場合必要となる知識がCORSです。
CORSとはオリジン間リソース共有のことで、異なるオリジン同士で通信を可能にする仕組みのことです。
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

CORSのエラー対応にはいくつか手法がありますが、この記事ではCORS Anywhereを使ったプロキシサーバーをHerokuに用意して利用する方法を紹介します。
CORS AnywhereとはプロキシされたリクエストにCORSヘッダーを追加するNode.jsプロキシのことです。

CORS Anywhereを試すにはこれまで公開サーバーが用意されていましたが、2021年1月31日に限定的な利用のみしか受け付けなくなりました。
https://github.com/Rob--W/cors-anywhere/issues/301

CORS Anywhereを利用したプロキシサーバーを構築する上で使用したサービス、アプリケーション、言語等は以下になります。

環境の準備

最終的なプロジェクトのファイル構成は以下のようになります。

project-name
├── package.json
├── Procfile
├── .gitignore
├── src
│   └── server.ts
├── tsconfig.json
└── yarn.lock

yarnでパッケージの初期化を行います。インストールがまだの方は以下リンクを参照してみてください。
https://classic.yarnpkg.com/en/docs/install#mac-stable

yarn init

早速CORS Anywhereをインストールしましょう。CORS Anywhereは型定義ファイルが提供されていないようなので@typesではインストールしません。

yarn add cors-anywhere

次にNode.jsをTypeScriptで扱えるようにしてみます。

yarn add typescript -D

Node.jsの型定義ファイルをインストールします。

yarn add @types/node

プロジェクトのルートディレクトリにsrcフォルダーを作成し、server.tsファイルを作成します。
originWhitelistにリクエストを許可するオリジンを記載できます。
空の配列にするとすべてのオリジンからリクエストを受け付ける事になります。

server.ts
const host = process.env.HOST || '0.0.0.0';
const port = process.env.PORT || 8080;

import * as cors_proxy from 'cors-anywhere';
cors_proxy.createServer({
    // 許可するオリジンを記載
    originWhitelist: ['https://example.com'],
    requireHeader: ['origin', 'x-requested-with'],
    removeHeaders: ['cookie', 'cookie2']
}).listen(port, host, function() {
    console.log('OHTAM CORS Anywhere起動中 ' + host + ':' + port);
});

サーバー側の記述は以上になります。
続いてpackage.jsonにTypeScriptのビルドやNode.jsサーバーの起動コマンドを記述します。

package.json
{
  "name": "typescript-cors-anywhere",
  "version": "1.0.0",
  "main": "server.js",
  "license": "MIT",
  "scripts": {
    "build": "tsc",
    "watch": "tsc --watch",
    "start": "node server.js",
    "heroku-postbuild": "tsc"
  },
  "dependencies": {
    "@types/node": "^14.14.31",
    "cors-anywhere": "^0.4.3"
  },
  "devDependencies": {
    "typescript": "^4.1.5"
  }
}

heroku-postbuildコマンドにはHeroku上でパッケージインストール処理が終了した後に実行する処理を明示的に記述できます。
https://devcenter.heroku.com/ja/articles/nodejs-support

tsconfig.jsonファイルをプロジェクトのルートディレクトリに作成し、以下のように記述します。
tsconfig.jsonではコンパイルに必要なオプションを指定します。

tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2020",
    "sourceMap": true,
    "types": [
      "node"
    ],
    "rootDir": "./src",
    "outDir": "./",
  },
  "exclude": [
    "node_modules"
  ]
}

https://www.typescriptlang.org/docs/handbook/tsconfig-json.html

ここまで準備ができたらビルドコマンドを実行してみましょう!

yarn build

ルートディレクトリにserver.jsが作成されていればOKです。
次にNode.jsサーバーを起動してみましょう。

yarn start

Herokuへのデプロイ

問題なくローカルサーバーの起動が確認できたら最後にProcfileをルートディレクトリに作成します。
Procfileでアプリの起動時に実行するコマンドを明示的に宣言します。ここでは以下のように記述します。

web: node server.js

https://devcenter.heroku.com/ja/articles/getting-started-with-nodejs#procfile

ローカルでの作業は以上です。ローカルで用意したプロジェクトファイルはGitHubにリポジトリを作成し、プッシュしておきます。

Herokuでアプリケーションを新規作成し、先ほど作成したGitHubのリポジトリと紐付けを行います。
GitHubと統合しておくことでソースコードのデプロイを自動化できます。
https://devcenter.heroku.com/ja/articles/github-integration
https://qiita.com/sho7650/items/ebd87c5dc2c4c7abb8f0

手動デプロイを実行して問題なくサーバーが起動するか確認してみましょう。

フロントからのリクエスト方法

リクエスト時にはプロキシサーバーのURLとリクエスト先のURLを以下のように繋げて記述します。

http://localhost:8080/http://example.com/

フロントからCORS Anywhereを使ってリクエストを投げるサンプル処理が以下になります。

(function() {
    // Herokuにホスティングしたホストを記載
    var cors_api_host = 'cors-anywhere.herokuapp.com';
    var cors_api_url = 'https://' + cors_api_host + '/';
    var slice = [].slice;
    var origin = window.location.protocol + '//' + window.location.host;
    var open = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function() {
        var args = slice.call(arguments);
        var targetOrigin = /^https?:\/\/([^\/]+)/i.exec(args[1]);
        if (targetOrigin && targetOrigin[0].toLowerCase() !== origin &&
            targetOrigin[1] !== cors_api_host) {
            args[1] = cors_api_url + args[1];
        }
        return open.apply(this, args);
    };
})();

jQueryを使ったサンプル処理なども以下公式ドキュメントに記載されていますので参考にしてみてください。
https://github.com/Rob--W/cors-anywhere#readme

まとめ

今回CORSエラー対応にプロキシを用いていますが、可能であればリクエストを受け付けるサーバー側で処理を実装しておくことが理想です。

Discussion