概要
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の利用
入ってなかったら追記してください。
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には以下のような例が載っている。
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を入れてあげる。
gem 'rack-cors'
gemを入れると 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ヘッダとして公開を許可する(別オリジンからのリクエスト者でも見えるようにする)
ヘッダが取得できなかった状態の設定ファイル
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()));