本篇博文會從使用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
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