この記事はソニックガーデン 若手プログラマ Advent Calendar 2024の22日目の記事です。
普段業務でRuby on Railsを使用しているのですが、ある日いつも通りRailsガイドを読んでいると「Rack」という単語に目が止まりました。
「…うん、Rackって何?」となりました。
RailsガイドになんでRackとやらの説明が載っているんだ?と困惑し、さらにページを読み進めるとそもそも「ミドルウェア」という単語が理解できていないことに気がつきました。お恥ずかしいかぎりです。
とはいえいつまでも恥ずかしがっていられないので、これを機に調べました。
アドカレ二本目の記事は、Railsを使うなら押さえておきたいRackについて調べたことを共有したいと思います。
Rackとは
Rackは、簡単にいうとwebアプリケーションサーバーとフレームワーク間を共通化してくれるライブラリです。
例えばRailsというフレームワークではPumaやUnicornサーバーなどを利用することが多いですよね。ここは魔法で連動していると思っていましたが、その魔法の正体はRackという仲介役なんですね。
RailsはRackというインターフェースに則っているので、複数のサーバーで起動することができるのです。
Rackの基本を理解する
Rackはシンプルなインターフェースを定義しています。
def call(env)
[status, headers, body]
end
- callメソッドを定義する
- 引数にはenvを受け取る
- callメソッドは、ステータスコード・ヘッダー(中身はハッシュオブジェクト)、レスポンスボディを戻り値として返す
Rackアプリケーションを動かしてみよう
Rackアプリケーションを作成してみます。
まずはディレクトリを作成し、rack gemをインストールしましょう
mkdir rack-sample
cd rack-sample
gem install rack
Rackの規約に準ずるAppクラスを定義します。ファイル名はapp.rbです。
class App
def call(env)
status = 200
headers = { 'content-type' => 'text/html' }
body = ['Hello, World!']
[status, headers, body]
end
end
次に、エントリーポイント用のファイルを作成しましょう。ファイル名はconfig.ruです。
require 'rack'
require_relative 'app'
run App.new
これで準備は完了です。
rackupコマンドでサーバー立ち上げされます。
rackup
callメソッドに記述した戻り値が画面に表示されています!
改めてRackの構造をまとめると
- rackupコマンドを叩くと、config.ruに記述したrunメソッドが呼ばれる
- runメソッドは、callメソッドの戻り値をレスポンスとしてもらう
Rackミドルウェアって何?
Rackはミドルウェアとして紹介されることがあります。
ミドルウェアとは「アプリケーションサーバーとアプリケーションの間に処理を追加する」機構のことを指します。
Rackミドルウェアも、Rackアプリケーション同様に実装することができます。
用意するものは、initializeメソッドとcallメソッドの二つです。
Appクラスとは別に、SimpleMiddlewareクラスを作成してみます。
class SimpleMiddleware
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
return [status, headers, body]
end
end
initializeメソッドでは何をしているのでしょうか。引数のappとは何なのか、確かめるためにputsで確認してみます
class SimpleMiddleware
def initialize(app)
puts "*" * 50 # 表示のため
puts "* #{self.class} initialize(app = #{app.class})"
puts "*" * 50 # 表示のため
@app = app
end
def call(env)
status, headers, body = @app.call(env)
puts "*" * 50 # 表示のため
puts "* #{self.class} call(body = #{body})"
puts "*" * 50 # 表示のため
return [status, headers, body]
end
end
config.ruも書き直します。
require 'rack'
require_relative 'app'
require_relative 'simple_middleware'
use SimpleMiddleware
run App.new
rackupしてサーバーを立ち上げます
rackup
rack-practice % rackup
**************************************************
* SimpleMiddleware initialize(app = App)
**************************************************
# 略
**************************************************
* SimpleMiddleware call(body = ["Hello, World!"])
**************************************************
これによると、initializeメソッドでappとして受け取っていたのはAppクラスということがわかります。
そしてbodyとして受け取っているものは、Appクラスのcallメソッドを呼び出した内容なんですね。
今回のミドルウェア実装では以下のことが分かりました。
- Rackアプリケーションには、中心(今回はApp.rb)と周辺(今回はSimpleMiddleware.rb)という概念が存在する
- ミドルウェアはinitializeで、アプリケーションの中心にあたるオブジェクトを受け取り、自分自身のcallメソッドが呼ばれた時に、initializeで受け取ったオブジェクトのcallメソッドを呼び出し、処理を行う
今回扱ったことはかなり基本中の基本ですので、まだまだ奥が深そうです。
終わりに
Railsでコードを書く以上、避けては通れないRackについて調べてみました。正直完全に理解できたとは言い難いですが、仕組みについて一定知識を得ることができたと思います。読者の皆さんにとっても、理解の一助けになると嬉しいです。
参考文献
- すがわら まさのり他、パーフェクト Ruby on Rails【増補改訂版】、技術評論社、2022、p129~p137
「ソニックガーデン 若手プログラマ Advent Calendar 2024」23日目は@ynitamiです。お楽しみに!