本ガイドでは、Railsと[Rack][]の統合について説明します。
このガイドの内容:
Rackは、RubyでWebアプリケーションを開発するためのモジュール式インターフェイスを提供します。 HTTPリクエストとレスポンスをRackの規約に沿った構造でラップすることで、Webサーバー、Webフレームワーク、およびその間のソフトウェア(ミドルウェアと呼ばれます)のAPIを単一のメソッド呼び出しに統合します。
このしくみにより、PumaやFalconなどのRack準拠のWebサーバーを、RailsなどのRackベースのWebフレームワークで自由に差し替えられます。
RailsとRackの統合について詳しく知る前に、Rack自体について見てみましょう。
Rackアプリケーションは、callメソッドを実装するオブジェクトです。callメソッドには、Rack環境として知られるenvハッシュを渡します。
以下は、最小限のRackアプリケーションの例です。
class App def call(env) [200, { "content-type" => "text/plain" }, ["Hello World"]] end end run App.new
HTTPリクエストを受け取ると、Rack準拠のWebサーバーがリクエストを解析してenvハッシュを作成し、このenvを渡してRackアプリケーションを呼び出します。
callメソッドが返す配列は、HTTPレスポンスを表す以下の3つの要素のみを含んでいなければなりません。
200)Rackアプリケーションは、一般的にWebサーバーのコマンドラインプログラムで実行されます。
また、Rackアプリケーションのエントリポイントはconfig.ruファイルに格納されます。
$ cat > config.ru << APP rack_app = lambda do |env| [200, { "content-type" => "text/plain" }, ["Hello World"]] end run rack_app APP $ gem install puma $ puma
上のシェルコマンドを実行すると、Rackアプリが作成されてhttp://localhost:9292で起動します。
以下のコマンドで動作を確認できます。
$ curl localhost:9292 Hello World
Rackアプリケーションは、ミドルウェア(middleware)でラップできます。ミドルウェアは、リクエストがメインのアプリケーションに到達する直前と、メインのアプリケーションがリクエストに対してレスポンスを返した直後のどちらでも操作を実行できます。
ミドルウェアは通常、ログ出力、キャッシュ、認証、パフォーマンス計測などのタスクに利用されます。
Rackミドルウェアは、Rackアプリケーションと、ミドルウェアを設定するための任意の引数を受け取るnewメソッドを必ず持つ必要があります。newメソッドは、callに応答するRackアプリケーションを返す必要があります。
Rackミドルウェアはクラスとして書かれるのが普通で、ミドルウェアの各インスタンスは関連するアプリケーションへのアクセスをラップします。
class MyMiddleware def initialize(app) @app = app end def call(env) # リクエストがメインのアプリケーションに到達する直前の操作はここで行う # ------------------------------------------------------- # ミドルウェアスタックの下流にリクエストを伝播させる status, headers, body = @app.call(env) # --------------------------------------- # リクエストがアプリケーションから返された後の操作はここで行う # ミドルウェアスタックの上流にレスポンスを伝播させる [status, headers, body] end end
ミドルウェアは、必要に応じて@app.callを完全にスキップして自分自身でレスポンスを返すことで、ミドルウェアスタックの処理を打ち切る(ショートサーキット)ことが可能です。この場合、リクエストはメインのアプリケーションやスタック内の残りのミドルウェアに到達しなくなります。
リクエスト認証用のミドルウェアは、ショートサーキットを利用する場合があります。
class AuthenticateRequest def initialize(app) @app = app end def call(env) if authenticated?(env["HTTP_AUTHORIZATION"]) @app.call(env) else [401, { "content-type" => "text/plain" }, ["Authentication failed"]] end end def authenticated?(token) # ... end end
Rackアプリにミドルウェアを追加するには、useを使います。
class AuthenticateRequest # ... end class App def call(env) [200, { "content-type" => "text/plain" }, ["Hello World"]] end end use AuthenticateRequest run App.new
Rackアプリケーションを構築するためのこのようなDSLは、Rack::Builderによって提供されます。Rackについて詳しくは、Rackの仕様およびRackのWebサイトを参照してください。
Rails.applicationは、Railsアプリケーションにおける主要なRackアプリケーションオブジェクトです。
Rack準拠のWebサーバーは、Railsアプリケーションを提供するためにRails.applicationオブジェクトを使う必要があります。
Railsは、Rackup::Serverをサブクラス化する形でRails::Serverを作成します。
bin/rails serverコマンドを実行すると、Rails::Serverオブジェクトがインスタンス化されてWebサーバーが起動します。
Rails::Server.new.tap do |server| require APP_PATH Dir.chdir(Rails.application.root) server.start end
サーバーの起動方法について詳しくは、Railsの初期化プロセスガイドを参照してください。
ActionDispatch::MiddlewareStackは、RailsのRack::Builderに相当します。Railsの要件を満たすために、より柔軟で多くの機能を備えています。
Rails::Applicationオブジェクトは、このActionDispatch::MiddlewareStackを使って内部ミドルウェアと外部ミドルウェアを組み合わせ、Railsを使った完全なRackアプリケーションを構築します。
ミドルウェアスタックを表示するには、以下のコマンドを実行します。
$ bin/rails middleware
以下は、作成直後のRailsアプリケーションで表示されたミドルウェアスタックの例です。
use ActionDispatch::HostAuthorization use Rack::Sendfile use ActionDispatch::Static use Propshaft::Server use ActionDispatch::Executor use ActionDispatch::ServerTiming use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use ActionDispatch::RemoteIp use Propshaft::QuietAssets use Rails::Rack::Logger use ActionDispatch::ShowExceptions use WebConsole::Middleware use ActionDispatch::DebugExceptions use ActionDispatch::ActionableExceptions use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ContentSecurityPolicy::Middleware use Rack::Head use Rack::ConditionalGet use Rack::ETag use Rack::TempfileReaper run MyApp::Application.routes
上に示したデフォルトのミドルウェアの概要については、後述の内部ミドルウェアスタックを参照してください。
Railsが提供するconfig.middleware設定インターフェイスを用いることで、ミドルウェアスタックのミドルウェアを追加・削除・変更できます。これはapplication.rb設定ファイルで行うことも、環境ごとのenvironments/<環境名>.rb設定ファイルで行うことも可能です。
ミドルウェアスタックに新しいミドルウェアを追加するには、以下の3つのメソッドが利用できます。
config.middleware.use(new_middleware, args): ミドルウェアスタックの末尾に新しいミドルウェアを追加します。
config.middleware.insert_before(existing_middleware, new_middleware, args): 新しいミドルウェアを、(第1引数で)指定された既存のミドルウェアの直前に追加します。
config.middleware.insert_after(existing_middleware, new_middleware, args): 新しいミドルウェアを、(第1引数で)指定された既存のミドルウェアの直後に追加します。
利用例:
# config/application.rb # `Rack::BounceFavicon`を末尾に追加する config.middleware.use Rack::BounceFavicon # `ActionDispatch::Executor`の直後に`Lifo::Cache`を追加する。 # `Lifo::Cache`に`{ page_cache: false }`引数を渡す。 config.middleware.insert_after ActionDispatch::Executor, Lifo::Cache, page_cache: false
config.middleware.swapを使って、ミドルウェアスタック内にあるミドルウェアを置き換えられます。
# config/application.rb # ActionDispatch::ShowExceptionsをLifo::ShowExceptionsで置き換える config.middleware.swap ActionDispatch::ShowExceptions, Lifo::ShowExceptions
ミドルウェアスタック内の既存のミドルウェアを移動して順序を変更するには、config.middleware.move_beforeやconfig.middleware.move_afterを使います。
# config/application.rb # ActionDispatch::ShowExceptionsをLifo::ShowExceptionsの直前に移動する config.middleware.move_before Lifo::ShowExceptions, ActionDispatch::ShowExceptions
# config/application.rb # ActionDispatch::ShowExceptionsをLifo::ShowExceptionsの直後に移動する config.middleware.move_after Lifo::ShowExceptions, ActionDispatch::ShowExceptions
config.middleware.deleteを使ってミドルウェアを削除します。
# config/application.rb config.middleware.delete Rack::Runtime
delete!を使うと、指定したミドルウェアが存在しない場合にエラーが発生します。
# config/application.rb config.middleware.delete! Some::NonExistentMiddleware
ミドルウェアスタックが一度読み込まれると、以後の変更は監視されません。 ミドルウェアスタックに変更を加えた場合は、サーバーを再起動してください。
Action Controllerの機能の多くはミドルウェアとして実装されています。
それぞれのミドルウェアの目的について以下で説明します。
ActionDispatch::ActionableExceptionsActionDispatch::ActionableExceptionsミドルウェアは、リクエストがローカルの場合に、Railsのエラーページからアクションをディスパッチする方法を提供します。
ActionDispatch::CallbacksActionDispatch::Callbacksミドルウェアは、リクエストのディスパッチ前後に実行されるコールバックを提供します。
ActionDispatch::ContentSecurityPolicy::MiddlewareActionDispatch::ContentSecurityPolicy::Middlewareミドルウェアは、Content-Security-Policyヘッダを設定するためのDSLを提供します。詳しくはセキュリティガイドを参照してください。
ActionDispatch::CookiesActionDispatch::Cookiesミドルウェアは、リクエストからCookieデータを読み取り、レスポンスにCookieデータを書き込みます。
ActionDispatch::DebugExceptionsActionDispatch::DebugExceptionsミドルウェアは、例外をログに記録し、リクエストがローカルの場合にデバッグ用のページを表示します。
ActionDispatch::ExecutorActionDispatch::Executorミドルウェアは、development環境でコードがスレッドセーフで再読み込みされることを保証します。
ActionDispatch::FlashActionDispatch::Flashミドルウェアは、flashのキーを設定します。config.session_storeに何らかの値が設定されている場合にのみ利用可能です。
ActionDispatch::HostAuthorizationActionDispatch::HostAuthorizationミドルウェアは、DNSリバインディング攻撃を防ぐために、リクエストの送信先として許可されるホストを制限します。設定方法については設定ガイドを参照してください。
ActionDispatch::ReloaderActionDispatch::Reloaderミドルウェアは、development環境でのコード自動再読み込みを支援するために、prepareコールバックとcleanupコールバックを提供します。
ActionDispatch::RemoteIpActionDispatch::RemoteIpミドルウェアは、IPスプーフィング攻撃をチェックします。
ActionDispatch::RequestIdActionDispatch::RequestIdミドルウェアは、リクエストに一意のX-Request-Idヘッダを設定し、ActionDispatch::Request#request_idメソッドを利用可能にします。
一意のリクエストIDは、リクエストのエンドツーエンドのトラッキングに利用できます。 通常は、スタックを構成する複数のコンポーネントのログファイルに記録されます。
ActionDispatch::ServerTimingActionDispatch::ServerTimingミドルウェアは、リクエストのパフォーマンス指標を含むServer-Timingヘッダを設定します。
ActionDispatch::Session::CookieStoreActionDispatch::Session::CookieStoreミドルウェアは、セッションをCookieに保存する役割を担当します。
ActionDispatch::ShowExceptionsActionDispatch::ShowExceptionsミドルウェアは、アプリケーションから返された例外をキャッチし、エンドユーザー向けの形式でラップする例外アプリを呼び出します。
ActionDispatch::StaticActionDispatch::Staticミドルウェアは、publicフォルダの静的ファイルを配信します。
config.public_file_server.enabledがfalseの場合は無効化されます。
ActiveRecord::Migration::CheckPendingActiveRecord::Migration::CheckPendingミドルウェアは、保留中のマイグレーションをチェックし、保留中のマイグレーションがある場合はActiveRecord::PendingMigrationErrorを発生させます。
config.active_record.migration_errorが:page_loadに設定されている場合にのみ有効です。
ActiveSupport::Cache::Strategy::LocalCache::MiddlewareActiveSupport::Cache::Strategy::LocalCache::Middlewareミドルウェアは、インメモリのローカルキャッシュ用のミドルウェアです。このキャッシュはスレッドセーフではなく、単一のスレッドの一時的なメモリキャッシュとしてのみ使われます。
Propshaft::QuietAssetsPropshaft::QuietAssetsミドルウェアは、アセットリクエストのログ出力を抑制します。
Rack::ConditionalGetRack::ConditionalGetミドルウェアは、if-none-matchおよびif-modified-sinceによる「条件付きGET」リクエストを処理します。
リクエストされたページが変更されていない場合、304 Not Modifiedを返し、bodyは空になります。
Rack::ETagRack::ETagミドルウェアは、すべての文字列bodyにETagヘッダを追加します。ETagはキャッシュのバリデーションに使われ、上記の「条件付きGET」リクエストで利用されます。
詳しくはキャッシュのガイドを参照してください。
Rack::HeadRack::Headミドルウェアは、すべてのHEADリクエストに対して空のbodyを返します。それ以外のリクエストは変更されません。
Rack::LockRack::Lockミドルウェアは、すべてのリクエストをミューテックス内でロックするため、すべてのリクエストは実質的に同期的に実行されます。
Rack::MethodOverrideRack::MethodOverrideミドルウェアは、params[:_method]が設定されている場合にHTTPメソッドをオーバーライドできるようにします。これは、ブラウザがネイティブにサポートしていないPUT、PATCH、DELETE HTTPメソッドをRailsでサポートする方法です。
Rack::RuntimeRack::Runtimeミドルウェアは、X-Runtimeヘッダを設定します。このヘッダには、リクエストの実行にかかった時間(秒単位)が含まれます。
Rack::SendfileRack::Sendfileミドルウェアは、サーバー固有のX-Sendfileヘッダを設定します。
これは、ApacheやNginxのようなリバースプロキシサーバーを利用している場合に、ファイル送信を高速化するのに役立ちます。たとえば、Apacheの場合はX-Sendfileに設定できます。これはconfig.action_dispatch.x_sendfile_headerオプションで設定します。
Rack::TempfileReaperRack::TempfileReaperミドルウェアは、マルチパートリクエストをバッファリングするための一時ファイルをクリーンアップします。
Rails::Rack::LoggerRails::Rack::Loggerミドルウェアは、リクエストの開始をログに通知します。リクエストが完了すると、すべてのログをフラッシュします。
これらのミドルウェアはいずれも、カスタムRackスタックで利用することも可能です。
独自のミドルウェアを作成してRailsアプリに組み込むことも可能です。
カスタムミドルウェアは、lib/フォルダに配置したうえで、手動でrequireする必要があります(ミドルウェアは自動再読み込みされないため)。
以下の例では、URLパラメータからlocaleの値を読み取り、Rackのenvに保存してから、localeをクエリパラメータから削除します。
これにより、リクエストがRailsコントローラに到達したときにparamsハッシュにlocaleが含まれなくなり、パラメータをシンプルに保てます。
# lib/middleware/extract_locale.rb module RackMiddleware class ExtractLocale def initialize(app) @app = app end def call(env) request = ActionDispatch::Request.new(env) if request.params["locale"].present? env["myapp.locale"] = env["action_dispatch.request.query_parameters"]["locale"] env["action_dispatch.request.query_parameters"].delete("locale") env["action_dispatch.request.parameters"].delete("locale") end @app.call(env) end end end
Railsは、lib/middleware/フォルダをデフォルトで作成しないため、自分で作成する必要があります。
自動読み込みの問題を避けるため、このフォルダはautoloadパスに含めないことが推奨されます。
# config/application.rb module MyApp class Application < Rails::Application # ... config.autoload_lib(ignore: %w[assets tasks middleware]) # ... end end
カスタムミドルウェアは、以下のようにconfig/application.rbファイルに追加できます。
# config/application.rb # ... require_relative "../lib/middleware/extract_locale" module MyApp class Application < Rails::Application # ... config.middleware.use RackMiddleware::ExtractLocale # ... end end
または、以下のように単独のイニシャライザにも追加できます。
# config/initializers/extract_locale.rb require "#{Rails.root.join("lib", "middleware", "extract_locale")}" Rails.application.config.middleware.use RackMiddleware::ExtractLocale
基盤となるRack APIは、Railsコントローラ内で利用できます。
envにアクセスするRailsのコントローラ内では、request.envでRackのenvハッシュにアクセスできます。
class HomeController def index user_agent = request.env["HTTP_USER_AGENT"] # ... end end
Railsのコントローラ内では、以下の方法でRackのレスポンスを直接書き込めます。
class HomeController def index self.response = Rack::Response[200, {}, ["I'm Home!"]] end end
config/routes.rbファイルでリクエストをRackアプリにルーティングできます。
詳しくはルーティングガイドを参照してください。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。もしご興味あれば、協賛プランから気軽にお問い合わせいただけると嬉しいです。