giftee Tech Blog

ギフティの開発を支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信しています。

Ruby on RailsビギナーがRackについて学んでみた!

image_name

こんにちは、ギフティでエンジニアをしている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!"]

どのようなアプリケーションにおいても最終的にこのインターフェースに準拠していれば、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!」が表示されます。 image_name 非常にシンプルですね!

ちなみに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にアクセスすると、レスポンスボディが全て大文字になります。

image_name

このようにアプリケーション全体で共通処理を実装する場合にRackミドルウェアは非常に有効な手となります。目立たないところで大きな活躍をしてくれており、縁の下の力持ち感がありますね!

RailsにおけるRackの役割

続いてRailsとRackの関係をみていきたいと思います。上記で少し述べましたが、RailsもRackインターフェースに準拠しています。したがってフレームワークにRailsを使用しているアプリケーションは基本的に全てRackアプリケーションとして定義されます。

それを踏まえて、RailsアプリケーションにおけるHTTPリクエスト/レスポンス処理の流れは以下のようになっています。

image_name

  1. ブラウザ
    • ブラウザからHTTPリクエストが送信されます。
  2. Webサーバ(NginxやApache等)
    • ブラウザからのHTTPリクエストを受け取り、アプリケーションサーバに渡します。
  3. アプリケーションサーバ(PumaやUnicorn等)
    • WebサーバからHTTPリクエストを受け取り、Rackに渡します。
  4. Rack
    • HTTPリクエストをRailsアプリケーションが理解できる形に変換して、Railsアプリケーションに渡します。Rackミドルウェアはここに介入して、ログ記録やセッション管理等を行います。
  5. Railsアプリケーション
    • コントローラーで処理を行い、結果を返します。
  6. Rack
    • Railsアプリケーションの結果をHTTPレスポンスとして加工し、アプリケーションサーバ(PumaやUnicorn等)に返します。
  7. アプリケーションサーバ(PumaやUnicorn等)
    • WebサーバにHTTPレスポンスを返します。
  8. Webサーバ(NginxやApache等)
    • ブラウザにHTTPレスポンスを返します。
  9. ブラウザ
    • HTTPレスポンスを表示します。

Rackを学ぶことで開発者としてどのようなメリットがあるか

ここまで簡易的ではありますが、RackそのものとRailsにおけるRackの役割を見てきました。ここからはRackを学ぶことで開発者としてどのようなメリットがあるかを考えていきたいと思います。私自身は特に次のメリットが大きいと考えています。

Rackミドルウェアを活用できるようになることでアプリケーション全体に及ぼす共通処理をRack層に柔軟に実装できるようになる

RailsにはRack::Sendfile、Rack::Runtime、Rack::MethodOverrideなど多くのRackミドルウェアが活用されています。ただそれ以外にも多くのRackミドルウェアが存在しており、アプリケーション全体で共通的に利用される機能においてはRackミドルウェアとして提供されているライブラリを活用することで実装コストをその分だけ抑えたりできます。例えばBasic認証などの複雑な制御が必要ではない認証機構やセッション機構をアプリケーションに導入するとなった場合にはRack::Auth::BasicRack::Session系を使用することで簡単に機能を実現することができます。また必要に応じて自分自身でRackミドルウェアとして処理を実装することで、既存アプリケーションに対しても柔軟に共通機能を追加することができます。

結果としてRailsアプリケーションのコントローラーやモデルに認証管理やHTTPリクエストのロギング処理といった共通処理を書かずに済むので、コードが読みやすくなり、保守性も向上します。また、それらの共通処理を別のアプリケーションでも簡単に再利用できるようになります。

まとめ

本記事ではRailsにおいて非常に重要な役割を果たしているRackについて紹介しました。 本記事を通じてRackに少しでも興味を持っていただけたなら幸いです。

ちなみに私がRackを知るきっかけとなった上記のKaigi on Rails2024でのワークショップ資料はhogelogさんのGitHubリポジトリにまとまっています。ハンズオン形式のワークショップであり、リポジトリの資料を見ながら簡単にハンズオンを開始することができるので、興味のある方はぜひやってみてください。今回の記事で記載した内容をより深く理解していただけると思います。私も今後の開発ではRackミドルウェア中心に活用できる部分はどんどん活用していきたいと思っています!

ここまで読んでいただきありがとうございました!皆さんも素敵なRails生活を送ってください〜!Good Rack!(これが言いたかっただけ

参考・引用