Rails による API 専用アプリ

このガイドの内容:

1 APIアプリについて

従来、Railsの「API」というと、プログラムからアクセスできるAPIをwebアプリに追加することを指すのが通例でした。たとえば、GitHubが提供するAPI をカスタムクライアントから利用できます。

近年、さまざまなクライアント側フレームワークが登場したことによって、Railsで作ったバックエンドサーバ―を、他のwebアプリケーションとネイティブアプリケーションの間で共有する手法が増えてきました。

たとえば、Twitterは自社のwebアプリで パブリックAPI を利用しています。このwebアプリは、JSONリソースを消費するだけの静的サイトとして構築されています。

多くの開発者が、Railsで生成したHTMLフォームやリンクをサーバー間のやりとりに使うではなく、webアプリケーションを単なるAPIクライアントにとどめて、JSON APIを利用するHTMLとJavaScriptの提供に徹するようになってきました。

本ガイドでは、JSONリソースをAPIクライアントに提供するRailsアプリの構築方法を解説します。クライアント側フレームワークについても言及します。

2 JSON APIにRailsを使う理由

RailsでJSON APIを構築することについて、多くの開発者から真っ先に受ける質問といえば「RailsでJSONを出力するのは大げさすぎませんか?Sinatraじゃだめなんですか?」です。

単なるAPIサーバーであれば、おそらくそうでしょう。しかし、フロントのHTMLの比重が非常に大きいアプリであっても、ロジックのほとんどはビューレイヤ以外の部分にあるのです。

Railsが多くの開発者に採用されている理由は、細かな設定をいちいち決めなくても、すばやくアプリを立ち上げられるからこそです。

APIアプリケーションの開発にすぐ役立つRailsの機能をいくつかご紹介します。

ミドルウェア層で提供される機能

  • 再読み込み: Railsアプリでは「透過的な再読み込み」がサポートされます。たとえアプリケーションが巨大化し、リクエストごとにサーバーを再起動する方法が使えなくなっても、透過的な再読み込みは有効です。
  • developmentモード: Railsアプリのdevelopmentモードには洗練されたデフォルト値が設定されているので、本番のパフォーマンスなどの問題にわずらわされません。
  • test モード: developmentと同様です。
  • ログ出力: Railsアプリはリクエストごとにログを出力します。また、現在のモードに応じてログの詳細レベルが調整されます。developmentモードのログには、リクエスト環境、データベースクエリ、基本的なパフォーマンス情報などが出力されます。
  • セキュリティ: IPスプーフィング攻撃 を検出・防御します。また、タイミング攻撃 に対応できる暗号化署名を扱います。ところでIPスプーフィング攻撃やタイミング攻撃って何でしょうね。
  • パラメータ解析: URLエンコード文字列の代わりにJSONでパラメータを指定できます。JSONはRailsでデコードされ、paramsでアクセスできます。もちろん、ネストしたURLエンコードパラメータも扱えます。
  • 条件付きGET: Railsでは、ETagLast-Modifiedを使った条件付きGETを扱えます。条件付きGETはリクエストヘッダを処理し、正しいレスポンスヘッダとステータスコードを返します。コントローラに stale? チェックを追加するだけで、HTTPの細かなやりとりはRailsが代行してくれます。
  • HEADリクエスト: Railsでは、HEADリクエストを透過的にGETリクエストに変換し、ヘッダだけを返します。これによって、すべてのRails APIでHEADリクエストを確実に利用できます。

Rackミドルウェアのこうした既存の機能を自前で構築することもできますが、Railsのデフォルトのミドルウェアを「JSON生成専用」に使うだけでも多数のメリットが得られます。

Action Pack層で提供される機能

  • リソースベースのルーティング: RESTful JSON APIを開発するなら、Railsのルーターも使いたいところです。RailsでおなじみのHTTPからコントローラへの明確なマッピングを利用できるので、生のHTTPに沿ってAPIモデルをゼロから設計する必要がありません。
  • URL生成: ルーティングは、URL生成にも便利です。よくできたHTTPベースのAPIにはURLも含まれています(GitHub Gist API がよい例)。
  • ヘッダレスポンスやリダイレクトレスポンス: head :no_contentredirect_to user_url(current_user)などをすぐ利用できます。ヘッダレスポンスを自分で書かずに済みます。
  • キャッシュ: Railsでは、ページキャッシュ、アクションキャッシュ、フラグメントキャッシュを利用できます。特に、フラグメントキャッシュはネストJSONオブジェクトを構成するときに便利です。
  • BASIC認証、ダイジェスト認証、トークン認証: 3種類のHTTP認証を簡単に導入できます。
  • Instrumentation(計測): Railsのinstrumentation APIは、登録したさまざまなイベントハンドラをトリガーできます。アクションの処理、ファイルやデータの送信、リダイレクト、データベースクエリなどを扱えます。各イベントのペイロードにはさまざまな関連情報が含まれます。たとえば、イベントを処理するアクションの場合、ペイロードにはコントローラ、アクション、パラメータ、リクエスト形式、リクエストの完全なパスなどが含まれます。
  • ジェネレータ: コマンド1つでリソースを手軽に生成して、APIに合うモデル、コントローラ、テストスタブ、ルーティングをすぐに利用できます。マイグレーションなども同じコマンドで行えます。
  • プラグイン: サードパーティのライブラリを多数利用できます。ライブラリの設定やwebフレームワークとの連携も簡単なので、コストを削減できます。プラグインによっては、デフォルトのジェネレータをオーバーライドするものがあります。追加されるRakeタスクは、Rails標準に沿ったものになります(ロガーやキャッシュのバックエンドなど)。

もちろん、Railsのブートプロセスでは、登録済みのコンポーネントをすべて読み込んで連携します。たとえば、ブート中にconfig/database.ymlファイルを使ってActive Recordを設定します。

忙しい方へ: Railsからビュー層を取り除いた後で、どんな機能を引き続き利用できるのでしょう。手短に言うと「ほとんどの機能」です。

3 基本設定

APIサーバーにするRailsアプリをすぐにでも構築したいのであれば、機能を限定したRailsサブセットを作って、必要な機能を順次追加するのがよいでしょう。

3.1 アプリケーションを新規作成する

API Railsアプリの生成には次のコマンドを使います。

$ rails new my_api --api

上のコマンドを実行すると、次の3つの操作を行います。

  • 利用するミドルウェアを通常よりも絞り込んでアプリケーションを起動するよう設定します。特に、ブラウザ向けアプリケーションで有用なミドルウェア(cookiesのサポートなど)を一切利用しなくなります。
  • ApplicationControllerを、通常のActionController::Baseの代わりにActionController::APIから継承します。ミドルウェアと同様、Action Controllerモジュールのうち、ブラウザ向けアプリケーションでしか使われないモジュールをすべて除外します。
  • ビュー、ヘルパー、アセットを生成しないようジェネレーターを設定します。

3.2 既存アプリを変更する

既存のアプリをAPI専用に変えるには、次の手順をお読みください。

config/application.rbApplicationクラス定義の冒頭に、次を追加します。

config.api_only = true

developmentモードでのエラー発生時にレスポンスで使う形式を設定するには、config/environments/development.rbファイルでconfig.debug_exception_response_formatを設定します。

値を:defaultにすると、HTMLページにデバッグ情報を表示します。

config.debug_exception_response_format = :default

値を:apiにすると、レスポンスの形式を保ったままデバッグ情報を表示します。

config.debug_exception_response_format = :api

config.api_onlyをtrueに設定すると、config.debug_exception_response_formatがデフォルトで:apiに設定されます。

最後に、app/controllers/application_controller.rbの以下のコードを置き換えます。

class ApplicationController < ActionController::Base
end 

上を以下に変更します。

class ApplicationController < ActionController::API
end 

4 ミドルウェアの選択

APIアプリケーションでは、デフォルトで以下のミドルウェアを利用できます。

  • Rack::Sendfile
  • ActionDispatch::Static
  • ActionDispatch::Executor
  • ActiveSupport::Cache::Strategy::LocalCache::Middleware
  • Rack::Runtime
  • ActionDispatch::RequestId
  • ActionDispatch::RemoteIp
  • Rails::Rack::Logger
  • ActionDispatch::ShowExceptions
  • ActionDispatch::DebugExceptions
  • ActionDispatch::Reloader
  • ActionDispatch::Callbacks
  • ActiveRecord::Migration::CheckPending
  • Rack::Head
  • Rack::ConditionalGet
  • Rack::ETag

詳しくは、Rackガイドの「Rails と Rack - ミドルウェアスタックの内容」をご覧ください。

ミドルウェアは、Active Recordなど他のプラグインによって追加されることがあります。一般に、構築するアプリの種類とミドルウェアは関係ありませんが、API専用Railsアプリでは意味があります。

アプリの全ミドルウェアを表示するには次のコマンドを使います。

$ rails middleware

4.1 キャッシュミドルウェアを使う

Railsにデフォルトで追加されるミドルウェアは、アプリケーションの設定に基づくキャッシュストア(デフォルトはmemcache)を提供します。このため、Railsに組み込まれているHTTPキャッシュはこのキャッシュストアに依存します。

たとえば、次のようにstale?メソッドを呼び出すとします。

def show
  @post = Post.find(params[:id])

  if stale?(last_modified: @post.updated_at)
    render json: @post
  end
end

stale?呼び出しは、リクエストにあるIf-Modified-Sinceヘッダと@post.updated_atを比較します。ヘッダが最終更新時より新しい場合、「304 Not Modified」を返すか、レスポンスをレンダリングしてLast-Modifiedヘッダをそこに表示します。

通常、この動作はクライアントごとに行われますが、キャッシュミドルウェアがあるとクライアント間でこのキャッシュを共有できるようになります。クロスクライアントキャッシュは、stale?の呼び出し時に有効にできます。

def show
  @post = Post.find(params[:id])

  if stale?(last_modified: @post.updated_at, public: true)
    render json: @post
  end 
end

キャッシュミドルウェアは、URLに対応するLast-Modified値をRailsキャッシュに保存し、以後同じURLへのリクエストを受信したときにIf-Modified-Sinceヘッダを追加します。

これは、HTTPセマンティクスを利用したページキャッシュと考えることができます。

4.2 Rack::Sendfileを使う

Railsコントローラ内部でsend_fileメソッドを実行すると、X-Sendfileヘッダが設定されます。実際のファイル送信を担当するのはRack::Sendfileです。

ファイル送信のアクセラレーションをサポートするフロントエンドサーバーでは、Rack::Sendfileの代わりにフロントエンドサーバーがファイルを送信します。

フロントエンドサーバーでのファイル送信に使うヘッダの名前は、該当する環境設定ファイルのconfig.action_dispatch.x_sendfile_headerで設定できます。

著名なフロントエンドでRack::Sendfileを使う方法について、詳しくは the Rack::Sendfile documentation をご覧ください。

定番のサーバーでファイル送信アクセラレーションを有効にするには、ヘッダに次のような値を設定します。

# Apacheやlighttpd
config.action_dispatch.x_sendfile_header = "X-Sendfile"

# Nginx
config.action_dispatch.x_sendfile_header = "X-Accel-Redirect"

これらのオプションを有効にするには、Rack::Sendfileドキュメントに従ってサーバーを設定してください。

4.3 ActionDispatch::Requestを使う

ActionDispatch::Request#paramsは、クライアントからのパラメータをJSON形式で受け取り、コントローラ内部のparamsでアクセスできるようにします。

この機能を使うには、JSONエンコード化したパラメータをクライアントから送信し、Content-Typeapplication/jsonを指定する必要があります。

jQueryでは次のように行います。

jQuery.ajax({
  type: 'POST',
  url: '/people',
  dataType: 'json',
  contentType: 'application/json',
  data: JSON.stringify({ person: { firstName: "Yehuda", lastName: "Katz" } }),
  success: function(json) { }
});

ActionDispatch::Requestでは、このContent-Typeで 次のパラメータを受け取ります。

{ :person => { :firstName => "Yehuda", :lastName => "Katz" } }

4.4 その他のミドルウェア

Railsではこの他にも、APIアプリ向けのミドルウェアを多数利用できます。特に、ブラウザがAPIクライアントになる場合は、次のミドルウェアが便利です。

  • Rack::MethodOverride
  • ActionDispatch::Cookies
  • ActionDispatch::Flash
  • セッション管理向け
    • ActionDispatch::Session::CacheStore
    • ActionDispatch::Session::CookieStore
    • ActionDispatch::Session::MemCacheStore

これらのミドルウェアは、次の方法で追加できます。

config.middleware.use Rack::MethodOverride

4.5 ミドルウェアを削除する

API専用ミドルウェアに含めたくないミドルウェアは、次の方法で削除できます。

config.middleware.delete ::Rack::Sendfile

これらのミドルウェアを削除すると、Action Controllerの一部の機能が利用できなくなりますので、ご注意ください。

5 コントローラモジュールを選択する

APIアプリケーション(ActionController::APIを利用)には、デフォルトで次のコントローラモジュールが含まれます。

  • ActionController::UrlFor: url_forなどのヘルパーを提供
  • ActionController::Redirecting: redirect_toをサポート
  • AbstractController::RenderingActionController::ApiRendering: 基本的なレンダリングのサポート
  • ActionController::Renderers::All: render :jsonなどのサポート
  • ActionController::ConditionalGet: stale?のサポート
  • ActionController::BasicImplicitRender: 指定がない限り、空のレスポンスを返す
  • ActionController::StrongParameters: パラメータのホワイトリストをサポート(Active Modelのマスアサインメントと連携)
  • ActionController::ForceSSL: force_sslのサポート
  • ActionController::DataStreaming: send_filesend_dataのサポート
  • AbstractController::Callbacks: before_actionなどのヘルパーをサポート
  • ActionController::Rescue: rescue_fromをサポート
  • ActionController::Instrumentation: Action Controllerで定義するinstrumentationフックをサポート(詳しくはthe instrumentation guide を参照)
  • ActionController::ParamsWrapper: パラメータハッシュをラップしてネスト化ハッシュにし、POSTリクエスト送信時のルート要素指定などを不要にする
  • ActionController::Head: コンテンツのないヘッダのみのレスポンスを返すのに用いる

他のプラグインによってモジュールが追加されることもあります。ActionController::APIの全モジュールのリストは、次のコマンドで表示できます。

$ bin/rails c
>> ActionController::API.ancestors - ActionController::Metal.ancestors
=> [ActionController::API,
    ActiveRecord::Railties::ControllerRuntime,
    ActionDispatch::Routing::RouteSet::MountedHelpers,
    ActionController::ParamsWrapper,
    ... ,
    AbstractController::Rendering,
    ActionView::ViewPaths]

5.1 その他のモジュールを追加する

Action Controllerのどのモジュールも、自身が依存するモジュールを把握しているので、コントローラにモジュールを含めるだけで、必要な依存モジュールも同様に設定できます。

よく追加されるのは、次のようなモジュールです。

  • AbstractController::Translation: ローカライズ用のlメソッドや、翻訳用のtメソッド
  • HTTPのBASIC認証、ダイジェスト認証、トークン認証
    • ActionController::HttpAuthentication::Basic::ControllerMethods
    • ActionController::HttpAuthentication::Digest::ControllerMethods
    • ActionController::HttpAuthentication::Token::ControllerMethods
  • ActionView::Layouts: レンダリングのレイアウトをサポート
  • ActionController::MimeResponds: respond_toをサポート
  • ActionController::Cookies: cookiesのサポート(署名や暗号化も含む)。cookiesミドルウェアが必要。

モジュールはApplicationControllerに追加するのが最適ですが、個別のコントローラに追加しても構いません。