来自rack-2.0.1

Builder

module Rack

  class Builder
    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

    # default_app = nil主要是为了generate_map中
    # 可方便地直接写self.class.new(default_app, &b)
    #
    # 同时,这也使“对特定路径添加middleware,
    # 但不切换另一app来处理请求”的需求得以实现:
    #
    # use MW_ALL
    #
    # map '/location_b' do
    #   use MW_B
    # end
    #
    # run app
    #
    # 但这时app中不应有对/location_b的特殊处理
    # 因为URLMap会将/location_b截掉
    # app将无法通过path_info探知请求是否来自/location_b
    # 对该路径的特殊处理都该用MW_B来做
    #
    # 当然,你可以在该block中用run来切换另一app

    def initialize(default_app = nil, &block)
      @use, @map, @run, @warmup = [], nil, default_app, nil
      instance_eval(&block) if block_given?
    end

    # new方法和app方法的区别在于
    # app就是返回一个层层middleware包裹的rack应用(通过调一次to_app)
    # 而new返回的仍是一个builder,它能响应call(每次都调to_app,每次生一堆middleware对象),
    # 也仍可在运行时方便地继续调use、map、run来改变其表现(这用途似乎很复杂)
    # 另外,rack.multithread默认是true的,若需middleware使用实例变量来作context variable,则应new
    # 不过核心的@run是始终只有一个的

    def self.app(default_app = nil, &block)
      self.new(default_app, &block).to_app
    end

    # 当此次use之前有map时,先将那些map用generate_map包装成middleware
    # @map其实只是一个context variable
    #
    # 因为to_app中会作@use.reverse.inject(app) { |a,e| e[a] }
    # 所以@use收藏proc { |app| ...

    def use(middleware, *args, &block)
      if @map
        mapping, @map = @map, nil
        @use << proc { |app| generate_map app, mapping }
      end
      @use << proc { |app| middleware.new(app, *args, &block) }
    end

    def run(app)
      @run = app
    end

    # Takes a lambda or block that is used to warm-up the application.
    #
    #   warmup do |app|
    #     client = Rack::MockRequest.new(app)
    #     client.get('/')
    #   end
    #
    #   use SomeMiddleware
    #   run MyApp
    def warmup(prc=nil, &block)
      @warmup = prc || block
    end

    def map(path, &block)
      @map ||= {}
      @map[path] = block
    end

    # 如果有map,且最后一堆map之后没有use
    # 则将该堆map包装成middleware
    # 再将剩余的use一层层地包裹

    def to_app
      app = @map ? generate_map(@run, @map) : @run
      fail "missing run or map statement" unless app
      app = @use.reverse.inject(app) { |a,e| e[a] }
      @warmup.call(app) if @warmup
      app
    end

    def call(env)
      to_app.call(env)
    end

    private

    # 对于每一个mapping规则,都将block转换成builder
    # 因为是builder,所以在block中可定义run和use,甚至嵌套map
    # 然后用URLMap包装这一堆location=>builder
    # URLMap也相当于一个符合rack标准,能被call的app

    def generate_map(default_app, mapping)
      mapped = default_app ? {'/' => default_app} : {}
      mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
      URLMap.new(mapped)
    end
  end
end


URLMap

module Rack

  class URLMap
    NEGATIVE_INFINITY = -1.0 / 0.0
    INFINITY = 1.0 / 0.0

    def initialize(map = {})
      remap(map)
    end

    # 路径越长的越靠前
    def remap(map)
      @mapping = map.map { |location, app|
        if location =~ %r{\Ahttps?://(.*?)(/.*)}
          host, location = $1, $2
        else
          host = nil
        end

        unless location[0] == ?/
          raise ArgumentError, "paths need to start with /"
        end

        location = location.chomp('/')
        match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')

        [host, location, match, app]
      }.sort_by do |(host, location, _, _)|
        [host ? -host.size : INFINITY, -location.size]
      end
    end

    def call(env)
      path        = env[PATH_INFO]
      script_name = env[SCRIPT_NAME]
      http_host   = env[HTTP_HOST]
      server_name = env[SERVER_NAME]
      server_port = env[SERVER_PORT]

      is_same_server = casecmp?(http_host, server_name) ||
                       casecmp?(http_host, "#{server_name}:#{server_port}")

      @mapping.each do |host, location, match, app|
        unless casecmp?(http_host, host) \
            || casecmp?(server_name, host) \
            || (!host && is_same_server)
          next
        end

        next unless m = match.match(path.to_s)

        # 截取剩余的相对路径,传给app,短一点方便一点
        rest = m[1]
        next unless !rest || rest.empty? || rest[0] == ?/

        env[SCRIPT_NAME] = (script_name + location)
        env[PATH_INFO] = rest

        return app.call(env)
      end

      [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]

    ensure
      env[PATH_INFO]   = path
      env[SCRIPT_NAME] = script_name
    end

    private
    def casecmp?(v1, v2)
      # if both nil, or they're the same string
      return true if v1 == v2

      # if either are nil... (but they're not the same)
      return false if v1.nil?
      return false if v2.nil?

      # otherwise check they're not case-insensitive the same
      v1.casecmp(v2).zero?
    end
  end
end