親バカエンジニアのナレッジ帳

webのエンジニアをやっており、日頃の開発で詰まったことや書き残しておきたいことを載せています。

RailsでAPI用のアプリを作成(POST処理編)

APIのPOST処理を実行

前回は以下の記事のように、RailsでAPIを使用してGET処理を行いました。
ti-tomo-knowledge.hatenablog.com

今回はPOST処理の方を行い、データの保存まで行いたいと思います。
最終的に実行したいAPIコマンドは以下になります。

curl -X POST -H "Content-Type: application/json" -H "Origin: http://localhost:8080" -d '{"title":"EEEE", "body":"FFFF"}' localhost:3000/api/articles

前回作成したarticlesというテーブル(モデル)に対してデータを挿入する簡単な処理です。

急に出てきた

-H "Origin: http://localhost:8080"

の部分ですが、これはクロスサイトオリジンのテストをする時用に使用します。
簡単に言ってしまえば、APIでは色々な場所から自由にアクセスされてしまうと困るものがあります。
そのような場合に、指定のドメイン以外からのアクセスを遮断する設定(CORS対策)を入れる必要があるのですが、その設定が正しく動いているか確認するために接続元(Origin)を色々変えてテストしようと思います。

APIの記述

クロスサイトオリジンの部分は後述するとして、まずはAPIを実行できるようにしましょう。

rake routesを見ると、

       Prefix Verb   URI Pattern                   Controller#Action
 api_articles GET    /api/articles(.:format)       api/articles#index
              POST   /api/articles(.:format)       api/articles#create
  api_article GET    /api/articles/:id(.:format)   api/articles#show
              PATCH  /api/articles/:id(.:format)   api/articles#update
              PUT    /api/articles/:id(.:format)   api/articles#update
              DELETE /api/articles/:id(.:format)   api/articles#destroy

/api/articlesのPOST処理ではcreateメソッドで処理がされるようなので、コントローラに処理を書きましょう。

module Api
  class ArticlesController < ApplicationController
    def create
      articles = Article.new({title: params[:title], body: params[:body]})
      articles.save
      render json: { status: 'OK' }
    end
  end
end

これで実行すると、、、

以下のようなログが吐かれているので、無事にINSERTできたことがわかりますね。

app/controllers/api/articles_controller.rb:10
  Article Create (26.9ms)  INSERT INTO `articles` (`title`, `body`, `created_at`, `updated_at`) VALUES ('EEEE', 'FFFF', '2019-07-25 02:13:58', '2019-07-25 02:13:58')
 app/controllers/api/articles_controller.rb:10

ほんとこういう確認Rails楽だわ〜

CORSの設定

さて次はCORSの設定をしましょう。

やり方は「config/initializers/cors.rb」のファイルにもご丁寧に書いてあるのですが、
まずはrack-corsをインストールします。

Gemfileに以下を記述して(コメントアウト外して)

gem 'rack-cors'

インストールです。

bundle install


次にcors.rbのコメントアウトを消して以下のようにします。

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:3000'

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

これはlocalhost:3000からのアクセスだけ許可する(allow)という意味です。
※Cookie(クッキー)の送信をする場合は、合わせてcredentials: trueを入れておきましょう。

ではこの状態でもう一度実行してみましょう。

curl -X POST -H "Content-Type: application/json" -H "Origin: http://localhost:8080" -d '{"title":"EEEE", "body":"FFFF"}' localhost:3000/api/articles

はい、がんがんINSERTされます。
って、Originがlocalhost:8080なのにINSERTされちゃだめだろう!笑

どうにもcurlからやると上手くcorsの設定が効かずに弾いてくれません。
色々調べたのですがどうにもわからなかったので、fetch APIで実行することにしました。
適当なサイトを開いてdeveloperツールのconsole画面を開き、以下のコマンドを実行します。

const obj = {"title":"EEEE", "body":"FFFF"};
const method = "POST";
const body = JSON.stringify(obj);
const headers = {
  'Content-Type': 'application/json'
};
fetch("http://localhost:3000/api/articles", {method, headers, body}).then((res)=> res.json()).then(console.log).catch(console.error);

以下のメッセージが出てくるので上手く行ってる(ブロックされてる)みたいですね。

Access to fetch at 'http://localhost:3000/api/articles' from origin 'http://labs.opentone.co.jp' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

CORSの設定を環境ごとに分ける

ただしこれでは開発環境では良いですが、本番環境ではlocalhost:3000からのアクセスを許可してしまうことになります。
そこで、環境ごとに許可するOriginを場合分けしましょう。
やり方は様々で、環境変数で指定する方法などもありますが、今回はymlの設定ファイルを別途で用意して環境ごとの設定値を入れることにします。

configディレクトリ直下に、request.ymlというファイルを作り、以下のように環境ごとの値をいれましょう。

default: &default
  domain: localhost:3000, localhost:8080

development:
  <<: *default

test:
  <<: *default

production:
  domain: production_domain.com

※production_domain.comはあくまで例です。環境に合わせて設定してください。

次にapplication.rbに以下を追記しましょう。

config.x.request = ActiveSupport::InheritableOptions.new(config_for(:request))

最後にcors.rbのoriginsを書き換えればOKです。

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins Rails.application.config.x.request['domain']

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

これで環境ごとにoriginsの値を変更できますね。

次回はPOSTされた値のバリデート処理について書こうと思います。