railsの起動プロセスを追う その4
はじめに
railsの起動までを見ていこうその4ということで、今回は rails new
で生成される config.ru
から見ていこうと思います。
以下の「railsの起動プロセスを追う」の部分に各回へのリンクがあります。
※この起動プロセスについてはrails公式のドキュメントがあります。詳しくはこちらをご覧下さい。
run
前回までで、下記の require_relative 'config/environment'
の解析を終えたので、今回は run Rails.application
から見ていきます。
# This file is used by Rack-based servers to start the application. require_relative 'config/environment' run Rails.application
Rails.applicationの部分はその3で調査した下記コードから Rails::Application
を継承したクラスのインスタンスが入ります。
rails/rails.rb at v5.2.1 · rails/rails · GitHub
def application @application ||= (app_class.instance if app_class) end
rails/application.rb at v5.2.1 · rails/rails · GitHub
def inherited(base) super Rails.app_class = base add_lib_to_load_path!(find_root(base.called_from)) ActiveSupport.run_load_hooks(:before_configuration, base) end
./config/application.rb
module TestRails class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.2
run
自体は引数に渡ってきた Rails::application
を @app
にセットするだけです。
rack/builder.rb at 2.0.5 · rack/rack · GitHub
# Takes an argument that is an object that responds to #call and returns a Rack response. # The simplest form of this is a lambda object: # # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] } # # However this could also be a class: # # class Heartbeat # def self.call(env) # [200, { "Content-Type" => "text/plain" }, ["OK"]] # end # end # # run Heartbeat def run(app) @run = app end
new_from_string の評価結果
これでやっと、 new_from_string
内の Rack::Builder.new {\n" + builder_script + "\n}}
が何を返してくるのかが分かりました。
※builder_scriptはconfig.ruの内容と等価です。
次の関心事は、Rack::Builder.new {\n" + builder_script + "\n}.to_app
が何をしているかです。
to_app
の実体は railties/lib/rails/application.rb
にあります。
この処理では自分自身、つまりRails::Application
を継承したクラスのインスタンスが返されます。
rails/application.rb at v5.2.1 · rails/rails · GitHub
def to_app #:nodoc: self end
よって、new_from_string
はRails::Application
を継承したクラスのインスタンスが返ることになります。
rack/builder.rb at 2.0.5 · rack/rack · GitHub
def self.new_from_string(builder_script, file="(rackup)") eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app", TOPLEVEL_BINDING, file, 0 end
ということで wrapped_app
の app
が返す値はself.parse_file -> build_app_and_options_from_config -> app->app から、Rails::Application
を継承したクラスのインスタンスが返ります。
再びwrapped_app
ここまでで、 wrapped_app
内の app
が何を返すかが分かりました。
次に、 build_app
が何をしているのかを見ていきます。
rack/server.rb at 2.0.5 · rack/rack · GitHub
def wrapped_app @wrapped_app ||= build_app app end
build_appの内容は以下のようになっています。
rack/server.rb at 2.0.5 · rack/rack · GitHub
def build_app(app) middleware[options[:environment]].reverse_each do |middleware| middleware = middleware.call(self) if middleware.respond_to?(:call) next unless middleware klass, *args = middleware app = klass.new(app, *args) end app end
何やらいろいろと処理をしていますが、 middleware
は継承先のクラスで以下のように定義されていて、このメソッド自体は 引数のappの内容をそのまま返します。
rails/server_command.rb at v5.2.1 · rails/rails · GitHub
def middleware Hash.new([]) end
これで再びstartメソッドの処理を追うことに戻れます。
再びstart
以下にstartのソースを再掲します。
rack/server.rb at 2.0.5 · rack/rack · GitHub
def start &blk if options[:warn] $-w = true end if includes = options[:include] $LOAD_PATH.unshift(*includes) end if library = options[:require] require library end if options[:debug] $DEBUG = true require 'pp' p options[:server] pp wrapped_app pp app end check_pid! if options[:pid] # Touch the wrapped app, so that the config.ru is loaded before # daemonization (i.e. before chdir, etc). wrapped_app daemonize_app if options[:daemonize] write_pid if options[:pid] trap(:INT) do if server.respond_to?(:shutdown) server.shutdown else exit end end server.run wrapped_app, options, &blk end
ここまでで、wrapped_app
が行っていることは判明しています。
次の daemonize_app
は デーモン化をしているだけなので、特に読むところはありません。
その次の write_pid
はサーバが二重起動しないようにする為のpidが入ったロックファイルを作成するという物で、コード的には読むところはあるのですが、趣旨と外れるので飛ばします。
trap(:INT)
のブロックもその3で説明した Ctrl-Cのときの動作を定義する物なので飛ばします。
最後の server.run wrapped_app, options, &blk
は、今まで待ちに待ったサーバを起動する部分になります。
サーバの起動。
serverについては、rack側には以下の定義があります。
一般的には、@_server
に起動するサーバの実体が入ってくるようにします。
rack/server.rb at 2.0.5 · rack/rack · GitHub
def server @_server ||= Rack::Handler.get(options[:server]) unless @_server @_server = Rack::Handler.default # We already speak FastCGI @ignore_options = [:File, :Port] if @_server.to_s == 'Rack::Handler::FastCGI' end @_server end
例えば、Pumaの場合 Rack::Handler.default
に Rack::Handler::Puma
が入ってきます。
Puma側では下記のように self.run
メソッドが定義されていて、Pumaが起動されるようになっています。
puma/puma.rb at master · puma/puma · GitHub
def self.run(app, options = {}) conf = self.config(app, options) events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio launcher = ::Puma::Launcher.new(conf, :events => events) yield launcher if block_given? begin launcher.run rescue Interrupt puts "* Gracefully stopping, waiting for requests to finish" launcher.stop puts "* Goodbye!" end end
これでやっと、一連のrailsの起動プロセスを追うことが出来ました。
まとめ
- 起動の際に使うアプリケーションは
Rails::Application
を継承したクラスのインスタンスを使っている。 - 起動するサーバはアプリケーションサーバ側で runメソッドを定義してある物を実行する。