こんにちは、ギフティでエンジニアをしているshiraiです。 私は普段、バックエンドにRuby on Railsを採用したtoC向けプロダクトの開発を担当しています。
はじめに
早速ですがRailsでアプリケーションを動かすとき、内部でHTTPリクエスト/レスポンスがどんな仕組みで処理されているか疑問に思ったことはありませんか? その答えの一部がRackというライブラリにあります。
私自身はRailsに携わり始めてからまだ半年と日が浅く、いわゆるRailsビギナーです。そんな中で先日のKaigi on Rails 2024において「Rackを理解しRailsアプリケーション開発の足腰を鍛えよう」ワークショップに参加してきました。その際にRackというものを初めてちゃんと意識し、RackがRailsにおいて非常に重要な役割を果たしていることを学びました。
ただ意識しないとなかなか普段の業務の中では見過ごされてしまうものなので、改めてこの記事ではRackというものがどのようなものなのか自分自身の振り返りも兼ねて、ご紹介できればと思います。
Rackというライブラリを理解することでRails開発の色々な場面で役立ていただけると思います。
この記事で紹介すること・しないこと
この記事では以下のような事項を紹介します。主に私のようなRailsを触り始めてまだ比較的日が浅い方向けの記事となっています。
- Rackがどのようなものか
- RailsにおいてRackがどのような役割をになっているか
- Rackを学ぶことで開発者としてどのようなメリットがあるか
逆にRackそのものの実装やRailsにおけるRack実装部分を詳細に紹介することはこの記事ではしません。そのような情報を参照したい方はRackリポジトリやRailsガイドをご確認いただければと思います。
Rackの概要
Rackとは何か
RackリポジトリのREADMEには以下の記載があります。
Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the bridge between web servers, web frameworks, and web application into a single method call.
簡単にまとめると、RackとはRubyでWebアプリケーションを開発する際のアプリケーションサーバとフレームワーク間で統一されたインターフェースを提供するライブラリのことです。このインターフェースが統一されているというところがポイントで、Rackインターフェースに準拠していればフレームワーク(RailsやSinatraなど)とアプリケーションサーバ(PumaやUnicornなど)は疎結合となり、それぞれの種類に依らず柔軟に組み合わせることが可能となっています。
Rackインターフェース
Rackインターフェースはとてもシンプルで基本構造および定義は以下のようになっています。
def call(env) [status, headers, body] end
call
というメソッドを定義するcall
メソッドは慣例的にenv
あるいはenviroment
と命名する引数を受け取るcall
メソッドは次の値を配列型で戻り値として返す必要がある- HTTPステータスコードを表す数値オブジェクト
- 例)
200
- 例)
- HTTPヘッダーを表すハッシュオブジェクト
- 例)
{"content-type" => "text/plain"}
- 例)
- レスポンスボディとなる文字列を含んだ配列風オブジェクト
- 例)
["Hello Rack!"]
- 例)
- HTTPステータスコードを表す数値オブジェクト
どのようなアプリケーションにおいても最終的にこのインターフェースに準拠していれば、Rackに準拠していることになります。RailsやSinatraなどのフレームワークも最終的にはこのようなシンプルなインターフェースに収束しています。
このRackインターフェースに準拠したアプリケーションをRackアプリケーションと呼びます。例えば以下は非常にシンプルなRackアプリケーションであるSampleApp(sample_app.rb)の実装例です。
class SampleApp def call(env) status = 200 headers = {"content-type" => "text/plain"} body = ["Hello Rack!"] [status, headers, body] end end
このRackアプリケーションを起動するためには一般的にconfig.ru
というファイルに以下のような記載をします。
require "rack" require_relative "sample_app" run SampleApp.new
この状態でRackアプリケーションを起動するrackup
コマンドを使うことで、config.ru
が読み込まれ、Rackアプリケーションが起動します。
その状態でhttp://localhost:9292
にアクセスすると、ブラウザにレスポンスボディである「Hello Rack!」が表示されます。
非常にシンプルですね!
ちなみにRailsにおいてもrails new
した時にディレクトリ直下にconfig.ru
が自動生成されています。
Rackミドルウェア
さらに、RackにはRackミドルウェアというHTTPリクエストとレスポンスの間に処理を挟むことができる機構があります。この機構を利用することで認証、ロギング、セッション管理などのアプリケーション全体で共通的に利用される機能をRackに独自に実装することできるようになります。またそもそも認証、ロギング、セッション管理などのよく使われる機能についてはRackミドルウェアがライブラリとしてすでに提供してくれているので、そちらを使用することも可能となっています。
Rackミドルウェアは以下のような基本構造および定義になっています。
class SampleMiddleware def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) return [status, headers, body] end end
initialize
メソッドに引数を1つ取る- Rackアプリケーションと同じインターフェースの引数を取る
以下は例としてレスポンスボディの中身を大文字にするRackミドルウェア(sample_middleware.rb)の実装例です。
class SampleMiddleware def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) body.map{|part| part.upcase! } return [status, headers, body] end end
このRackミドルウェアを使用するためにはconfig.ru
においてuse
メソッドを使用します。
require "rack" require_relative "sample_app" require_relative "sample_middleware" use SampleMiddleware run SampleApp.new
これでrackup
コマンドを実行し、http://localhost:9292
にアクセスすると、レスポンスボディが全て大文字になります。
このようにアプリケーション全体で共通処理を実装する場合にRackミドルウェアは非常に有効な手となります。目立たないところで大きな活躍をしてくれており、縁の下の力持ち感がありますね!
RailsにおけるRackの役割
続いてRailsとRackの関係をみていきたいと思います。上記で少し述べましたが、RailsもRackインターフェースに準拠しています。したがってフレームワークにRailsを使用しているアプリケーションは基本的に全てRackアプリケーションとして定義されます。
それを踏まえて、RailsアプリケーションにおけるHTTPリクエスト/レスポンス処理の流れは以下のようになっています。
- ブラウザ
- ブラウザからHTTPリクエストが送信されます。
- Webサーバ(NginxやApache等)
- ブラウザからのHTTPリクエストを受け取り、アプリケーションサーバに渡します。
- アプリケーションサーバ(PumaやUnicorn等)
- WebサーバからHTTPリクエストを受け取り、Rackに渡します。
- Rack
- HTTPリクエストをRailsアプリケーションが理解できる形に変換して、Railsアプリケーションに渡します。Rackミドルウェアはここに介入して、ログ記録やセッション管理等を行います。
- Railsアプリケーション
- コントローラーで処理を行い、結果を返します。
- Rack
- Railsアプリケーションの結果をHTTPレスポンスとして加工し、アプリケーションサーバ(PumaやUnicorn等)に返します。
- アプリケーションサーバ(PumaやUnicorn等)
- WebサーバにHTTPレスポンスを返します。
- Webサーバ(NginxやApache等)
- ブラウザにHTTPレスポンスを返します。
- ブラウザ
- HTTPレスポンスを表示します。
Rackを学ぶことで開発者としてどのようなメリットがあるか
ここまで簡易的ではありますが、RackそのものとRailsにおけるRackの役割を見てきました。ここからはRackを学ぶことで開発者としてどのようなメリットがあるかを考えていきたいと思います。私自身は特に次のメリットが大きいと考えています。
Rackミドルウェアを活用できるようになることでアプリケーション全体に及ぼす共通処理をRack層に柔軟に実装できるようになる
RailsにはRack::Sendfile、Rack::Runtime、Rack::MethodOverrideなど多くのRackミドルウェアが活用されています。ただそれ以外にも多くのRackミドルウェアが存在しており、アプリケーション全体で共通的に利用される機能においてはRackミドルウェアとして提供されているライブラリを活用することで実装コストをその分だけ抑えたりできます。例えばBasic認証などの複雑な制御が必要ではない認証機構やセッション機構をアプリケーションに導入するとなった場合にはRack::Auth::BasicやRack::Session系を使用することで簡単に機能を実現することができます。また必要に応じて自分自身でRackミドルウェアとして処理を実装することで、既存アプリケーションに対しても柔軟に共通機能を追加することができます。
結果としてRailsアプリケーションのコントローラーやモデルに認証管理やHTTPリクエストのロギング処理といった共通処理を書かずに済むので、コードが読みやすくなり、保守性も向上します。また、それらの共通処理を別のアプリケーションでも簡単に再利用できるようになります。
まとめ
本記事ではRailsにおいて非常に重要な役割を果たしているRackについて紹介しました。 本記事を通じてRackに少しでも興味を持っていただけたなら幸いです。
ちなみに私がRackを知るきっかけとなった上記のKaigi on Rails2024でのワークショップ資料はhogelogさんのGitHubリポジトリにまとまっています。ハンズオン形式のワークショップであり、リポジトリの資料を見ながら簡単にハンズオンを開始することができるので、興味のある方はぜひやってみてください。今回の記事で記載した内容をより深く理解していただけると思います。私も今後の開発ではRackミドルウェア中心に活用できる部分はどんどん活用していきたいと思っています!
ここまで読んでいただきありがとうございました!皆さんも素敵なRails生活を送ってください〜!Good Rack!(これが言いたかっただけ)