rack

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

你可能感兴趣的:(rack)