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

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

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

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

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


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

[CORS](https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control)の設定が足りなかったから。

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

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

こういった場合、異なるオリジン間でのリクエストということで制限がかかります。
CORSの設定に不備があったため、レスポンスヘッダが取得できなかった、という状態。



# 環境

- Rails 5.2.0
    - APIモードを利用
- Ruby 2.5.1
- [rack-cors](https://github.com/cyu/rack-cors)
    - CORSをいろいろするgem
- [api-pagination](https://github.com/davidcelis/api-pagination)
    - ページネーションのためのgem

# api-paginationの利用

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

```ruby:Gemfile
gem 'api-pagination'
```

[README](https://github.com/davidcelis/api-pagination)に書いてあることで全て説明は終わりだ、いいね？

> Paginate in your headers, not in your response body. This follows the proposed RFC-5988 standard for Web linking.
> 
> レスポンスボディでなくてヘッダでページネートする。RFC-5988標準のリンク要素に従う。


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

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

```ruby
paginate json: hoge_obj
```

みたいな感じですね。

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

- Link
    - URLの情報
- Total
    - レコード数
- Per-Page
    - 1ページあたりのレコード数


[README](https://github.com/davidcelis/api-pagination)には以下のような例が載っている。

```:叩いた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を入れてあげる。

```ruby:Gemfile
gem 'rack-cors'
```

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

```ruby: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ヘッダとして公開を許可する（別オリジンからのリクエスト者でも見えるようにする）



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


```ruby: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](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API)を使って、正しく設定できたか確認してみる。

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

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


<img width="1095" alt="スクリーンショット 2018-06-20 21.53.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/213486/e0f8500d-7ff2-45f7-1798-6623db620569.png">


このConsoleにて[FetchAPI](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API)を使う。

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

```
> 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()));
```