TL;DR
- cors_plugを入れる
- routingのpipelineに
plug CORSPlug
を追加 - 各routeにOPTIONSを追加
動機
phoenixで作ったアプリのAPIをchrome拡張から叩こうとしたらCORSでハマって1時間苦労しました。
そもそもCORSをよく知らなかったのが大きくハマった原因。分かってる人なら「ああ、こうすれば解決するね」ってすぐ分かると思います。
環境
- Ubuntu 16.04
サーバ側
- Elixir 1.3.4
- Phoenix 1.2.1
- CORSPlug 1.1.3
クライアント側
- chrome 55
- superagent 3.3.1
流れ
1. Chromeに怒られる
chrome拡張からapiを叩いたら
XMLHttpRequest cannot load http://localhost/api/items. Origin null is not allowed by Access-Control-Allow-Origin.
とか言われる。
phoenixではcors_plugを使えばCORSに対応できるらしい。
defp deps do
[
# ...
{:cors_plug, "~> 1.2"} # 追加
]
end
$ mix deps.get
pipeline :api do
plug :accepts, ["json"]
plug CORSPlug # 追加
end
今回は全てのホストに対して許可していますが、特定のホストに対してCORSを許可したい時は、この辺を読んでください。
cors_plugのREADMEには「lib/your_app/endpoint.ex
に追加するのを勧める」って書いてあるのですが、web/router.ex
じゃダメなのでしょうか・・・?
2. また怒られる
以上でGETメソッドは叩けるようになったのですが、CREATEメソッドを叩くと今度はまた別のエラーがでました。
OPTIONS http://localhost:4000/api/items 404 (Not Found)
XMLHttpRequest cannot load http://localhost:4000/api/items. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'chrome-extension://fjimhgkchedkbdhbohdgabmafmfgcipg' is therefore not allowed access. The response had HTTP status code 404.
どうも事前にOPTIONSも叩くようなのでそれ用のルートも追加します。
scope "/", PhoenixApp do
resources "/items", ItemController
options "/items", ItemController, :options # 追加
options "/items/:id", ItemController, :options # 追加
end
コントローラー側に手を加える必要はありません。
これで問題なくCREATEメソッドのapiも叩けるようになりました。
関係ない話
production用のDockerイメージを作って、そちらでも動かしたりしているのですが、phoenixアプリをビルドしたDockerイメージに対してdocker-compose.yml
でvolumes: - .:/app
と書いていたせいで_build
が上書きされ、ソースを書き換える→docker-compose build
しても挙動が変わらずとても無駄な苦労をしました。
ちなみにDockerfile内でADD . /app
しておくとに_build
のディレクトリが使いまわせるため、phoenixアプリのコンパイル時にライブラリのコンパイルが走らず時間の節約になります。phoenixアプリのコンパイルは再度されるため、上記のような変なことは起こりません。
この辺りの話はまた別の機会に。