rack
一個(gè)可以相應(yīng)call方法的ruby對(duì)象。
env => rack => [code, header, body]
rack在webserver和應(yīng)用之間提供了最小的接口缆巧,所有的web服務(wù)只需要在Rack::Handle中創(chuàng)建一個(gè)實(shí)現(xiàn)了 .run 方法的類就可以了十减。
中間件
中間件實(shí)現(xiàn)了initialize和call方法,初始化接受兩個(gè)參數(shù)分別是app和options享甸。
rackup
rackup的執(zhí)行后歷程
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)
根據(jù)gem名 命令名和版本load了對(duì)應(yīng)的文件
require "rack"
Rack::Server.start
server的啟動(dòng)流程
實(shí)例化并且調(diào)用start方法
def self.start(options = nil)
new(options).start
end
構(gòu)造方法里都是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
包裝應(yīng)用
實(shí)力的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和開(kāi)啟了web服務(wù).
def wrapped_app
@wrapped_app ||= build_app app
end
這個(gè)方法由 build_app和 app組成嗽冒,讓我們來(lái)看一下app的整個(gè)調(diào)用棧.
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中通過(guò)File.read讀取config.ru的代碼通過(guò)eval執(zhí)行猖任,config.ru的代碼,最終得到了app.
build_app 根據(jù)運(yùn)行環(huán)境給app包裝上一些基礎(chǔ)的中間件
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實(shí)現(xiàn)
首先是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將中間件加入到數(shù)組中
def use(middleware, *args, &block)
@use << proc { |app| middleware.new(app, *args, &block) }
end
run的作用很簡(jiǎn)單只是把初始的rackapp復(fù)制給 @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根據(jù)options 沒(méi)有獲取默認(rèn)
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啟動(dòng)
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