こんにちは!
私はサーバーサイドのエンジニアとして、普段プログラムを書いているのですが、
インフラ領域に苦手意識があるので、
インフラにも強いサーバーサイドエンジニアを目指して、勉強しております!
今回は、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 と実行すると、実行予定のミドルウェア一覧が表示されます。