RackとRailsの関係について備忘録

rack概要図 Ruby
rack概要図

こんにちは!

私はサーバーサイドのエンジニアとして、普段プログラムを書いているのですが、

インフラ領域に苦手意識があるので、

インフラにも強いサーバーサイドエンジニアを目指して、勉強しております!

今回は、Rackがどういうもので、Railsを使ってどういう関係性になっているのかを手で動かしたので、その備忘録です!

多少の誤差はあるかもしれませんが、勉強し始めたピヨコがざっくりでも理解しようとしているので、
何卒ご容赦くださいませ!

Rackとは

Rackというのは、アプリケーションサーバーとフレームワークの間にいうミドルウェアの一種のようなもの。

つまり、アプリケーションサーバーとフレームワークの仲介役です。

これまでの歴史の中で、たくさんの種類のアプリケーションサーバーと、たくさんの種類のフレームワークが存在してきましたが…

それらの仲介役がいないと…

アプリケーションサーバーAは、フレームワークBには繋げられない!でもフレームワークAには繋げられるよ!

という現象が起きてきた。

とても面倒だし、選択肢も狭まってしまうし面倒。

そこで救世主になったのが、この仲介役をになってくれるRack様!

Rackについてもう少し詳しく

Rackのふるまい

Raskがどういうポジションなのかわかったところで、もう少しRackのことを具体的に知ってみる。

Rack:「俺様が仲介役をしてやるかるが、俺様にも限界がある…。俺様が定めるルールに、お前らが合わせたら、ちゃんと仲介してやろう!」

…と強気の態度。

ただ仲介役がいないとみんな幸せじゃないので、アプリケーションサーバーも、フレームワークも、Rack様がいうルールに則ることにしたのです。

そのルールとは…

Rackの規約

Rack様がアプリケーションサーバーとフレームワークに出したルール(主にフレームワークに)。

それはざっくり以下の通りです。

アプリケーションサーバーへのルール

「ポート9292を開けろ!そのポートは俺様が使う!」

フレームワーク(アプリケーション)へのルール

  • 「俺様がお前を呼ぶ用のcallメソッドを1つ用意しろ!」
  • 「callメソッドは引数としてenvが渡ってくることにしろ!アプリケーションサーバーの内容とか色々envの中に1つのハッシュとしてまとめて渡すから!」
  • 「callメソッドの戻り値は…HTTPステータス、HTTPヘッダー、レスポンスボディ の3つの要素を含んだ配列を返すようにしろ!」

RackとRailsの関係性を掘る

上述した感じで、Rackはアプリケーションサーバーとフレームワークにルールを定めたわけです。

言い換えると、Rackを利用できるRailsは、このルールに則ったフレームワークというわけです。

Railsを触っているとある程度の規模のアプリケーションになるのでイメージがしづらいかもしれませんので、Railsをめちゃくちゃ簡略化したアプリケーションになるようちょっと触ってみます。

Rails を小さいアプリケーションにしてみる

まずは手元でも動かせるように、localにRackを入れてみましょうー。

大丈夫、Railsを介してRackを入れることができます。

Railsアプリケーションを手元に用意している状態で、Rackをインストールしてみます。

gem install rack

これでRackが入りました。あー便利。

便利すぎて本当にRackが入ったのが心配な人は、rackup -v をしてバージョンが表示されればOK!

これで、アプリケーションサーバー(今回だとローカル)、Rack、フレームワーク(今回だとRails)が用意できたわけです。

ではわかりやすいようにRailsをめちゃくちゃ小さいアプリにしちゃいます。

Rackをインストールしたら、config.ru というファイルが自動で出来上がっています。

# This file is used by Rack-based servers to start the application.

require_relative 'config/environment'
require "rack"

use Rack::Runtime
run Rails.application

このconfig.ru ファイルは何かというと、Rack様との窓口です。

run Rails.application で、大きな規模のrailsアプリケーションの中で用意されているcallが渡されているわけです。

今回は、callを用意した超小さいアプリケーションを渡してみようと思います。

require_relative 'config/environment'
require "rack"
require_relative "app"

run App.new

Rails.applicationではなく、自作のAppを渡します。

以下の内容で、app.rbを新規作成します。

class App
  def call(env)
    status = 200
    headers = {"Content-Type" => "text/plain"}
    body =["sample"]
    [status, headers, body]
  end
end

これで、callしか持っていない超小さいアプリケーションを、Rackに渡すことができます。

callを用意しているのは、Rack様のルールに則るためです。

rackup コマンドを実行すると、ローカルの9292ポートで起動します。

localhost:9292でブラウザからアクセスしてみると、callメソッドに実装した内容が出力されています。

Rackでミドルウェアを追加してみる

Rackはミドルウェアとしての機構も備えています。

小難しく言いましたが、つまり、アプリケーションサーバーとフレームワークの仲介役として、
間で何かした処理を入れ込むことができるよ、ということです。

例えば、Rack様の力をお借りして、自作のミドルウェアを追加して、ついでに処理を実行してもらいましょう!

とりあえず、自作のミドルウェアを以下のように作ってみます。

class UpcaseMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    body.each { |s| s.gsub!(/ruby/i, "RUBY") }
    [status, headers, body]
  end
end

これは、フレームワークから受け取った値のbodyの内容の中に、「ruby」という文字があったら、
大文字の「RUBY」に変えてね、というミドルウェアです。

これをついでに実行してもらえるようにRack様にお願いするわけですが…
Rack様にお願いする時はそう!ルールがあるのです!

ミドルウェアをついでに実行してもらう場合も、ちゃんとルールを仰っております。

「initializeメソッドを1つ作れ、引数は1つな! フレームワークと同じルールのcallメソッドも用意しろ!」

このルールに則って、上記のミドルウェアは作っております!

では早速、このミドルウェアをRack様にお願いしてみます。

# This file is used by Rack-based servers to start the application.

require_relative 'config/environment'
require "rack"
require_relative "app"
require_relative "upcase_middleware"

use Rack::Runtime
use UpcaseMiddleware
run App.new

use ということで、「このミドルウェアも使ってね!実行してね!」となります。

この状態でrackup を実行すると、表示される「ruby」が「RUBY」になっているはずです。

ありがとうRack様!

initializeメソッドで、後続のミドルウェアを取得していき、取得し終わったら、callを呼んで、実行していくわけです。

実はRailsは、たくさんのミドルウェアを内部的にRack様にお願いしています。

rails middleware と実行すると、実行予定のミドルウェア一覧が表示されます。

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