Inside of rails server command
1. 何故調べたのか
Railsがどのようにルーティングを設定しリクエストを捌くようにしているのか気になっていました。
ルーティングはサーバー起動時に決定されるはずなので rails server コマンドをエントリーポイントとしてどこで設定がなされているか追うことにしましたが徐々に目的と逸れてしまい。。。この件は別の記事できればと思います。とりあえずRailsサーバー起動時に内部でどのようなコードが読まれているのか調べたのでその部分だけまとめてみました。
あくまでソースコードを読んだ個人の見解である事ご留意ください。
もし間違っていること書いていたらそっとコメントしていただけるとありがたいです。
2. bin/rails
処理のエントリーポイントは bin/rails server
bin/rails
の中身は以下
#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"
rails/commands
を require しておりこのファイルの中に処理の中身はありそう。
3. Rails::Command
requireしている railties/lib/rails/commands.rb を確認してみると、
コマンドの引数の部分( bin/rails server であれば server の部分)をを引数として
Rails::Command.invoke
を実行している。
上記のメソッドは引数のチェックを行い 以下の箇所 で動的に各コマンドを担当するクラスに処理を引き渡している。
command.perform(command_name, args, config)
serverコマンドの場合、 command は Rails::Command::ServerCommand
、 consoleコマンドの場合
Rails::Command::ConsoleCommand
となる。各クラスは perform
というクラスメソッドを持ちその中でコマンド実行時の処理が行われていく。
4. Rails::Command::ServerCommand
Rails::Command::ServerCommand
は server コマンド実行時の処理を担当するクラス。
perform メソッド内に処理が記載されている。
実態は Rails::Server
クラスのインスタンスに対して実行している start メソッドのように見える。
これは最終的に super() で継承元のクラスの start メソッドを実行している。
5. Rails::Rackup::Server
Rails::Rackup::Server は require の状況によってセットされるクラスが違う。
デフォルトでは rackup を使用するようになっているのこちらを追っていく。
6. Rackup::Server
rackupのrequireが成功すると Rails::Rackup::Server
には Rackup::Server クラスがセットされる。このクラスはRails::Command::ServerCommandの親クラスとなり、サーバー起動時には start メソッドが実行される。
実際にサーバーが起動されるのは こちら の行のように見える。
server.run(wrapped_app, **options, &block)
7. Rack::Builder
このクラスは文字通りRackアプリケーションをビルドする役割を持ったクラスのように見える。
wrapped_app の実態は build_app app だが、この箇所は引数で与えられた app
に対してミドルウェアを読み込んでいるように見える。(がここを読み込むには少々時間かかりそうなので別の機会に)
app は Rack::Builderで定義されている。Railsが起動する時は options[:builder]
は渡されない(はず?)ので Rackup::Builder.parse_file.() が呼び出されRackの設定ファイルである config.ru
が読み込まれる。
8. Rackup::Handler
server.run
のserverの部分は こちら の行で定義されており、Rackup::Handler が担っている。このクラスはRackアプリケーションを起動する際に利用する狭義のWebサーバーを定義する役割を担っている。
Railsサーバー起動時はオプションとしてサーバー名を渡さないので Handler.default の部分が読み込まれる。また環境変数の設定も無いので pick SERVER_NAMES
でサーバーが確定する。
pick メソッドは引数に与えられたサーバーを順に require していく。(詳しい説明は割愛)
SERVER_NAMES は puma falcon webrick の文字列を格納した配列となっているので、pumaから順に require していき LoadError が発生した場合、例外を握り潰し次のサーバーを require していく。
server_names.each do |server_name|
begin
server = self.get(server_name)
return server if server
rescue LoadError
# Ignore.
end
end
Railsの場合 puma で require が成功するようになっているが、素のRackアプリケーションの場合 webrick が採用される。
9. Puma::RackHandler
Rack互換のWebサーバーはRackアプリケーションのオブジェクトを引数に受け取りサーバーを起動する run
メソッドが提供されている。Pumaの場合は こちら に定義されている。
Rackアプリケーションを設定として読み込んでWebサーバーを起動しているように見えるがこの箇所も別の機会に・・・(正直よくわかってない)
9. 終わりに
ちょっと中途半端ですが、ここまでで一旦一区切り。
bin/rails server
コマンドは、コマンドを処理する railties と、Rackアプリケーションを起動する rackup という二つのライブラリがそれぞれの役割を果たしていた。
個人的に好きなコードは
- Rails::Commandで実行コマンドの引数を解析して適切なコマンドクラスの perform メソッドを呼び出す
- コマンドクラスのIFとして perform メソッドが提供されている!わかりやすい!
- Rackアプリケーション互換のWebサーバーを読み込む際に、順に require をしていきLoadErrorが発生したら次のWebサーバーをrequireする
- 豪快!こんなコード一度でいいから書いてみたい!
- Rackアプリケーション互換のWebサーバーに対して
run
メソッドを実行する- IFがしっかりしている!!
Discussion