rails4啓動過程

    本篇博文會從使用rails server命令到應用啓動完成的代碼調用順序,介紹rails server的啓動過程,是對rails guide的一個簡略翻譯和一些博主的認識,翻譯的很差還請各位見諒。看的時候最好找一份rails4的源碼一塊兒對照的來看。html

    原文地址: http://guides.ruby-china.org/initialization.htmlsql

    當咱們使用rails c(onsole)或者rails s(erver)時會啓動一個rails的應用,rails s其實至關於在當前路徑下執行了一段ruby腳本:ruby

version = ">=0"
load Gem.bin_path('railties', 'rails', version)

若是在Rails console裏輸入上面的命令,你會看到railties/bin/rails被執行了。服務器

在railties/bin/rails源碼的最後一行能夠看到session

require 「rails/cli」

require了railties/lib/rails/cli,繼續跟進,在railties/lib/rails/cli中:app

require 'rails/app_rails_loader'
# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
Rails::AppRailsLoader.exec_app_rails

繼續進入railties/lib/rails/app_rails_loader.rb框架

RUBY = Gem.ruby
EXECUTABLES = ['bin/rails', 'script/rails']
......

class << self
    ......
    def exec_app_rails
      original_cwd = Dir.pwd
      loop do
        if exe = find_executable
          contents = File.read(exe)
     
          if contents =~ /(APP|ENGINE)_PATH/
            exec RUBY, exe, *ARGV
            break # non reachable, hack to be able to stub exec in the test suite
          elsif exe.end_with?('bin/rails') && contents.include?('This file was generated by Bundler')
            $stderr.puts(BUNDLER_WARNING)
            Object.const_set(:APP_PATH, File.expand_path('config/application', Dir.pwd))
            require File.expand_path('../boot', APP_PATH)
            require 'rails/commands'
            break
          end
        end
        # If we exhaust the search there is no executable, this could be a
        # call to generate a new application, so restore the original cwd.
        Dir.chdir(original_cwd) and return if Pathname.new(Dir.pwd).root?
       
        # Otherwise keep moving upwards in search of an executable.
        Dir.chdir('..')
      end
    end
       
    def find_executable
      EXECUTABLES.find { |exe| File.file?(exe) }
    end
end

能夠經過上面的代碼看出Rails::AppRailsLoader.exec_app_rails會在當前目錄下找bin/rails或script/rails,找到了就會執行:less

 exec Gem.ruby, bin/rails, *ARGV

即至關於socket

exec ruby bin/rails server

若是當前目錄下沒有bin/rails或script/rails就會一直遞歸向上直到找到bin(script)/rails,因此在rails項目的根目錄以及子目錄下的任何地方均可以使用rails server或rails console命令ide

bin/rails:

#!/usr/bin/env ruby
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'

APP_PATH常量將會在以後的rails/commands裏使用require_relative '../config/boot'是require了config/boot.rb文件,boot.rb負責加載啓動Bundler

config/boot.rb

# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
 
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])

在標準的的Rails應用中,Gemfile文件申明瞭全部應用依賴關係,ENV['BUNDLE_GEMFILE']存放Gemfile的文件地址,當Gemfile文件存在,回去require bundle/setup,用於Bundler配置加載Gemfile依賴關係的路徑。

一個標準的Rails應用須要依賴幾個gem包,如:

  • actionmailer

  • actionpack

  • actionview

  • activemodel

  • activerecord

  • activesupport

  • arel

  • builder

  • bundler

  • erubis

  • i18n

  • mail

  • mime-types

  • polyglot

  • rack

  • rack-cache

  • rack-mount

  • rack-test

  • rails

  • railties

  • rake

  • sqlite3

  • thor

  • treetop

  • tzinfo

require了config/boot.rb後回到bin/rails,最後還會require rails/commands

rails/commands.rb

主要用於擴展rails命令參數的別名:

rails/commands.rb

ARGV << '--help' if ARGV.empty?
 
aliases = {
  "g"  => "generate",
  "d"  => "destroy",
  "c"  => "console",
  "s"  => "server",
  "db" => "dbconsole",
  "r"  => "runner"
}
 
command = ARGV.shift
command = aliases[command] || command
 
require 'rails/commands/commands_tasks'
 
Rails::CommandsTasks.new(ARGV).run_command!(command)

能夠看到當不傳參數時,實際上與rails --help等效,rails s等效於rails server

rails/commands/command_tasks.rb

當輸入了一個錯誤的rails命令,run_command方法負責拋出一個錯誤信息。若是命令是有效的,就會調用相同名稱的方法

COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help)
 
def run_command!(command)
  command = parse_command(command)
  if COMMAND_WHITELIST.include?(command)
    send(command)
  else
    write_error_message(command)
  end
end

def write_error_message(command)
  puts "Error: Command '#{command}' not recognized"
  if %x{rake #{command} --dry-run 2>&1 } && $?.success?
    puts "Did you mean: `$ rake #{command}` ?\n\n"
  end
  write_help_message 
  exit(1)
end
          
def parse_command(command) 
  case command 
  when '--version', '-v' 
    'version' 
  when '--help', '-h' 
     'help'
  else
     command 
  end
end

根據服務器命令,Rails會進一步運行如下代碼,rails server將會調用server方法

def set_application_directory!
  Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
end

def server
  set_application_directory!
  require_command!("server")
 
  Rails::Server.new.tap do |server|
    # We need to require application after the server sets environment,
    # otherwise the --environment option given to the server won't propagate.
    require APP_PATH
    Dir.chdir(Rails.application.root)
    server.start
  end
end
 
def require_command!(command)
  require "rails/commands/#{command}"
end

 如上代碼首先若是當前目錄config.ru不存在,會將目錄切換到rails的根目錄(APP_PATH是以前bin/rails文件中獲得),以後會require "rails/commands/server",而後建立Rails::Server的對象,調用tap方法啓動server

rails/commands/server.rb

require 'fileutils'
require 'optparse'
require 'action_dispatch'
require 'rails'
 
module Rails
  class Server < ::Rack::Server
    ......
    def initaialize(*)
        super
        set_environment
    end
    ......
    def set_environment
        ENV["RAILS_ENV"] ||= options[:environment]
    end
    ......
end

fileutils和optparse是Ruby的標準庫,提供文件炒做相關和解析元素的幫助方法。

action_dispatch,爲actionpack/lib/action_dispatch.rb文件,ActionDispatch是Rails框架的路由組件,它增長了像路由,會話,通常中間件的功能。

 Rails::Server.new實際上就是建立一個Rack::Server的實例再爲ENV["RAILS_ENV"]賦值。

Rack::Server負責爲所有的以Rack爲基礎的引用提供通用的server接口

Rack:lib/rack/server.rb

def initialize(options = nil)
  @options = options
  @app = options[:app] if options && options[:app]
end

實際上,options參數是nil的,這個構造方法中什麼都不會發生

再來看set_environment方法,在方法中並無找到options這個局部變量,說明這是一個方法調用,其實他的方法定義在Rack::Server中,以下:

def options
  @options ||= parse_options(ARGV)
end

而後parse_options是像這樣定義的:

def parse_options(args)
  options = default_options
 
  # Don't evaluate CGI ISINDEX parameters.
  # http://www.meb.uni-bonn.de/docs/cgi/cl.html
  args.clear if ENV.include?("REQUEST_METHOD")
 
  options.merge! opt_parser.parse!(args)
  options[:config] = ::File.expand_path(options[:config])
  ENV["RACK_ENV"] = options[:environment]
  options
end

default_options:

def default_options
  environment  = ENV['RACK_ENV'] || 'development'
  default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
 
  {
    :environment => environment,
    :pid         => nil,
    :Port        => 9292,
    :Host        => default_host,
    :AccessLog   => [],
    :config      => "config.ru"
  }
end

如今ENV中尚未REQUEST_METHOD鍵,因此先跳過args.clear,下面一行options merge了定義在Rack::Server中方法返回的對象的parse!方法的返回值

opt_parser:

def opt_parser
  Options.new
end

Options這個類定義在Rack::Server中,可是在Rails::Server中被重寫,重寫後的parse!方法:

def parse!(args)
  args, options = args.dup, {}
 
  opt_parser = OptionParser.new do |opts|
    opts.banner = "Usage: rails server [mongrel, thin, etc] [options]"
    opts.on("-p", "--port=port", Integer,
            "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
  ...

這個方法將爲那些Rails可使用來肯定服務應該怎麼運行的參數設置好鍵。到這裏initialize結束,咱們跳到rails/server中require APP_PATH的地方

當require APP_PATH被執行,config/application.rb將會被加載。這個文件在你的與應用中用於更具你的須要只有的給變配置

Rails::Server#start

當config/application被加載後,server.start被調用,這個方法定義以下:

def start
  print_boot_information
  trap(:INT) { exit }
  create_tmp_directories
  log_to_stdout if options[:log_stdout]
 
  super
  ...
end
 
private
 
  def print_boot_information
    ...
    puts "=> Run `rails server -h` for more startup options"
    ...
    puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
  end
 
  def create_tmp_directories
    %w(cache pids sessions sockets).each do |dir_to_make|
      FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make))
    end
  end
 
  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
 
    Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
  end

print_boot_information這裏將會是Rails啓動以來第一次答應信息,start方法爲INT信號建立了一個trap,因此只要你按住CTRL-C,將會退出進程。經過後面的create_tmp_directories方法,將會建立tmp/cache,tmp/session和tmp/sockets目錄。以後會調用wrapped_app方法,用於在建立和分配一個ActiveSupport::Logger實例以前。

super方法將會調用Rack::Server.start這個方法,以下:

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

Rails應用有趣的部分就是在最後一行,server.run。這裏咱們再次遇到了wrapped_app方法。

wrapped_app:

def wrapped_app
    @wrpped_app ||= build_app app
end

def app
  @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
end
...
private
  def build_app_and_options_from_config
    if !::File.exist? options[:config]
      abort "configuration #{options[:config]} not found"
    end
 
    app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
    self.options.merge! options
    app
  end
 
  def build_app_from_string
    Rack::Builder.new_from_string(self.options[:builder])
  end

option[:config]值默認是config.ru文件的位置,config.ru包含如下代碼:

# This file is used by Rack-based servers to start the application.
 
require ::File.expand_path('../config/environment', __FILE__)
run <%= app_const %>

Rack::Builder.parse_file方法在這裏獲取到config.ru文件的內容而後解析他,用了下面的代碼:

app = new_from_string cfgfile, config
 
...
 
def self.new_from_string(builder_script, file="(rackup)")
  eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
    TOPLEVEL_BINDING, file, 0
end

Rack::Builder的初始化方法會接受一個代碼塊同時在Rack::Builder的一個實例中執行它。這就是大多數Rails發生的初始化過程,當執行config.ru的代碼時首先會require config/environment.rb

config/environment.rb

這個文件被config.ru(rails server)和Passenger require的共同文件。

這個文件最開始require 了config/application.rb

config/application.rb

這個文件require了config/boot.rb

相關文章
相關標籤/搜索