##################
##################
#!/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/ and Gem::Version.correct?($1) then
version = $1
ARGV.shift
end
end
gem 'railties', version
load Gem.bin_path('railties', 'rails', version)
########
#!/usr/bin/env ruby_executable_hooks
#
# This file was generated by RubyGems.
#
# The application 'rspec-core' 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/ and Gem::Version.correct?($1) then
version = $1
ARGV.shift
end
end
gem 'rspec-core', version
load Gem.bin_path('rspec-core', 'rspec', version)
#$HOME/.rvm/gems/ruby-2.1.2/gems/railties-4.1.1/bin
#!/usr/bin/env ruby
git_path = File.expand_path('../../../.git', __FILE__) # 这个方法和奇葩,我总是搞不明白, 从当前文件算起,往上三层.
#-----$HOME/.rvm/gems/ruby-2.1.2/gems/railties-4.1.1/bin/rails
#`--- $HOME/.rvm/gems/ruby-2.1.2/gems/.git
if File.exist?(git_path)
railties_path = File.expand_path('../../lib', __FILE__)
#----$HOME/.rvm/gems/ruby-2.1.2/gems/railties-4.1.1/bin/rails
# `--$HOME/.rvm/gems/ruby-2.1.2/gems/railties-4.1.1/lib
$:.unshift(railties_path)
end
require "rails/cli" #接着就是这里
~/.rvm/gems/ruby-2.1.2/gems/railties-4.1.1/lib/rails/cli.rb
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
# 这个 exec_app_rails 方法干什么的呢?
# 我们接着看rails/app_rails_loader这个文件
require 'rails/ruby_version_check'
Signal.trap("INT") { puts; exit(1) }
if ARGV.first == 'plugin'
ARGV.shift
require 'rails/commands/plugin'
else
require 'rails/commands/application'
end
require 'pathname'
module Rails
module AppRailsLoader
RUBY = Gem.ruby
EXECUTABLES = ['bin/rails', 'script/rails']
BUNDLER_WARNING = <<EOS
Looks like your app's ./bin/rails is a stub that was generated by Bundler.
In Rails 4, your app's bin/ directory contains executables that are versioned
like any other source code, rather than stubs that are generated on demand.
Here's how to upgrade:
bundle config --delete bin # Turn off Bundler's stub generator
rake rails:update:bin # Use the new Rails 4 executables
git add bin # Add bin/ to source control
You may need to remove bin/ from your .gitignore as well.
When you install a gem whose executable you want to use in your app,
generate it and add it to source control:
bundle binstubs some-gem-name
git add bin/new-executable
EOS
def self.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 self.find_executable
EXECUTABLES.find { |exe| File.file?(exe) }
end
end
end
Looks like your app's ./bin/rails is a stub that was generated by Bundler.
In Rails 4, your app's bin/ directory contains executables that are versioned
like any other source code, rather than stubs that are generated on demand.
Here's how to upgrade:
bundle config --delete bin # Turn off Bundler's stub generator
rake rails:update:bin # Use the new Rails 4 executables
git add bin # Add bin/ to source control
You may need to remove bin/ from your .gitignore as well.
When you install a gem whose executable you want to use in your app,
generate it and add it to source control:
bundle binstubs some-gem-name
git add bin/new-executable
在 rails 3
里有个 script
文件夹,那个里面有 rails
命令.
而在4里会 bin
这个目录
这个地方会有之前 代替 script/rails
的东西.
EXECUTABLES = ['bin/rails', 'script/rails']
def self.find_executable EXECUTABLES.find { |exe| File.file?(exe) } end
看看这个 文件内容:
#!/usr/bin/env ruby
begin
load File.expand_path("../spring", __FILE__)
rescue LoadError
end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
这个里面有 APP_PATH
, 可以看见.
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) # 基于 app_path, 获取 boot 文件,并加载
require 'rails/commands' #下面我们研究这个.
break
end
end
兜了一圈都是 rails 自己的事情,现在终于要回到app based on rails
了, 为什么这么说呢,因为你之前的这些都看不见的啊.
上面的代码段中提到的 app_path
了. 什么 config/application
了 什么 config/boot
了, 这些相信抛开之前 rails 自己的代码,都能够大概明白什么意思了,到了这一步,也就是当前项目本身的一些初始化了.
我们先看看 require 的这个 boot 文件,内容是什么的呢?
In a standard Rails application, there's a Gemfile which declares all dependencies of the application. config/boot.rb sets ENV['BUNDLE_GEMFILE'] to the location of this file. If the Gemfile exists, then bundler/setup is required. The require is used by Bundler to configure the load path for your Gemfile's dependencies.
这个说明什么了呢,为什么叫 boot
这个名字呢?为什么,便可知,其实 boot 就是一些之根本了,就是从当前文件位置获取到 Gemfile 的位置,然后查看是否存在,然后利用 bundle
来管理本项目的依赖
接下来我们看看这个文件
rails/commands
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)
上面的代码有2目的:
- 给一些命令添加别名,好使!
require 'rails/commands/commands_tasks'
这个文件作用有2:
- 如果给的命令不在 list 中就负责扔一个异常帮助信息给他.
- 命令对了就继续往下执行那个命令.
我们现在在研究 rails server
这个命令,那我们继续往下:
# Change to the application's path if there is no config.ru file in current directory.
# This allows us to run `rails server` from other directories, but still get
# the main config.ru and properly set the tmp directory.
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! #这个可以让你在其他命令也可以执行 rails 命令
require_command!("server")
Rails::Server.new.tap do |server|
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
end
end
def require_command!(command)
require "rails/commands/#{command}"
end
看看这个 rails/commands/server.rb
require 'fileutils' #
require 'optparse' #此二就是 ruby 里正常库了. 用来处理文件和解析参数之用!!!
require 'action_dispatch' # 这个就是我们接下来研究的.
module Rails
class Server < ::Rack::Server #只简单继承,没有任何实现.
直接这样require 'action_dispatch'
会在哪里呢?
首先这个actionpack
它是作为单独的 gem 存在的.是 rails 框架的一个组件,他的作用呢,就是为了负责路由,会话, 还有还有一些通用中间件的工作.
这里有个以为,require 之后发生了什么?没有讲!
接下来还是回到 server.rb 这个文件里.
大概看了一眼 action_dispatch
, 这个加载了老多东西啊. action_support 啊 , rack 啊!
还是回到主要的. Rails::Server 急继承自 Rack::Server 的. 在 Rails::Server 被 new 的时候,调用初始化方法:
def initialize(*)
super
set_environment #重点!!!
end
- 首先调用的就是 Rack::Server 的初始化方法了:
def initialize(options = nil)
@options = options
@app = options[:app] if options && options[:app]
end
这个地方 options
就是空的,什么都没有发生,还是回到 Rails::Server
那里去.
看看这个看上去简单的方法做了些什么呢?乍一看,其实什么都没有的.
def set_environment
ENV["RAILS_ENV"] ||= options[:environment] #这个地方坑爹啊,看上去 `options` 就是个数组是不是? 还复数的, 殊不知™是方法! shit!
end
而事实上定义在Rack::Server
里的这个 options
方法牛了逼了,干了很多活的!我们来瞧瞧:
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",
pid: nil,
Port: 9292,
Host: "0.0.0.0",
AccessLog: [],
config: "config.ru"
}
end
def opt_parser
Options.new #这个类定义在 Rack::Server 里
end
#但是它的方法 `parse!` 在 Rails::Server 里被重写了.
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 }
...
至此,要回到最初提到 APP_PATH
的config/application
了.
到这里,这个 config/application.rb
就要被执行了.
看到这么一句话
1.11 Rails::Server#start
After config/application is loaded, server.start is called. This method is defined like this:
哪里来的 server.start
呢?我就莫名其妙不是.原来尼玛就在上面呢.
Rails::Server.new.tap do |server|
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
end
看见了吧, server.start
啊!
他就是在 之前提到的 rails/commands/server.rb
里
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
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 "=> Run `rails server -h` for more startup options"
if options[:Host].to_s.match(/0\.0\.0\.0/)
puts "=> Notice: server is listening on all interfaces (#{options[:Host]}). Consider using 127.0.0.1 (--binding option)"
end
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
上面代码干了几件事情:
完了之后,调用能够了 super 的 start 方法, 在 Rack:Server 里:
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
有意思的部分就是最后一行了:server.run wrapped_app, options, &blk
它又调用了一次 wrapped_app
.看看究竟
def app
@app ||= begin
app = super
app.respond_to?(:to_app) ? app.to_app : app
end
end
多次用到这个 options[:config],它默认的就是指向 config.ru
的.
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
run Rails.application
上面有提到了 config/environment
文件,我们再次回到熟悉的地方:
这个地方算是一个原点
,不管是那种 app server 启动,都是跑完 rails 之后再从这个出发.
首先就是 require config/application
而这个里面又从 config/boot
开始.当然我们都知道刚才从 rails server 那种方式,他已经跑过了,而以 passenger 启动的就还需要跑.
#config/application.rb
再往下就是 require 'rails/all'
了
这个文件railties/lib/rails/all.rb
可以看出就是加载组件了:
require "rails"
%w(
active_record
action_controller
action_mailer
rails/test_unit
sprockets
).each do |framework|
begin
require "#{framework}/railtie"
rescue LoadError
end
end
再往下执行就是 Rails.application 的 configuration 了. 走完上面的
#再回到 config/environment
你会发现有这么一个
APP_NAME::Application.initialize!
这个 initialize!
方法在哪里?
###railties/lib/rails/application.rb
def initialize!(group=:default) #:nodoc:
raise "Application has been already initialized." if @initialized
run_initializers(group, self)
@initialized = true
self
end
####railties/lib/rails/initializable.rb
def run_initializers(group=:default, *args)
return if instance_variable_defined?(:@ran)
initializers.tsort_each do |initializer|
initializer.run(*args) if initializer.belongs_to?(group)
end
@ran = true
end
到这里,我们还要回到 Rack::Server 那里去
至此, 这个 app
对象就是 Rails app 了.接下来就是 Rack 和中间件的事情了.
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
#记住上面的 build_app 它是有 wrapped_app
调用的.而 server.run
这个实现就取决于你用哪个 appserver 了.如果你用Mongrel,那实现就如下:
def self.run(app, options={})
server = ::Mongrel::HttpServer.new(
options[:Host] || '0.0.0.0',
options[:Port] || 8080,
options[:num_processors] || 950,
options[:throttle] || 0,
options[:timeout] || 60)
# Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
# Use is similar to #run, replacing the app argument with a hash of
# { path=>app, ... } or an instance of Rack::URLMap.
if options[:map]
if app.is_a? Hash
app.each do |path, appl|
path = '/'+path unless path[0] == ?/
server.register(path, Rack::Handler::Mongrel.new(appl))
end
elsif app.is_a? URLMap
app.instance_variable_get(:@mapping).each do |(host, path, appl)|
next if !host.nil? && !options[:Host].nil? && options[:Host] != host
path = '/'+path unless path[0] == ?/
server.register(path, Rack::Handler::Mongrel.new(appl))
end
else
raise ArgumentError, "first argument should be a Hash or URLMap"
end
else
server.register('/', Rack::Handler::Mongrel.new(app))
end
yield server if block_given?
server.run.join
end
#THE END