rails启动过程(一)代码流程概述

这篇博客探讨了Rails 3的启动过程,从在工程目录下输入`rails s`开始,详细分析了命令背后的执行路径,包括加载railties gem,以及railties gem中的rails脚本如何启动。文章旨在揭示Railtie、engine和application等核心概念在Rails启动中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

Rails 3中出现了很多新的名词,如Railtie,engine,application等,这些都和Rails的启动过程及gem的开发密切相关,由于好奇,想尝试分析整个Rails 3的启动过程,给自己一个交代。


从我们在工程目录下输入:

 rails s 

开始。首先看看这个命令中的rails是什么:

$ which rails

/home/tomwang/.rvm/gems/ruby-1.9.3-p194@rails323/bin/rails


这个启动脚本如下:

#!/usr/bin/env ruby
#
# This file was generated by RubyGems.
#
# The application 'railties' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0"

if ARGV.first
  str = ARGV.first
  str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
  if str =~ /\A_(.*)_\z/
    version = $1
    ARGV.shift
  end
end

gem 'railties', version
load Gem.bin_path('railties', 'rails', version)
这个文件主要做了两件事:
  • 导入railties这个gem,这是rails 3中的核心概念,后面还会对它进行更详尽的分析
  • load railties这个gem下的另一个rails脚本:/home/tomwang/.rvm/gems/ruby-1.9.3-p194@rails323/gems/railties-3.2.3/bin/rails
它的内容如下:
#!/usr/bin/env ruby

if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git'))
  railties_path = File.expand_path('../../lib', __FILE__)
  $:.unshift(railties_path)
end
require "rails/cli"
这个rails主要是require了
"railties-3.2.3/lib/rails/cli.rb "

这个文件,它的内容如下:

require 'rbconfig'
require 'rails/script_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::ScriptRailsLoader.exec_script_rails!

它最后一行执行的函数如下:

module Rails
  module ScriptRailsLoader
    RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
    SCRIPT_RAILS = File.join('script', 'rails')
  
    def self.exec_script_rails!
      cwd = Dir.pwd
      return unless in_rails_application? || in_rails_application_subdirectory?
      exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? # 终于执行到了我们工程目录下的script/rails
      Dir.chdir("..") do      
        # Recurse in a chdir block: if the search fails we want to be sure
        # the application is generated in the original working directory.
        exec_script_rails! unless cwd == Dir.pwd
      end
    rescue SystemCallError                                                                                         
      # could not chdir, no problem just return
    end
                                                                                                                   
    def self.in_rails_application?                                                                                 
      File.exists?(SCRIPT_RAILS)                                                                                   
    end
      
    def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd))                                      
      File.exists?(File.join(path, SCRIPT_RAILS)) || !path.root? && in_rails_application_subdirectory?(path.parent)
    end 
  end   
end   
由上面注释行可见,最后我们自己工程目录下的script/rails被执行了,它的内容如下:
#!/usr/bin/env ruby                                                                                                 
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.         
APP_PATH = File.expand_path('../../config/application',  __FILE__)                            
require File.expand_path('../../config/boot',  __FILE__)                                                            
require 'rails/commands'                                                                                            
它定义APP_PATH,即我们的项目文件:config/application.rb,并require了config/boot,boot文件主要是做了bundle的初始化。然后,rails/commands(railties-3.2.3/lib/rails/commands.rb)文件被load进来,由于我们传入的command参数为s,也就是server,所以我们只展示对应的代码:
when 'server'                                                                                       
  # Change to the application's path if there is no config.ru file in current dir.                  
  # This allows us to run script/rails server from other directories, but still get                 
  # the main config.ru and properly set the tmp directory.                                          
  Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru"))
                                                                                                    
  require 'rails/commands/server'                                                                   
  Rails::Server.new.tap { |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                                                                                    
  }                                                                                                 
server.start函数如下,它打印出了我们熟悉的控制台启动信息:
def start                                                                        
  url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
  puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"               
  puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
  puts "=> Call with -d to detach" unless options[:daemonize]
  trap(:INT) { exit }
  puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
  #Create required tmp directories if not found                                  
  %w(cache pids sessions sockets).each do |dir_to_make|                          
    FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))                       
  end                                                                            
  
  puts 'server start ---'
  super
ensure
  # The '-h' option calls exit before @options is set.                           
  # If we call 'options' with it unset, we get double help banners.              
  puts 'Exiting' unless @options && options[:daemonize]                          
end                                                                              
最后的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

  # 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方法最终调用Rack::Server#app:
def app
  @app ||= begin                                                              
    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
end   
由于我们没有指定:config命令行参数,系统将取默认参数,及工程目录下的:config.ru,这个文件和rack(后面有详细论述)有关。这行代码将导入config.ru,其内容如下:
require ::File.expand_path('../config/environment',  __FILE__)
run Tao800Fire::Application                                   
它将再导入config/environment.rb文件,在其中,我们的程序将完成初始化:
# Load the rails application                        
require File.expand_path('../application', __FILE__)
                                                    
# Initialize the rails application                  
Tao800Fire::Application.initialize!
初始化完成之后,我们回到 Rack::Server#start的最后一行:
server.run wrapped_app, options, &blk
由于我们没有指定:server命令行参数,rack为我们选定默认的server:
def server                                                                       
  @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
end                                                                              
def self.default(options = {})        
  # Guess.                            
  if ENV.include?("PHP_FCGI_CHILDREN")
    # We already speak FastCGI        
    options.delete :File              
    options.delete :Port              
                                      
    Rack::Handler::FastCGI            
  elsif ENV.include?("REQUEST_METHOD")
    Rack::Handler::CGI                
  else                                
    begin                             
      Rack::Handler::Thin             
    rescue LoadError                  
      Rack::Handler::WEBrick          
    end                               
  end                                 
end                                   
一般来说是WEBrick,除非你安装了其他application server。然后WEBrick启动,打出了如下信息:
[2013-03-11 17:30:11] INFO  WEBrick 1.3.1
[2013-03-11 17:30:11] INFO  ruby 1.9.3 (2012-04-20) [x86_64-linux]
[2013-03-11 17:30:11] INFO  WEBrick::HTTPServer#start: pid=28371 port=3000
至此,整个启动过程就完成了,后面的篇章将尝试详细解释其中的一些关键步骤,并介绍其中涉及的一些重要概念。










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值