「rails server」コマンドを読み解く

#「rails server」コマンドを読み解く

##前置き
本記事の内容はタイトルのまんま。`rails server`コマンドを叩いた際に一体何が実行されているのかを調べてみた。内容的に、対象者は初心者以上中級者未満。多分。なお、調べながら記事を書いていたので、説明の流れが下手くそな部分があるがそこは許してンゴンゴ。

※Rackについて詳しい人は得るものないかもしれませんがご了承ください。


##バージョン
ruby 2.6.3
Rails 6.0.1

##準備
多分そんなに必要ないけどRails newしてアプリを作成。

```terminal
$ rails new adc2019
```

からのbundle install

```terminal
$ bundle install
```

コントローラも作成してルートを通しておこう。

```terminal
rails g controller home
```

```routes.rb
Rails.application.routes.draw do
  get '/', to: 'home#homw'
end
```

viewsは適当で

```home.html.erb
<h1>HOME</h1>
```

んでrails s

```terminal
$ rails s
```

[localhost:3000](http://localhost:3000)にアクセス

![スクリーンショット 2019-12-13 18.48.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174526/57081ad9-7d84-9e76-551d-373b3332f435.png)


##rails server とは
早速、`rails server`の中身をみていく。

###rails コマンド
基本、ターミナル上で`rails`コマンドを叩いた場合、システム上のrailsコマンドを呼ぶ。

```terminal
$ which rails
/Users/かもひこ/.rbenv/shims/rails
```

んでもって、``rails new`して作ったアプリのディレクトリにおいては、その配下の`bin/rails`が呼ばれる。
（上記システム上のrailsを呼ぶ。→からのアプリディレクトリ配下の`bin/rails`を呼ぶ。という流れ。）

中身は下記のとおり、

```bin/rails.rb
#!/usr/bin/env ruby
begin
  load File.expand_path('../spring', __FILE__)
rescue LoadError => e
  raise unless e.message.include?('spring')
end
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
```

最初の部分で`spring`（アプリケーションプリローダー）を呼び出しとる。
コマンドの2回目以降の起動時間を短縮してくれるやつ。
Rails4.1からデフォルトで追加されたらしい。

次に`APP_PATH`を定義して、なにやらconfig系のファイルを読み、やっと最後`rails/commands`という、如何にもなファイルを読みにいく。
`rails server`が何をやっているのかを知りたいので、configの部分は「なんか初期化やら設定してるんやろな」ぐらいに済ませて、さっさと`rails/commands`を見てみる。（ここからはgemのrailsをみていく。アプリ配下のファイルではないのでご注意を。）

###commands.rb

```rails/railties/lib/rails/commands.rb
require "rails/command"

aliases = {
  "g"  => "generate",
  "d"  => "destroy",
  "c"  => "console",
  "s"  => "server",
  "db" => "dbconsole",
  "r"  => "runner",
  "t"  => "test"
}

command = ARGV.shift
command = aliases[command] || command

Rails::Command.invoke command, ARGV
```

ここでコマンドのエイリアスを定義してる。普段`rails s`とかで`rails server`扱いしてくれているのはここのおかげ。

んで、最後の`invoke`メソッドの記述部分。コマンドラインで叩いた`server`を`invoke`に渡している。直略で`呼び出す`の意味を持つこのメソッド。
次に見るんはここやな。

てわけで`invoke`メソッドを見てみる。


###command.rb

```rails/railties/lib/rails/command.rb
def invoke(full_namespace, args = [], **config)
  namespace = full_namespace = full_namespace.to_s

  if char = namespace =~ /:(\w+)$/
    command_name, namespace = $1, namespace.slice(0, char)
  else
    command_name = namespace
  end

  command_name, namespace = "help", "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name)
  command_name, namespace = "version", "version" if %w( -v --version ).include?(command_name)

  command = find_by_namespace(namespace, command_name)
  if command && command.all_commands[command_name]
    command.perform(command_name, args, config)
  else
    find_by_namespace("rake").perform(full_namespace, args, config)
  end
end
```

`namespace`って書いてあるあたり、名前空間が引数（今回でいうと`server`）のモジュールを読みにいくのかな？

下記の部分がおそらくそうっぽい。

```.rb
command = find_by_namespace(namespace, command_name)
```

`find_by_namespace`は下記のとおり。

```rails/railties/lib/rails/command.rb
def find_by_namespace(namespace, command_name = nil) # :nodoc:
  lookups = [ namespace ]
  lookups << "#{namespace}:#{command_name}" if command_name
  lookups.concat lookups.map { |lookup| "rails:#{lookup}" }

  lookup(lookups)

  namespaces = subclasses.index_by(&:namespace)
  namespaces[(lookups & namespaces.keys).first]
end
```

最初の３行で探しにいくモジュールの名前空間を配列にぶちこんでいる。`lookups`の中身は下記。
`["server", "server:server", "rails:server", "rails:server:server"]`
`rails:server`だけでよくね？と思ったが、きっと何か理由があるのだろう。（他のコマンド使用時に必要な処理かな？）とりあえず進む。

`lookup`メソッドに上記の名前空間たちを渡している。きっとこのメソッド内でモジュールを読み込むのだろう。


```rails/railties/lib/rails/command/behavior.rb
def lookup(namespaces)
  paths = namespaces_to_paths(namespaces)

  paths.each do |raw_path|
    lookup_paths.each do |base|
      path = "#{base}/#{raw_path}_#{command_type}"

      begin
        require path
        return
      rescue LoadError => e
        raise unless /#{Regexp.escape(path)}$/.match?(e.message)
      rescue Exception => e
        warn "[WARNING] Could not load #{command_type} #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
      end
    end
  end
end
```
場所は変わって`behavior.rb`内に`lookup`メソッドはいる。
最初の一行目で渡されてきた名前空間たちをパスへと変換している。
んで、案の定`require`している。そろそろメソッド一つ一つ追っていくのがだるくなったので`path`を`puts`して確認する。
（できればソースを直にいじるべきではないが、パッチをあてるのもあれだったので。）

```.rb
begin
  puts path #ここで確認
  require path
  return
```

んでサーバーたてる。

```terminal
$ rails server
rails/commands/server/server_command
=> Booting Puma
=> Rails 6.0.1 application starting in development 
...
..
.
〜略〜
```

でましたでました。ここでやっと、

**「`require`したのは`rails/commands/server/server_command`か！！じゃあ、`server`コマンド叩いたときの処理はそこを見ればいいんだな！」(棒読み)**

となるわけである。（まあソース開いてディレクトリ構成見た時点で予想はついてたけど、我慢して順繰りに処理を追ってみた。）


さて、モジュールを読み込んだところで、少し戻って`find_by_namespace`の下記の部分。

```.rb
  lookup(lookups)

  namespaces = subclasses.index_by(&:namespace)
  namespaces[(lookups & namespaces.keys).first]
```

モジュールを読み込んだだけでは意味がない。最後に返り値としてサブクラスを返している。
つまり`Rails::Command::ServerCommand`を返す。

そして、ここで`invoke`メソッドに戻る。下記の部分である。

```.rb
  command = find_by_namespace(namespace, command_name)
  if command && command.all_commands[command_name]
    command.perform(command_name, args, config)
  else
    find_by_namespace("rake").perform(full_namespace, args, config)
  end
```

`command`の中には上記サブクラスが入っており、その中にあるメソッド`perform`を実行している。
ようやくここで`server`コマンドの処理が実行されるわけである。


てわけで、いよいよ`server_command.rb`の中をみる。


###server_command.rb
では`perform`メソッドをみてみる。

```rails/railties/lib/rails/commands/server/server_command.rb
def perform
  extract_environment_option_from_argument
  set_application_directory!
  prepare_restart

  Rails::Server.new(server_options).tap do |server|
    # Require application after server sets environment to propagate
    # the --environment option.
    require APP_PATH
    Dir.chdir(Rails.application.root)

    if server.serveable?
      print_boot_information(server.server, server.served_url)
      after_stop_callback = -> { say "Exiting" unless options[:daemon] }
      server.start(after_stop_callback)
    else
      say rack_server_suggestion(using)
    end
  end
end
```

まだまだコードリーディングが続くのかあ....と思っていたが、じつはここの部分、公式ガイドラインにて説明されている。（→[ここから](https://railsguides.jp/rails_on_rack.html#rails%E3%81%A8rack)）

>Rails::ServerクラスはRack::Serverクラスを継承しており、以下のようにRack::Server#startを呼び出します。

つまり上記のまんま、`rails server`はRackよりサーバーを起動していたのである。

##Rackとは
`rails s`がRackよりサーバーを起動してるとわかったところで、ここでRackについて説明する。

###Rackとは
[Rackについて説明しているページ](http://rubylearning.com/blog/2013/04/02/whats-rack/)より下記を引用する。

>In the words of the author of Rack – Christian Neukirchen: Rack aims to provide a minimal API for connecting web servers supporting Ruby (like WEBrick, Mongrel etc.) and Ruby web frameworks (like Rails, Sinatra etc.).

簡潔に説明すると、RackとはRuby製のWEBサーバーとWEBアプリケーション間のインターフェースである。様々なwebサーバー、WEBアプリケーションが双方依存せずに開発できるのはこのRackのおかげ。

Rackの仕様に従って開発されているWEBサーバー、WEBアプリケーションであればどのような組み合わせも可能なのである。（puma & rails、 unicorn & rails, unicorn & sinatra みたいな）

###Rackを使ってみる1
それでは実際に、引用元のページを参考にしてRackの仕様に従ったアプリ、いわゆるRackアプリケーションを作ってみる。Rackアプリケーションとしての最低限の仕様は下記のとおり。

> A Rack application is a Ruby object that has a call method, which has a single argument, the environment, (corresponding to a HTTP request) and returns an array of 3 elements, status, headers and body (corresponding to a HTTP response).

つまり、

1. callメソッドを持つこと。
2. 引数としてWEBサーバーからのHTTPリクエストを受けること
3. 返り値は、ステータスコード（100以上）、レスポンスヘッダ(ハッシュ)、レスポンスボディであること。

の３つがRackアプリケーションとしての最低限の条件である。


それで`irb`で簡単に作ってみる。（ここは参考元のほぼ丸パクリ。）

```terminal
$ irb
```

まず、`rack`を読み込む。

```.rb
irb(main):001:0> require 'rack'
=> true
```
callメソッドを持っていればよいので、`lambda`を使ってProcオブジェクトを作る。
引数は一つ。返り値は仕様どおりのレスポンスコード、レスポンスヘッダ、レスポンスボディ。

```.rb
irb(main):002:0> my_app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
=> #<Proc:0x00007fdb888ff528@(irb):2 (lambda)>
```

試しに実行。

```.rb
irb(main):003:0> my_app.call({})
=> [200, {"Content-Type"=>"text/plain"}, ["Hello. The time is 2019-12-19 11:26:39 +0900"]]
```

問題ないので、最後にWEBrickをWEBサーバーとしてをアプリ起動、

```.rb
irb(main):004:0> Rack::Handler::WEBrick.run my_app, :Port => 9876
[2019-12-19 11:27:02] INFO  WEBrick 1.4.2
[2019-12-19 11:27:02] INFO  ruby 2.6.3 (2019-04-16) [x86_64-darwin16]
[2019-12-19 11:27:02] INFO  WEBrick::HTTPServer#start: pid=2809 port=9876
```

[http://localhost:9876/](http://localhost:9876/)をブラウザで開く。

下記の様に表示されるはず。
<img width="444" alt="スクリーンショット 2019-12-19 11.42.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174526/fa34af1d-2513-93c7-c808-5222672bbeab.png">


レスポンスヘッダもばっちり。
<img width="318" alt="スクリーンショット 2019-12-19 11.42.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174526/6d447b73-bbef-a4be-2ac5-627f23be262c.png">

簡単なRackアプリケーションはこんな感じ。シンプルである。

###Rackを使ってみる2
Rackアプリケーションの起動方法として便利なコマンド、`rackup`がある。
`rackup`は`congfig.ru`というrack構成ファイルを元にRackアプリケーションを起動してくれる。

さっきのProcオブジェクトだとRailsをイメージしにくいので、簡単なクラスを定義してみる。
response_bodyとして、引数に渡されるWEBサーバーからのリクエストをjsonにして返そう。


```my_app.rb
require 'json'

class MyApp

  def call(env)
    response_body = env.to_json

    response = [
      200,
      {"Content-Type" => "application/json; charset=utf-8"},
      [response_body]
    ]

    return response
  end

end
```

構成ファイルはこんな感じ。

```config.ru
require './my_app'
run MyApp.new
```
後は下記コマンドで起動。

```terminal
$ rackup config.ru
```



[http://localhost:9292/](http://localhost:9292/)を開けばjsonで下記が返ってくるはず。

![スクリーンショット 2019-12-19 20.29.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174526/1cb3781d-c665-833a-b19b-5b8aa9ce36d6.png)


Rackについてはこんな感じで説明は終わる。Railsに戻る。


##RackによるRailsアプリケーションの起動
さて、RackがどうやってRackアプリケーションを起動するかわかったところでRailsの場合をみていく。
アプリを作成したディレクトリ直下に構成ファイルである`config.ru`がある。先程の例同様、この構成ファイルを元にRackはRailsを起動するのである。

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

require_relative 'config/environment'

run Rails.application
```
先程例にあげた構成ファイルとほぼ同じ記述内容であることがわかるだろう。`Rails.application`はもちろん`Rails new`して作ったアプリケーションのことだ。出力すればわかる。

```config.ru
puts Rails.application.class
```

```terminal
$ rails s
...
..
〜略〜
.
.

Adc2019::Application
```

さて、こうなるとRackアプリケーションとして必要な`call`メソッドはどこに書いてあるの？という疑問がもちろんわく。
継承関係的に予想はつくけど、まあ念の為もう一度`puts`する。

```config.ru
puts Rails.application.method(:call).source_location
```

```terminal
/Users/かもひこ/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/railties-6.0.1/lib/rails/engine.rb
```

`engine.rb`で定義されているらしい。

```engine.rb
def call(env)
  req = build_request env
  app.call req.env
end
```

こいつがRailsのRackアプリケーションとしてのcallメソッドとなる。以後、リクエストがくれば、このメソッドにそのリクエストが渡され実行される。

##まとめ
流れがわかったところでまとめる。

|処理の流れ|
|:------------------:|
|`rails server`コマンドを叩く|
|↓|
|server_command.rbの`perform`メソッドを実行|
|↓|
|Rack::Server#startを呼び出す。|
|↓|
|config.ruを元にRailsアプリケーションを起動。|
|↓|
|以降、リクエストはengine.rbのcallメソッドで処理される。|

長々と書いたが、やっていることは至ってシンプルだった。
RailsアプリケーションをRackアプリケーションとして起動しているだけ。これが`rails server`の処理の流れかな。


##最後に
長々とコードリーディングして得られたのは、RailsってよりRackについての知見な気がする。
まあ、ソースコードを読んだおかげでRailsへの「なんか動いている」感を少しでもなくせたのは良き収穫。
構成もなんとなく掴めたし、gemとか作ろうと思った時にこの経験は力になるだろう。

てわけで長々と読んでいただきありがとうございました。
間違い等あればコメント欄までお願いします。

ではおやすみ。

##参考
[railsコマンドを追う](https://shgam.hatenadiary.jp/entry/2014/08/15/001501)
[Rack解説 - Rackの構造とRack DSL](https://qiita.com/higuma/items/838f4f58bc4a0645950a#rack%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E5%AE%9F%E8%A1%8C)
[Rackとは何か（1）Rackの生まれた背景](https://gihyo.jp/dev/serial/01/ruby/0023)
[What’s Rack?](http://rubylearning.com/blog/2013/04/02/whats-rack/)