rack
一个可以相应call方法的ruby对象。
env => rack => [code, header, body]
rack在webserver和应用之间提供了最小的接口,所有的web服务只需要在Rack::Handle中创建一个实现了 .run 方法的类就可以了。
中间件
中间件实现了initialize和call方法,初始化接受两个参数分别是app和options。
rackup
rackup的执行后历程
1.找到rackup的bin文件
require 'rubygems'
version = ">= 0.a"
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
load Gem.activate_bin_path('rack', 'rackup', version)
根据gem名 命令名和版本load了对应的文件
require "rack"
Rack::Server.start
server的启动流程
实例化并且调用start方法
def self.start(options = nil)
new(options).start
end
构造方法里都是options的处理
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
From: lib/rack/server.rb @ line 199:
Owner: Rack::Server
def options
merged_options = @use_default_options ? default_options.merge(@options) : @options
merged_options.reject { |k, v| @ignore_options.include?(k) }
end
From: lib/rack/server.rb @ line 204:
Owner: Rack::Server
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
包装应用
实力的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
其最重要的部分是wrapped_app 和 server.run,包装后的app和开启了web服务.
def wrapped_app
@wrapped_app ||= build_app app
end
这个方法由 build_app和 app组成,让我们来看一下app的整个调用栈.
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)
@options.merge!(options) { |key, old, new| old }
app
end
def self.parse_file(config, opts = Server::Options.new)
options = {}
if config =~ /\.ru$/
cfgfile = ::File.read(config)
if cfgfile[/^#\\(.*)/] && opts
options = opts.parse! $1.split(/\s+/)
end
cfgfile.sub!(/^__END__\n.*\Z/m, '')
app = new_from_string cfgfile, config
else
require config
app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
end
return app, options
end
def self.new_from_string(builder_script, file="(rackup)")
eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
TOPLEVEL_BINDING, file, 0
end
parse_file中通过File.read读取config.ru的代码通过eval执行,config.ru的代码,最终得到了app.
build_app 根据运行环境给app包装上一些基础的中间件
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
这样就完成了wrapped_app.
让我们看看config.ru中use 和 run的DSL实现
首先是config.ru 和eval组合后的代码
Rack::Builder.new {
use StatusLogger
use BodyTransformer, count: 3
run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['get rack\'d']] }
}.to_app
初始化一些变量
def initialize(default_app = nil, &block)
@use, @map, @run, @warmup = [], nil, default_app, nil
instance_eval(&block) if block_given?
end
use将中间件加入到数组中
def use(middleware, *args, &block)
@use << proc { |app| middleware.new(app, *args, &block) }
end
run的作用很简单只是把初始的rackapp复制给 @run
def run(app)
@run = app
end
to_app 递归将app包装上中间件
def to_app
fail "missing run or map statement" unless @run
@use.reverse.inject(@run) { |a,e| e[a] }
end
选择合适的webserver
有options根据options 没有获取默认
def server
@_server ||= Rack::Handler.get(options[:server])
unless @_server
@_server = Rack::Handler.default
end
@_server
end
def self.default
# Guess.
if ENV.include?("PHP_FCGI_CHILDREN")
Rack::Handler::FastCGI
elsif ENV.include?(REQUEST_METHOD)
Rack::Handler::CGI
elsif ENV.include?("RACK_HANDLER")
self.get(ENV["RACK_HANDLER"])
else
pick ['puma', 'thin', 'webrick']
end
end
def self.pick(server_names)
server_names = Array(server_names)
server_names.each do |server_name|
begin
return get(server_name.to_s)
rescue LoadError, NameError
end
end
raise LoadError, "Couldn't find handler for: #{server_names.join(', ')}."
end
def self.get(server)
return unless server
server = server.to_s
unless @handlers.include? server
load_error = try_require('rack/handler', server)
end
if klass = @handlers[server]
klass.split("::").inject(Object) { |o, x| o.const_get(x) }
else
const_get(server, false)
end
rescue NameError => name_error
raise load_error || name_error
end
一路选择合适webserver
webserver启动
module Rack
module Handler
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
def self.run(app, options={})
environment = ENV['RACK_ENV'] || 'development'
default_host = environment == 'development' ? 'localhost' : nil
options[:BindAddress] = options.delete(:Host) || default_host
options[:Port] ||= 8080
@server = ::WEBrick::HTTPServer.new(options)
@server.mount "/", Rack::Handler::WEBrick, app
yield @server if block_given?
@server.start
end
end
end
end