rails server と rackup の違い

 前回の記事でミニマムな Rack アプリケーションを動かしてみました。 Rails も Rack ベースのアプリケーションですが、通常は rails server コマンドでアプリケーションを起動します。 Rails のアプリでも config.ru は持っているので、 rackup でも起動できるはずですが、 rails server コマンドで起動するのと何が違うのかを少し調べてみました。

 ちなみに前回の記事はこちらです。

blog.akanumahiroaki.com

まずはそれぞれで動かしてみる

 とりあえず実際に動かしてみてその違いを見てみます。まずは rails server コマンドで起動してみます。

$ rails s
=> Booting Puma
=> Rails 5.2.2 application starting in development 
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.0 (ruby 2.6.0-p0), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:8080
Use Ctrl-C to stop

 tcp://localhost:8080 でアプリケーションが起動しますので、こちらにブラウザ等でアクセスすると下記のようなログが出力されます。

Started GET "/" for 220.211.52.113 at 2019-04-14 13:32:54 +0000
Cannot render console from 220.211.52.113! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by Rails::WelcomeController#index as HTML
  Rendering /home/ec2-user/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/railties-5.2.2/lib/rails/templates/rails/welcome/index.html.erb
  Rendered /home/ec2-user/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/railties-5.2.2/lib/rails/templates/rails/welcome/index.html.erb (2.2ms)
Completed 200 OK in 18ms (Views: 6.2ms | ActiveRecord: 0.0ms)

 今度は rackup で起動してみます。

$ rackup
Puma starting in single mode...
* Version 3.12.0 (ruby 2.6.0-p0), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:9292
Use Ctrl-C to stop

 rails server で起動した時と比べると、最初の3行が出力されていません。また、ポートも 8080 ではなく 9292 で起動されています。

 tcp://localhost:9292 にアクセスするとログは下記のような出力になります。

127.0.0.1 - - [14/Apr/2019:13:36:22 +0000] "GET / HTTP/1.1" 200 - 0.1098

 シンプルな Web サーバのログが出力されるだけで、 rails server の時に出力されていた Rails に関するログは出力されていません。

処理内容の差分

 それでは起動時に実際にどのような処理が行われているのか、コードから確認してみたいと思います。下記 Rails ガイドのページで初期化プロセスが紹介されていますので、これを参考に確認してみます。

railsguides.jp

 rails server コマンドで起動した場合も最終的に config.ru から Rack ベースの Rails アプリケーションが起動されることになるのですが、 rackup と違って Rails の環境を色々整えた上でアプリケーションが起動されることになります。

 詳細なステップについては上記サイトで紹介されているので、ここで一つ一つの紹介は割愛しますが、主に違う点は下記の点かと思います。

  • Bundler の読み込みと設定が行われる

  • rails コマンドの別名の拡張が行われる

  • PORT番号などの設定が行われる

  • Logger の設定が行われる

 上記が行われた上で config.ru から Rails アプリケーションが起動します。それぞれについて簡単に見てみたいと思います。

Bundler の読み込みと設定

 起動シーケンスの最初の方で、 config/boot.rb が実行されます。内容は下記のようになっています。

# config/boot.rb

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)

require 'bundler/setup' # Set up gems listed in the Gemfile.

 これによって Gemfile のパスが取得され、 bundler/setup が require されています。

rails コマンドの別名の拡張

 続いて railties/lib/rails/commands.rb の内容が実行され、 rails コマンドの別名の拡張が行われます。これによって例えば rails srails server として解釈できるようになります。ここまではまだ rails コマンドの内容になります。

# railties/lib/rails/commands.rb

# frozen_string_literal: true

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

PORT 番号などの設定

 ここからは rails server コマンドの内容になり、railties/lib/rails/commands/server/server_command.rb 内で Rack::Server を継承した Rails::Server が定義されています。 Rails::Server は初期化時にオプションを受け取るようになっていて、この中で PORT 番号などの設定が行われています。

module Rails
  class Server < ::Rack::Server
    class Options
      def parse!(args)
        Rails::Command::ServerCommand.new([], args).server_options
      end
    end

    def initialize(options = nil)
      @default_options = options || {}
      super(@default_options)
      set_environment
    end

 rails server コマンドでは Rails::Server を初期化する際にオプションを渡しています。 rackup で起動するとデフォルトでは 9292 番ですが、ここではデフォルトが 3000 番ポートとして設定され、さらに今回のケースでは PORT 環境変数で 8080 番ポートが指定されているので、起動時には 8080 番ポートが使用されます。

  module Command
    class ServerCommand < Base # :nodoc:
      include EnvironmentArgument

      # Hard-coding a bunch of handlers here as we don't have a public way of
      # querying them from the Rack::Handler registry.
      RACK_SERVERS = %w(cgi fastcgi webrick lsws scgi thin puma unicorn)

      DEFAULT_PORT = 3000
      DEFAULT_PID_PATH = "tmp/pids/server.pid"
      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
$ echo $PORT
8080

Logger の設定

 続けて同じく Rails::Server クラスの log_to_stdout メソッドで ActiveSupport::Logger のインスタンスの作成とアサインが行われます。これによって Web サーバのログだけでなく Rails に関係するログが出力されるようになります。

      def log_to_stdout
        wrapped_app # touch the app so the logger is set up

        console = ActiveSupport::Logger.new(STDOUT)
        console.formatter = Rails.logger.formatter
        console.level = Rails.logger.level

        unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
          Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
        end
      end

まとめ

 rails serverrackup の違いは私も今までよくわかっていなかったので、今回整理できて参考になりました。今回は差分のところだけに絞ったので、 Rails アプリの起動シーケンスについてはざっくり割愛してしまいましたが、今後時間があったら Rack ミドルウェアの関連や、実際にリクエストがあった時の挙動なども見てみたいと思います。