Rails 是怎么启动的
1 启动 rails
执行rails server
或rails s
命令后,执行脚本./bin/rails
:
1.1 bin/rails
#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"
在bin/rails
中引入了config/boot.rb
和rails/commands.rb
,作用分别如下所述。
1.2 config/boot.rb
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler/setup" # Set up gems listed in the Gemfile.
require "bootsnap/setup" # Speed up boot time by caching expensive operations.
第一句负责将ENV["BUNDLE_GEMFILE"]
设置为当前项目Gemfile
的位置,加载Bundler
,接着引入"bundler/setup"
来为 Bundler 提供 Gemfile 所需的依赖(dependencies)。
1.3 rails/commands.rb
在config/boot.rb
执行完后,bin/rails
继续引入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
首先,rails/commands.rb
会帮助我们扩展别名(如:rails s
扩展成rails server
);
接着,调用模块Rails
下的子模块Command
的invoke
方法。
1.4 rails/command.rb
module Rails
module Command
class << self
def invoke(full_namespace, args = [], **config)
namespace = full_namespace = full_namespace.to_s
if char = namespace =~ /:(\w+)$/
command_name, namespace = $1, namespace.slice(0, char)
else
command_name = namespace
end
command_name, namespace = "help", "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name)
command_name, namespace = "version", "version" if %w( -v --version ).include?(command_name)
command = find_by_namespace(namespace, command_name)
if command && command.all_commands[command_name]
command.perform(command_name, args, config)
else
find_by_namespace("rake").perform(full_namespace, args, config)
end
end
end
end
end
代码实现检索你所输入的rails xxx
命令,如:
command_name, namespace = "help", "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name)
该代码实现了当 namespace 为空,即输入的命令为rails
时,返回 rails 的 help 界面。
如果执行的命令是rails server
,则会继续执行同样在模块Rails
下的子模块Command
中子类ServerCommand
的perform
方法:
1.5 rails/commands/server/server_command.rb
module Rails
module Command
class ServerCommand < Base # :nodoc:
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
# ......
end
end
end
-
set_application_directory!
:此时,如果在config.ru
中没有配置root
路径,则会将此文件的目录配置为网页路由的根目录。这就是为什么在使用默认跟路由的情况下,点击 http://localhost:3000 后看到的是下面这个默认界面: -
Rails::Server.new(server_options).tap do |server| ......
:创建Rails::server
类对象,部分代码如下:module Rails class Server < ::Rack::Server def initialize(options = nil) @default_options = options || {} super(@default_options) set_environment end end end
该类
Rails::Server
继承自Rack::Server
,在创建该类对象时调用构造方法initialize
。首先通过
super(@default_options)
调用父类Rack::Server
的构造方法:
1.6 Rack: lib/rack/server.rb
Rack::Server
对基于 Rack 的 rails 应用提供了一组接口。
首先,initialize
方法初始化了一系列变量:
module Rack
class Server
def initialize(options = nil)
@ignore_options = []
if options
@use_default_options = false
@options = options
@app = options[:app] if options[:app]
else
argv = defined?(SPEC_ARGV) ? SPEC_ARGV : ARGV
@use_default_options = true
@options = parse_options(argv)
end
end
end
end
Rails::Command::ServerCommand#server_options
:模块 Rails
子模块 Command
中 ServerCommand
类的 server_options
方法的返回值会被赋给实例变量 @ignore_options
。
该方法代码返回值包含了服务器、是否在标准输出打印日志、host、port等用户配置的选项信息,代码如下:
module Rails
module Command
class ServerCommand
no_commands do
def server_options
{
user_supplied_options: user_supplied_options,
server: using,
log_stdout: log_to_stdout?,
Port: port,
Host: host,
DoNotReverseLookup: true,
config: options[:config],
environment: environment,
daemonize: options[:daemon],
pid: pid,
caching: options[:dev_caching],
restart_cmd: restart_command,
early_hints: early_hints
}
end
end
end
end
end
父类Rack::Server
构造方法执行完后,回到rails/commands/server/server_command.rb
继续执行构造方法initialize
中的set_environment
方法:
module Rails
module Server
def set_environment
ENV["RAILS_ENV"] ||= options[:environment]
end
end
end
构造完成。
1.7 Rails::Server#start
加载 config/application.rb
后,调用模块 Rails
中类Server
的start
方法:
module Rails
class Server < ::Rack::Server
def start(after_stop_callback = nil)
trap(:INT) { exit }
create_tmp_directories
setup_dev_caching
log_to_stdout if options[:log_stdout]
super()
# ...
end
# ......
其中,trap(:INT) { exit }
保证控制台能够捕捉中断CTRL + C
信号来停止服务器;create_tmp_directories
会生成tmp/cache
, tmp/pids
, 和 tmp/sockets
目录;同时,setup_dev_caching
允许缓存数据。最后,调用父类Rack::Server.start
方法(这里略去代码)。在父类代码中会用到options[:config]
变量,它的值由config.ru
决定,Rack::Builder.parse_file
方法会解析config.ru
的内容。config.ru
代码如下
1.8 config.ru
require_relative "config/environment"
run Rails.application
Rails.application.load_server
首先引入config/environment.rb
1.9 config/environment.rb
# Load the Rails application.
require_relative "application"
# Initialize the Rails application.
Rails.application.initialize!
引入config/application
注意:
config/application
在之前就已经在bin/rails
中通过APP_PATH = File.expand_path("../config/application", __dir__)
加入到APP_PATH
中,随后在模块Rails
、子模块Command
、类ServerCommand
的perform
方法中通过require APP_PATH
代码加载过config/application
的第一行require_relative "boot"
了!!!此时所有内容已经通过 Rack 和 Rails 设置好了!!!
至此,Rails启动完成。
2 加载rails
2.1 config/application
config/application
第二行代码:
require "rails/all"
引入 railties/lib/rails/all.rb
2.2 railties/lib/rails/all.rb
该文件负责加载 rails 所需的每个文件(gem
包):
require "rails"
%w(
active_record/railtie
active_storage/engine
action_controller/railtie
action_view/railtie
action_mailer/railtie
active_job/railtie
action_cable/engine
action_mailbox/engine
action_text/engine
rails/test_unit/railtie
).each do |railtie|
begin
require railtie
rescue LoadError
end
end
3.3 启动 puma 服务器
此前省略了一部分操作。
通过Rack::Server.run
调用如下run
方法:
module Rack
module Handler
module Puma
# ...
def self.run(app, options = {})
conf = self.config(app, options)
events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio
launcher = ::Puma::Launcher.new(conf, :events => events)
yield launcher if block_given?
begin
launcher.run
rescue Interrupt
puts "* Gracefully stopping, waiting for requests to finish"
launcher.stop
puts "* Goodbye!"
end
end
# ...
end
end
end
成功启动服务器,终端会返回连接状态信息:(下面为执行rails s
的信息)
$ rails s
=> Booting Puma
=> Rails 7.0.4 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 5.6.5 (ruby 3.1.2-p20) ("Birdie's Version")
* Min threads: 5
* Max threads: 5
* Environment: development
* PID: 19684
* Listening on http://[::1]:3000
* Listening on http://127.0.0.1:3000
Use Ctrl-C to stop
参考资料
The Rails Initialization Process
标签:end,启动,Rails,server,rails,config,options From: https://www.cnblogs.com/NormalLLer/p/16808102.html