Rackとは
Rubyにおいて、アプリケーションサーバ(Puma、Unicornなど
)とWebアプリケーションやフレームワーク(Rails、Sinatraなど
)を接続するための標準化インターフェース(規約)です。
これらはRackの規約に則ることで、さまざまな組み合わせでも動作します。
公式にサポートしてあるアプリケーションサーバとWebアプリケーションが記載してあります。
ある記事ではRackを共通言語と説明していました。こちらの方がわかりやすいですね。
RackはRailsのようなRuby製のwebフレームワークとアプリケーションサーバーの両方が話せる共通言語のようなものだと考えてください。
引用;Rails開発におけるwebサーバーとアプリケーションサーバーの違い(翻訳)
Webアプリケーション側のRackの規約
Rackに対応するWebアプリケーションやフレームワークはこの規約に則ったインターフェイスを定義する必要があります。
・callメソッドを定義する
引用:パーフェクトRuby on Rails【増補改訂版】
・callメソッドはenvあるいはenviromentと命名する引数を一つ受け取る
・callメソッドは次の値を配列型で戻り値として返す
・HTTPのステータスコードを表す数値オブジェクト(status)
・HTTPヘッダーを表すハッシュオブジェクト(headers)
・レスポンスボディとなる文字列を含んだ配列風オブジェクト(body)
わかりやすくコードで表すと、
def call(env)
[status, headers, body]
end
どんなアプリケーションでも最終的にcallメソッドで[status, header, body]
の値を返すことができれば、Rackの規約に則ったアプリケーションとなります。
もっと具体的な例で表すと、
class SampleApp
def call(env)
status = 200
headers = {"Content-Type" => "text/plain"}
body = ["Hello, World"]
[status, headers, body]
end
end
上記のコードはRackの規約に則っているのでRackアプリケーションと言えます。
では上記のRackアプリケーションを実施に動かしてみます。
Rackアプリケーションを作成する
まず、Rackをインストールしてます。
$ gem install rack
Rackアプリケーションを作成するためのファイル(config.ru
)を作成します。
require 'rack'
require_relative 'sample_app'
run SampleApp.new
先ほどのsample_app.rb
を読み込みます。SampleApp
のオブジェクトをrun
メソッドに渡します。
run
メソッドについては後述する記事を参考にしてください。
次にrackup
コマンドを実行します。
$ rackup
Puma starting in single mode...
* Version 4.3.10 (ruby 2.7.5-p203), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:9292
* Listening on tcp://[::1]:9292
Use Ctrl-C to stop
rackup
コマンドを実行すると、Rackアプリケーションが起動し、サーバが立ち上がります。rackup
はRack::Sever.start
メソッド実行しています。
rackup
ではpart9292番になるので、localhost:9292
へアクセスします。

ステータスコードが200
、レスポンスヘッダーのContent-Type: text/plain
、Hello, World
の記述があるので、sample_app.rb
で定義したcall
メソッドの戻り値[status, headers, body]
がレスポンスとして返されているのが分かります。
これらの動作から、call
メソッドは引数(env)
にアプリケーションサーバーからHTTPリクエストに関するデータを受け取り、戻り値として[status, headers, body]
を含むHTTPレスポンスを返すメソッドということです。
Rackの仕組み、各コード、コマンドが何をしているかは下記の記事を参考にしてください。

Rackミドルウェア
RackはアプリケーションサーバとWebアプリケーションの中間に存在し、双方はRackを通してやりとり(リクエスト、レスポンス)をしています。
Rackミドルウェアは、このリクエスト、レスポンスに処理を追加することができます。

ミドルウェアは中心にWebアプリケーションを置く入れ子構造で、追加するほど入れ子構造は深くなります。
Rackにはいくつかのミドルウェアが標準で備わっているので、詳細は公式を参考にしてください。
Rackミドルウェアの作成
Rackミドルウェアは簡単に作成することができます。
ミドルウェアの追加
まず、Rackに備わっているRack::Runtime
ミドルウェアを追加してみます。run
メソッドの上部にuse
メソッドでミドルウェアを指定することで追加できます。
require 'rack'
require_relative 'sample_app'
use Rack::Runtime
run SampleApp.new

rackup
でアプリケーションを立ち上げるとレスポンスヘッダにX-Runtime: 0.000012
が追加されています。
これはリクエストの処理にかかった時間を示します。
このように簡単ミドルウェアは追加することができます。
ミドルウェアの作成
RackミドルウェアもRackの規約に則ることで作成することができ、下記がミドルウェアの規約です。
・initializeメソッドに引数を一つ取る
・Rackアプリケーションと同じインターフェイスのcallメソッドを用意する
引用:パーフェクトRuby on Rails【増補改訂版】
変数や引数を出力するだけのミドルウェアを作成・追加してみます。
class SampleMiddleware
def initialize(app)
puts '+' * 60
puts "app: #{app.class}"
puts '+' * 60
@sample_app = app
end
def call(env)
status, headers, body = @sample_app.call(env)
puts '+' * 60
puts "status: #{status}"
puts '+' * 60
puts "headers: #{headers}"
puts '+' * 60
puts "body: #{body}"
puts '+' * 60
[status, headers, body]
end
end
require 'rack'
require_relative 'sample_app'
require_relative 'sample_middleware' # 追加
use Rack::Runtime
use SampleMiddleware # 追加
run SampleApp.new

puts
が呼ばれ、コンソールに表示されていることがわかります。
まずすぐにSampleMiddleware
のinitialize
が呼ばれます。
この時の引数app
はSampleApp
のオブジェクトを受け取っています。
次にHTTPリクエストをenv
で受け取ることで、SampleMiddleware
のcall
メソッドが呼ばれ、その中でSampleApp
のcall
メソッドを呼び出しています。
受け取った[status, headers, body]
がSampleApp
で記載したものと同じなのが確認できます。
一連の処理をまとめると、
ミドルウェアのinitialize
で後続(config.ru
で記載したuse・run
の順番、SampleMiddleware
の後続はSampleApp.new
)のオブジェクトを受け取ります。
HTTPリクエストを受け取り、自身のcall
メソッドが呼ばれた時にinitialize
で受け取ったオブジェクトのcall
メソッドを呼ぶことで後続の処理を行う流れです。

リクエストデータを処理したい場合はcall
の引数のenv
に、
レスポンスデータを処理したい場合はcall
の戻り値に対して、なにかしらの処理を追加すれば良いです。
RailsとRack
前述した通りRailsもRackの規約に則っているので、rails new
でプロジェクトを作成すると、Rackアプリケーションの証明であるconfig.ru
が作成されます。
# This file is used by Rack-based servers to start the application.
require_relative 'config/environment'
run Rails.application
Railsではconfig/environment.rb
を読み込んで、Rails固有の処理を実行し、run
メソッドでRails.application
オブジェクトを渡しています。
普段はbin/rails sever
コマンドを実行していますが、
RailsアプリケーションもRackアプリケーションなので、rackup
コマンドを入力するとport9292番
でRailsアプリケーションが立ち上がります。
bin/rails sever
Railsガイドにbin/rails sever
コマンドの説明がされているので参考にしてください。
bin/rails sever
を実行するとRails::Sever
オブジェクトを作成し、Rails::Sever.start
を実行します。
このRails::Sever
クラスはRuck::Sever
クラスを継承しており、Ruck::Sever
のいくつかのメソッドをオーバーライドしています。
このオーバーライドでRails特有のオプションの定義やport番号を3000にしています。
Railsのミドルウェア
Railsで実装されているミドルウェアはターミナルで下記のコマンドで調べることができます。
$ bin/rails middleware
前述で自作したRackミドルウェアをRailsに追加することもできます。
詳細な追加方法などはRailsガイドを参考にしてください。
実際に追加してみます。
まずミドルウェアファイルの置き場として、lib/middlewares
ディレクトリを作成します。
その配下にsample_middleware.rb
を置いてください。
$ mkdir lib/middlewares
Rackミドルウェアの追加は、常に使用する場合はconfig/application.rb
環境ごとで使用するならconfig/environments
配下の各環境ファイルに追加します。
今回は開発環境で使用したいので、config/environments/development.rb
に追加します。
require 'middlewares/sample_middleware'
Rails.application.configure do
省略
config.middleware.use SampleMiddleware
end
config.middleware.use
は既存のミドルウェア群の一番上にSampleMiddleware
を追加します。
他にも追加できる場所を選択できるAPIが用意されているので、Railsガイドを参考にしてください。
# This file is used by Rack-based servers to start the application.
require_relative 'config/environment'
run Rails.application
config.ru
は変更しなくて良いです。 bin/rails sever
を実行します。
$ bin/rails sever

コンソールにレスポンスデータが表示されていますね。これで追加完了です。
まとめ
Rackは、アプリケーションサーバとWebアプリケーションやフレームワークを接続するための標準化インターフェース。
call
メソッドで[status, header, body]
の値を返すことができれば、Rackの規約に則ったアプリケーションとなる。
Rackミドルウェアは、このリクエストとレスポンスに処理を追加することができる。
RailsもRackアプリケーションである。
RackでできることはRailsでも可能である。
コメント