【Ruby on Rails】RailsとRackについて理解する

Ruby on Rails

Rackとは

Rubyにおいて、アプリケーションサーバ(Puma、Unicornなど)とWebアプリケーションやフレームワーク(Rails、Sinatraなど)を接続するための標準化インターフェース(規約)です。

これらはRackの規約に則ることで、さまざまな組み合わせでも動作します。
公式にサポートしてあるアプリケーションサーバとWebアプリケーションが記載してあります。

ある記事ではRackを共通言語と説明していました。こちらの方がわかりやすいですね。

RackはRailsのようなRuby製のwebフレームワークとアプリケーションサーバーの両方が話せる共通言語のようなものだと考えてください。

引用;Rails開発におけるwebサーバーとアプリケーションサーバーの違い(翻訳)

Webアプリケーション側のRackの規約

Rackに対応するWebアプリケーションやフレームワークはこの規約に則ったインターフェイスを定義する必要があります。

・callメソッドを定義する
・callメソッドはenvあるいはenviromentと命名する引数を一つ受け取る
・callメソッドは次の値を配列型で戻り値として返す
・HTTPのステータスコードを表す数値オブジェクト(status)
・HTTPヘッダーを表すハッシュオブジェクト(headers)
・レスポンスボディとなる文字列を含んだ配列風オブジェクト(body)

引用:パーフェクトRuby on Rails【増補改訂版】

わかりやすくコードで表すと、

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アプリケーションが起動し、サーバが立ち上がります。
rackupRack::Sever.startメソッド実行しています。

rackupではpart9292番になるので、localhost:9292へアクセスします。

ステータスコードが200、レスポンスヘッダーのContent-Type: text/plainHello, Worldの記述があるので、
sample_app.rbで定義したcallメソッドの戻り値[status, headers, body]がレスポンスとして返されているのが分かります。

これらの動作から、
callメソッドは引数(env)にアプリケーションサーバーからHTTPリクエストに関するデータを受け取り、戻り値として[status, headers, body]を含むHTTPレスポンスを返すメソッドということです。

Rackの仕組み、各コード、コマンドが何をしているかは下記の記事を参考にしてください。

Rackとは何か - Qiita
僕はRackミドルウェアを何個か書いたことがあるけど、この前Rubyを始めたばかりの人に「Rackって何?」って聞かれた時、ちゃんと答えられなかった。 なので、rack/rackは何を実装していて、RailsやSinatraはどのよう...

Rackミドルウェア

RackはアプリケーションサーバとWebアプリケーションの中間に存在し、双方はRackを通してやりとり(リクエスト、レスポンス)をしています。

Rackミドルウェアは、このリクエスト、レスポンスに処理を追加することができます。

アプリケーションサーバとWebアプリケーションのやりとりのイメージ

ミドルウェアは中心に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が呼ばれ、コンソールに表示されていることがわかります。


まずすぐにSampleMiddlewareinitializeが呼ばれます。
この時の引数appSampleAppのオブジェクトを受け取っています。

次にHTTPリクエストをenvで受け取ることで、SampleMiddlewarecallメソッドが呼ばれ、その中でSampleAppcallメソッドを呼び出しています。
受け取った[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::severクラスのコード

Ruck::severクラスのコード

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でも可能である。

コメント

タイトルとURLをコピーしました