基本

Rails Rackミドルウェア入門|リクエスト処理の仕組みを理解する

Ruby on Rails Rack ミドルウェア

Rails Rackミドルウェア入門
リクエスト処理の仕組みを理解する

RackミドルウェアのアーキテクチャとRailsでの活用方法を解説。カスタムミドルウェアの作成からロギング・CORS対応まで学べます。

こんな人向けの記事です

  • Railsのリクエスト処理の仕組みを理解したい
  • Rackミドルウェアとは何か知りたい
  • カスタムミドルウェアを作成したい

Step 1Rackとは何か

Rackは、RubyのWebサーバーとWebアプリケーションフレームワークをつなぐインターフェース仕様です。Puma、Unicornなどのサーバーと、Rails、Sinatraなどのフレームワークが、共通のプロトコルで通信できるようにします。

ポイント

Rackの本質は「callメソッドを持つオブジェクト」というシンプルな規約です。引数に環境変数のハッシュ(env)を受け取り、[ステータスコード, ヘッダー, ボディ]の3要素の配列を返します。

最もシンプルなRackアプリケーションは以下のとおりです。

config.ru
# 最小のRackアプリケーション
app = Proc.new do |env|
  [200, { "Content-Type" => "text/plain" }, ["Hello, Rack!"]]
end

run app

このファイルをconfig.ruとして保存し、rackupコマンドで起動できます。

ターミナル
$ gem install rack
$ rackup config.ru
# => Puma starting on http://localhost:9292

Rackの役割を図で表すと次のようになります。

役割
Webサーバー HTTPリクエストの受信・レスポンスの送信 Puma, Unicorn, Falcon
Rack サーバーとアプリの橋渡し(インターフェース) Rack仕様
フレームワーク ルーティング・コントローラー・ビュー処理 Rails, Sinatra, Hanami

Step 2Rackミドルウェアの仕組み

Rackミドルウェアは、リクエストとレスポンスの間に挟まるフィルターのような存在です。複数のミドルウェアが順番に処理を行い、最終的にアプリケーション本体に到達します。

ポイント

ミドルウェアは「玉ねぎの層」に例えられます。リクエストは外側から内側へ、レスポンスは内側から外側へ通過します。各層が独立した処理を担当します。

ミドルウェアの基本構造は以下のとおりです。

lib/middleware/example_middleware.rb
class ExampleMiddleware
  def initialize(app)
    @app = app  # 次のミドルウェアまたはアプリ本体
  end

  def call(env)
    # === リクエスト処理(前処理) ===
    # ここでリクエストを加工・検査できる

    status, headers, body = @app.call(env)  # 次に処理を渡す

    # === レスポンス処理(後処理) ===
    # ここでレスポンスを加工できる

    [status, headers, body]
  end
end

ミドルウェアチェーンの処理の流れを整理します。

処理の流れ
リクエスト
  │
  ▼
[ミドルウェアA] 前処理 → @app.call(env)
  │                         │
  │                         ▼
  │               [ミドルウェアB] 前処理 → @app.call(env)
  │                         │                  │
  │                         │                  ▼
  │                         │            [Railsアプリ本体]
  │                         │                  │
  │                         │                  ▼
  │               [ミドルウェアB] 後処理 ← レスポンス
  │                         │
  ▼
[ミドルウェアA] 後処理 ← レスポンス
  │
  ▼
レスポンス

Step 3Railsのミドルウェアスタック

Railsはデフォルトで多くのミドルウェアを組み込んでいます。現在のミドルウェアスタックは以下のコマンドで確認できます。

ターミナル
$ bin/rails middleware

代表的なRailsミドルウェアの一覧です。

ミドルウェア 役割
ActionDispatch::HostAuthorization 許可されたホストからのリクエストのみ通す
Rack::Sendfile 静的ファイルの効率的な配信
ActionDispatch::Executor スレッドセーフなリクエスト処理
ActiveSupport::Cache::Strategy::LocalCache::Middleware リクエストごとのキャッシュ管理
Rack::Runtime X-Runtimeヘッダーの付与(処理時間)
ActionDispatch::RequestId リクエストにユニークIDを付与
Rails::Rack::Logger リクエストのログ出力
ActionDispatch::ShowExceptions 例外のHTMLレスポンス変換
ActionDispatch::Cookies Cookie の読み書き
ActionDispatch::Session::CookieStore セッション管理
ActionDispatch::Flash Flashメッセージ
Rack::Head HEADリクエストへの対応
Rack::ConditionalGet 条件付きGET(304レスポンス)
Rack::ETag ETagヘッダーの自動付与

注意: Railsのバージョンによってデフォルトのミドルウェア構成は変わります。bin/rails middlewareで必ず自分の環境を確認してください。

Step 4カスタムミドルウェアの作成

独自のミドルウェアを作成してみましょう。まず、リクエストの処理時間を計測するシンプルなミドルウェアを作ります。

lib/middleware/request_timer.rb
class RequestTimer
  def initialize(app)
    @app = app
  end

  def call(env)
    start_time = Time.now

    status, headers, body = @app.call(env)

    duration = ((Time.now - start_time) * 1000).round(2)
    headers["X-Request-Duration"] = "#{duration}ms"

    Rails.logger.info "[RequestTimer] #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]} - #{duration}ms"

    [status, headers, body]
  end
end

作成したミドルウェアをRailsに登録します。

config/application.rb
require_relative "../lib/middleware/request_timer"

module MyApp
  class Application < Rails::Application
    # ミドルウェアスタックの末尾に追加
    config.middleware.use RequestTimer
  end
end

次に、特定のIPアドレスをブロックするミドルウェアの例です。

lib/middleware/ip_blocker.rb
class IpBlocker
  BLOCKED_IPS = %w[192.168.1.100 10.0.0.50].freeze

  def initialize(app)
    @app = app
  end

  def call(env)
    client_ip = env["REMOTE_ADDR"]

    if BLOCKED_IPS.include?(client_ip)
      [403, { "Content-Type" => "text/plain" }, ["Forbidden"]]
    else
      @app.call(env)
    end
  end
end
ポイント

ミドルウェア内で@app.call(env)を呼ばずに直接レスポンスを返すと、それ以降のミドルウェアやアプリ本体は実行されません。認証やアクセス制御に便利なパターンです。

Step 5ミドルウェアの追加・削除・順序変更

Railsではconfig.middlewareを使ってミドルウェアスタックを柔軟に操作できます。

追加方法

config/application.rb
# スタックの末尾に追加
config.middleware.use MyMiddleware

# 特定のミドルウェアの前に追加
config.middleware.insert_before ActionDispatch::Cookies, MyMiddleware

# 特定のミドルウェアの後に追加
config.middleware.insert_after Rails::Rack::Logger, MyMiddleware

削除方法

config/application.rb
# ミドルウェアを削除
config.middleware.delete ActionDispatch::Flash

# ミドルウェアを入れ替え
config.middleware.swap ActionDispatch::Flash, MyCustomFlash

順序変更

config/application.rb
# 先頭に移動(最初に実行される)
config.middleware.move_before 0, MyMiddleware

# 特定のミドルウェアの前に移動
config.middleware.move_before ActionDispatch::Cookies, MyMiddleware

# 特定のミドルウェアの後に移動
config.middleware.move_after Rails::Rack::Logger, MyMiddleware

各メソッドの使い分けを整理します。

メソッド 用途
use スタックの末尾に追加
insert_before 指定ミドルウェアの前に追加
insert_after 指定ミドルウェアの後に追加
delete 指定ミドルウェアを削除
swap 指定ミドルウェアを別のものに入れ替え
move_before 指定位置の前に移動
move_after 指定位置の後に移動

注意: セッションやCookieなど、他のミドルウェアに依存するものを削除すると動作しなくなる場合があります。削除する前に依存関係を確認してください。

Step 6実践例(ロギング・認証・CORS対応)

1. 詳細なリクエストロギング

リクエストとレスポンスの詳細をJSON形式でログに出力するミドルウェアです。

lib/middleware/json_logger.rb
class JsonLogger
  def initialize(app)
    @app = app
  end

  def call(env)
    request = Rack::Request.new(env)
    start_time = Time.now

    status, headers, body = @app.call(env)

    duration = ((Time.now - start_time) * 1000).round(2)

    log_data = {
      timestamp: Time.now.iso8601,
      method: request.request_method,
      path: request.path,
      query: request.query_string,
      status: status,
      duration_ms: duration,
      ip: request.ip,
      user_agent: request.user_agent
    }

    Rails.logger.info log_data.to_json

    [status, headers, body]
  end
end

2. APIトークン認証

API用のトークン認証を行うミドルウェアです。/apiパスへのリクエストにのみ適用します。

lib/middleware/api_token_auth.rb
class ApiTokenAuth
  def initialize(app)
    @app = app
  end

  def call(env)
    request = Rack::Request.new(env)

    # /api パス以外はスルー
    unless request.path.start_with?("/api")
      return @app.call(env)
    end

    token = env["HTTP_AUTHORIZATION"]&.gsub(/^Bearer /, "")

    if token.nil? || token.empty?
      return [
        401,
        { "Content-Type" => "application/json" },
        [{ error: "Authorization token required" }.to_json]
      ]
    end

    unless valid_token?(token)
      return [
        403,
        { "Content-Type" => "application/json" },
        [{ error: "Invalid token" }.to_json]
      ]
    end

    @app.call(env)
  end

  private

  def valid_token?(token)
    # 実際にはDBやRedisでトークンを検証
    ApiToken.exists?(token: token, active: true)
  end
end

3. CORS対応

フロントエンドとバックエンドを分離している場合に必要なCORS(Cross-Origin Resource Sharing)対応のミドルウェアです。

lib/middleware/cors_handler.rb
class CorsHandler
  ALLOWED_ORIGINS = %w[
    http://localhost:3001
    https://frontend.example.com
  ].freeze

  def initialize(app)
    @app = app
  end

  def call(env)
    origin = env["HTTP_ORIGIN"]

    # プリフライトリクエスト(OPTIONS)への対応
    if env["REQUEST_METHOD"] == "OPTIONS"
      return preflight_response(origin)
    end

    status, headers, body = @app.call(env)

    if ALLOWED_ORIGINS.include?(origin)
      headers["Access-Control-Allow-Origin"] = origin
      headers["Access-Control-Allow-Credentials"] = "true"
    end

    [status, headers, body]
  end

  private

  def preflight_response(origin)
    headers = {
      "Access-Control-Allow-Origin" => origin,
      "Access-Control-Allow-Methods" => "GET, POST, PUT, PATCH, DELETE, OPTIONS",
      "Access-Control-Allow-Headers" => "Content-Type, Authorization, X-Requested-With",
      "Access-Control-Max-Age" => "86400"
    }

    [204, headers, []]
  end
end

これらのミドルウェアを登録する例です。

config/application.rb
require_relative "../lib/middleware/json_logger"
require_relative "../lib/middleware/api_token_auth"
require_relative "../lib/middleware/cors_handler"

module MyApp
  class Application < Rails::Application
    # CORSは最も外側(最初)に配置
    config.middleware.insert_before 0, CorsHandler

    # ロギングはRails::Rack::Loggerの後に配置
    config.middleware.insert_after Rails::Rack::Logger, JsonLogger

    # API認証はルーティングの前に配置
    config.middleware.use ApiTokenAuth
  end
end
ポイント

本番環境でのCORS対応にはrack-cors gemの利用も検討してください。Gemfilegem "rack-cors"を追加するだけで、より堅牢なCORS設定が可能になります。

まとめ

この記事で学んだこと

  • RackはWebサーバーとアプリケーションを繋ぐインターフェース仕様
  • ミドルウェアはinitialize(app)call(env)を持つシンプルなクラス
  • Railsにはデフォルトで多数のミドルウェアが組み込まれている
  • config.middlewareで追加・削除・順序変更が可能
  • ロギング、認証、CORS対応など横断的な処理に最適
  • ミドルウェアの配置順序がアプリケーションの動作に影響する