Edited at

RailsのAPIモードでいい感じにCORS設定をする

More than 1 year has passed since last update.


概要

Ionic製のクライアントアプリにて、Rails製のAPIサーバーからのレスポンスヘッダが一部取得できなかった。

「追加したヘッダがなんでクライアント側で見えないんだよ(泣」

ってなっていたものの、解決できたので備忘録として残すことにした。

RailsのAPIモードで、 rack-cors でCORS設定をいい感じにすることによって、api-pagination によりヘッダが追加されたリソースをクライアントで取得できるようにするまでを書いていく。


なんでヘッダが取得できなかったか

CORSの設定が足りなかったから。

APIサーバとWebクライアント、みたいな構成の場合、それぞれ異なるドメイン下に置くことも多いと思います。

あるいはスマホアプリのクライアントとAPIサーバー、みたいな場合とか。

僕もそういう状況でした。

こういった場合、異なるオリジン間でのリクエストということで制限がかかります。

CORSの設定に不備があったため、レスポンスヘッダが取得できなかった、という状態。


環境


  • Rails 5.2.0


    • APIモードを利用



  • Ruby 2.5.1


  • rack-cors


    • CORSをいろいろするgem




  • api-pagination


    • ページネーションのためのgem




api-paginationの利用

入ってなかったら追記してください。


Gemfile

gem 'api-pagination'


READMEに書いてあることで全て説明は終わりだ、いいね?


Paginate in your headers, not in your response body. This follows the proposed RFC-5988 standard for Web linking.

レスポンスボディでなくてヘッダでページネートする。RFC-5988標準のリンク要素に従う。


Railsで使う場合の記述もある。

そこには「コントローラーでrender使ってる感じでpaginate使え」みたいなことが書いてるので従う。

paginate json: hoge_obj

みたいな感じですね。

そうするとこのコントローラーによって作成されたレスポンスのヘッダに以下の要素が追加される。


  • Link


    • URLの情報



  • Total


    • レコード数



  • Per-Page


    • 1ページあたりのレコード数



READMEには以下のような例が載っている。


叩いたURL

https://localhost:3000/movies?page=5



レスポンスヘッダ

Link: <http://localhost:3000/movies?page=1>; rel="first",

<http://localhost:3000/movies?page=173>; rel="last",
<http://localhost:3000/movies?page=6>; rel="next",
<http://localhost:3000/movies?page=4>; rel="prev"
Total: 4321
Per-Page: 10

クライアントアプリで Link, Total, Per-Page の3つのヘッダを読めるようにしたい。


rack-corsの利用

こっちはrails newした時点でもう記述されてあると思う。

が、コメントアウトされた状態になっているため、解除してgemを入れてあげる。


Gemfile

gem 'rack-cors'


gemを入れると config/initializers/cors.rb が生成されるので、編集する。


config/initializers/cors.rb

Rails.application.config.middleware.insert_before 0, Rack::Cors do

allow do
origins "*"

resource "*",
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
expose: ['Per-Page', 'Total', 'Link']
end
end


以下のような設定になっています。


  • すべてのオリジンからのリクエストを許可する

  • 全てのリソースに対して次のものを許可する


    • APIサーバに対するリクエストにどんなヘッダでもつけることを許可する


    • methodsで指定したメソッドでのリソースへのアクセスを許可する


    • exposeで指定したものは、レスポンスのHTTPヘッダとして公開を許可する(別オリジンからのリクエスト者でも見えるようにする)




ヘッダが取得できなかった状態の設定ファイル


config/initializers/cors.rb

Rails.application.config.middleware.insert_before 0, Rack::Cors do

allow do
origins "*"

resource "*",
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end


僕はexposeの設定をしていなかったんですね。

なので、各メソッドでのリソースへのアクセスまでは許可されていたけど、ヘッダは提供できていなかった、と。


確認する

ChromeでFetchAPIを使って、正しく設定できたか確認してみる。

Chromeで適当なページを開き、デベロッパーツール -> [Console]タブを開く。

※自分のサイト以外でないとクロスオリジンの検証にならないのでそこだけ注意

このConsoleにてFetchAPIを使う。

僕の環境ではリクエストヘッダに認証情報が必要なのでヘッダの設定もしてる。

> const headers = new Headers();

> headers.append("Authorization", "hogehoge");

エンドポイントに対してfetchしてみる。

「レスポンスヘッダの"Per-Page"の情報をください」の意。

> fetch(new Request("https://example.com/hoge"), {method: "GET", mode: 'cors', headers}).then(r => console.log(r.headers.get("Per-Page")));

10

たとえば以下のようにすると、ETagは公開するように設定していないのでnullが返る。

> fetch(new Request("https://example.com/hoge"), {method: "GET", mode: 'cors', headers}).then(r => console.log(r.headers.get("ETag")));

null

みたいな感じです。

僕はリソースの取得は確認できていて、ヘッダが気になっていたのでr.headers.get()としています。

レスポンスのjsonデータなんかを確認したい時には以下のようにしてみるとよいと思います。

> const headers = new Headers();

> headers.append("Authorization", "hogehoge");
> fetch(new Request("https://example.com/hoge"), {method: "GET", mode: 'cors', headers}).then(r => console.log(r.json()));