Rackについて調べてみた

この記事は[ソニックガーデン 若手プログラマ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/sonicgarden-junior)の22日目の記事です。

普段業務でRuby on Railsを使用しているのですが、ある日いつも通り[Railsガイド](https://railsguides.jp/)を読んでいると「Rack」という単語に目が止まりました。

「…うん、Rackって何？」となりました。

RailsガイドになんでRackとやらの説明が載っているんだ？と困惑し、さらにページを読み進めるとそもそも「ミドルウェア」という単語が理解できていないことに気がつきました。お恥ずかしいかぎりです。

とはいえいつまでも恥ずかしがっていられないので、これを機に調べました。

アドカレ二本目の記事は、Railsを使うなら押さえておきたいRackについて調べたことを共有したいと思います。

## Rackとは

Rackは、簡単にいうとwebアプリケーションサーバーとフレームワーク間を共通化してくれるライブラリです。

例えばRailsというフレームワークではPumaやUnicornサーバーなどを利用することが多いですよね。ここは魔法で連動していると思っていましたが、その魔法の正体はRackという仲介役なんですね。

RailsはRackというインターフェースに則っているので、複数のサーバーで起動することができるのです。

## Rackの基本を理解する

Rackはシンプルなインターフェースを定義しています。

```ruby
def call(env)
 [status, headers, body]
end
```

- callメソッドを定義する
- 引数にはenvを受け取る
- callメソッドは、ステータスコード・ヘッダー（中身はハッシュオブジェクト）、レスポンスボディを戻り値として返す

## Rackアプリケーションを動かしてみよう

Rackアプリケーションを作成してみます。

まずはディレクトリを作成し、rack gemをインストールしましょう

```ruby
mkdir rack-sample
cd rack-sample

gem install rack
```

 Rackの規約に準ずるAppクラスを定義します。ファイル名はapp.rbです。

```ruby
class App
  def call(env)
    status = 200
    headers = { 'content-type' => 'text/html' }
    body = ['Hello, World!']
    [status, headers, body]
  end
end
```

次に、エントリーポイント用のファイルを作成しましょう。ファイル名はconfig.ruです。

```ruby
require 'rack'
require_relative 'app'

run App.new
```

これで準備は完了です。

rackupコマンドでサーバー立ち上げされます。

```ruby
rackup 
```

![CleanShot 2024-12-21 at 15.34.32@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927131/d78416fb-07c8-f833-b6df-ad0c174a4f47.png)


callメソッドに記述した戻り値が画面に表示されています！

改めてRackの構造をまとめると

- rackupコマンドを叩くと、config.ruに記述したrunメソッドが呼ばれる
- runメソッドは、callメソッドの戻り値をレスポンスとしてもらう

## Rackミドルウェアって何？

Rackはミドルウェアとして紹介されることがあります。

ミドルウェアとは「アプリケーションサーバーとアプリケーションの間に処理を追加する」機構のことを指します。

Rackミドルウェアも、Rackアプリケーション同様に実装することができます。

用意するものは、initializeメソッドとcallメソッドの二つです。

Appクラスとは別に、SimpleMiddlewareクラスを作成してみます。

```ruby
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で確認してみます

```ruby
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も書き直します。
``` ruby
require 'rack'
require_relative 'app'
require_relative 'simple_middleware'

use SimpleMiddleware
run App.new
```
rackupしてサーバーを立ち上げます

```ruby
rackup
```

```ruby
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](https://qiita.com/advent-calendar/2024/sonicgarden-junior)」23日目は[@ynitami](https://qiita.com/ynitami)です。お楽しみに！