<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>mr_zou123 (mr zou)</title>
    <link>https://ruby-china.org/mr_zou123</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Rails 中 Cookie 和 Session 的关联</title>
      <description>&lt;p&gt;&lt;a href="https://mrzou.github.io/ruby-warden" rel="nofollow" target="_blank" title=""&gt;上一节&lt;/a&gt;介绍了 warden 是如何验证用户的登录授权身份，但是对于 cookie 和 session 之间是如何生成的，是如何产生关联的，然后是怎么通过保存 session 数据到 cookie 中的还有点模糊。总结分享下 Rails 中是如何处理 cookie 和 session 的，有不正确的地方欢迎大家指出&lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;/p&gt;
&lt;h3 id="Rails中Session和Cookie是如何关联的"&gt;Rails 中 Session 和 Cookie 是如何关联的&lt;/h3&gt;
&lt;p&gt;session 可以有多种保存方式，可以通过保存在 cookie 中，也可以通过 cache_store 或者 activerecord_session_store(现在用独立的&lt;a href="https://github.com/rails/activerecord-session_store" rel="nofollow" target="_blank" title=""&gt;gem&lt;/a&gt;来实现了) 的方式去保存。如果是通过 cookie 保存的方式，则会把 session 中设置的键值对通过处理后放到一个特定的 key 下面，默认是&lt;code&gt;项目名_session&lt;/code&gt;。每次浏览器发出请求，都会带上该 cookie 键值对，然后服务端通过解出那串 cookie 值来识别设置 session。而数据库或者用 cache 的方式都是通过把请求中的 session_id 解析出来，再去数据库查找对应的 session 数据。下面具体讨论 session 存储在 cookie 中的方式。&lt;/p&gt;
&lt;h4 id="Rails中设置Session存储方式"&gt;Rails 中设置 Session 存储方式&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.rvm/gems/ruby-2.4.3/gems/railties-5.1.6.2/lib/rails/application/finisher.rb
initializer :setup_default_session_store, before: :build_middleware_stack do |app|
  unless app.config.session_store?
    app_name = app.class.name ? app.railtie_name.chomp("_application") : ""
    app.config.session_store :cookie_store, key: "_#{app_name}_session"
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果在 application.rb 中没有配置 session_store，则会用 cookie_store 作为默认的 session 存储方式，key 默认的名字是&lt;code&gt;_#{app_name}_session&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.rvm/gems/ruby-2.4.3/gems/railties-5.1.6.2/lib/rails/application/default_middleware_stack.rb
middleware.use config.session_store, config.session_options

# ~/.rvm/gems/ruby-2.4.3/gems/railties-5.1.6.2/lib/rails/application/configuration.rb
def session_store(new_session_store = nil, **options)
  ...
  else
    case @session_store
    when :disabled
      nil
    when :active_record_store
      ActionDispatch::Session::ActiveRecordStore
    when Symbol
      ActionDispatch::Session.const_get(@session_store.to_s.camelize)
    else
      @session_store
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面是通过&lt;code&gt;@session_store&lt;/code&gt;实例变量的名字去获取对应的类，而&lt;a href="/session_store" class="user-mention" title="@session_store"&gt;&lt;i&gt;@&lt;/i&gt;session_store&lt;/a&gt;是在执行&lt;code&gt;setup_default_session_store&lt;/code&gt;初始化的时候设置的 cookie_store。&lt;/p&gt;

&lt;p&gt;session_store 是一个 middleware，项目初始化的时候会去初始化一个 session 对象&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ActionDispatch::Session::CookieStore#initialize&lt;/code&gt; =&amp;gt; &lt;code&gt;ActionDispatch::Session::Compatibility#initialize&lt;/code&gt; =&amp;gt; &lt;code&gt;Rack::Session::Abstract::Persisted#initialize&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;上面是一个 middleware 初始化调用栈的过程&lt;/p&gt;

&lt;p&gt;CookieStore 继承了 AbstractStore 类，AbstractStore 继承了 Rack::Session::Abstract::Persisted，这个类是以 middleware 的接口规范去定义的。所以当一个请求发送过来时，会执行到 CookieStore 这个 middleware 上的 call 方法，然后再调用 context 方法。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.rvm/gems/ruby-2.4.3/gems/rack-2.0.6/lib/rack/session/abstract/id.rb

def call(env)
  context(env)
end

def context(env, app=@app)
  req = make_request env
  prepare_session(req) # 在header中设置session
  status, headers, body = app.call(req.env)
  res = Rack::Response::Raw.new status, headers
  commit_session(req, res)
  [status, headers, body]
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的 context 方法可分为执行&lt;code&gt;app.call&lt;/code&gt;，执行&lt;code&gt;app.call&lt;/code&gt;之前和之后这三个部分。在调用&lt;code&gt;app.call&lt;/code&gt;之前是在 header(这里的 header 不是 http 的 header，而是作为项目环境中的一个键值对保存，下面的 header 默认指这个) 中初始化了一个 Session 实例，到时在 controller 中对 session 的键值对的设置都会改变其中的实例变量的值。接着就是执行&lt;code&gt;app.call&lt;/code&gt;等一系列的 middleware，设置好 cookie 和 session。第三部分就是利用第二部分设置好的 session，通过上面的&lt;code&gt;commit_session&lt;/code&gt;方法把 session 中的数据经过处理后 set 到 cookie 中，上面是总的过程，下面分步解析。&lt;/p&gt;
&lt;h4 id="初始化Session实例"&gt;初始化 Session 实例&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/session/abstract_store.rb
def prepare_session(req)
  Request::Session.create(self, req, @default_options)
end

# ~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/request/session.rb
def self.create(store, req, default_options)
  session_was = find req # 查看头部是否有保存rack.session这个header
  session     = Request::Session.new(store, req) # 初始化的实例变量如下
  session.merge! session_was if session_was

  set(req, session) # 把Session实例作为value，rack.session作为key，set到头部中
  Options.set(req, Request::Session::Options.new(store, default_options))
  session
end

def self.find(req)
  req.get_header ENV_SESSION_KEY
end

~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/request/session.rb
def initialize(by, req)
  @by       = by # 存储session的方式
  @req      = req
  @delegate = {} # 存储在session里的key，value值
  @loaded   = false # 记录cookie中的值是否load过到session中了
  @exists   = nil # we haven't checked yet
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面是初始化 session 部分的说明，主要是设置 session 存储方式和设置一些实例变量。同时把 session 实例设置到 header 中。&lt;/p&gt;
&lt;h4 id="设置Session和Cookie"&gt;设置 Session 和 Cookie&lt;/h4&gt;
&lt;p&gt;在执行&lt;code&gt;app.call&lt;/code&gt;时，会执行到一系列 middleware，其中有些 middleware 会调用到一些 cookies 和 session 方法，就是利用这些方法设置了 cookie 和 session 的值。如 warden，用 session 记录了用户的信息。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.rvm/gems/ruby-2.4.3/gems/warden-1.2.8/lib/warden/session_serializer.rb
def store(user, scope)
  ...
  session[key_for(scope)] = specialized ? send(method_name, user) : serialize(user)
end

# ~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/request/session.rb
def []=(key, value)
  load_for_write! # 这个用于把cookies中的session值加载到session实例中
  @delegate[key.to_s] = value
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置 session 之前，会先去检查 cookie 是否 loaded 进 session 中了，如果已经加载过了，则不会再次加载，防止数据再次加载会发生覆盖。如果没有加载过，则会执行 load! 方法加载一次 cookie 的值到 session 中，该过程下面解析。设置 session 的过程其实就是执行 session 中的&lt;code&gt;[]=&lt;/code&gt;方法，在&lt;code&gt;@delegate&lt;/code&gt;实例变量中加入键值对。&lt;/p&gt;

&lt;p&gt;类似的，设置 cookies，也是为实例变量设置键值对。但是 session 的所有键值对设置到 cookies 中是经过处理的，键默认是开头部分提到的&lt;code&gt;_#{app_name}_session&lt;/code&gt;。具体如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_controller/metal/cookies.rb
def cookies # 为controller中定义的cookies方法
  request.cookie_jar
end

# ~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/cookies.rb
def cookie_jar
  fetch_header("action_dispatch.cookies".freeze) do
    self.cookie_jar = Cookies::CookieJar.build(self, cookies)
  end
end

def []=(name, options)
  if options.is_a?(Hash)
    options.symbolize_keys!
    value = options[:value]
  else
    value = options
    options = { value: value }
  end

  handle_options(options)

  if @cookies[name.to_s] != value || options[:expires]
    @cookies[name.to_s] = value
    @set_cookies[name.to_s] = options
    @delete_cookies.delete(name.to_s)
  end

  value
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面就是设置 cookie 方法调用栈的执行过程，通过 request 中的 cookie_jar 找到设置 cookie 的对应方法。cookie_jar 可以理解为装 cookie 的罐子，里面存储着 cookie 的键值对。如果 cookie 需要进行加密或者签名等处理，则会通过调用对应的方法代理到一个类去，然后代理的类用&lt;code&gt;parent_jar&lt;/code&gt;实例变量记录原先的 CookieJar 类，在代理的类上设置的 cookie 就是设置在&lt;code&gt;parent_jar&lt;/code&gt;上面。session 中的键值对放到 cookie 中就是通过代理到 EncryptedCookieJar 类中去，然后经过加密签名处理后再设置到 cookie_jar 中去。&lt;/p&gt;
&lt;h4 id="读取Cookie数据到Session"&gt;读取 Cookie 数据到 Session&lt;/h4&gt;
&lt;p&gt;程序在初始化 session 后，在第一次需要读取 session 中某个 key 的值时，会先把 cookie 中存储 session 的那个键值对读取出来。执行的过程如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/request/session.rb
def load!
  id, session = @by.load_session @req # 把session_id的值和cookie中的数据解析出来，放到@delegate实例变量中去
  options[:id] = id
  @delegate.replace(stringify_keys(session))
  @loaded = true
end

# ~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/session/cookie_store.rb
def load_session(req)
  stale_session_check! do
    data = unpacked_cookie_data(req) # 从cookie中取出数据到session
    data = persistent_session_id!(data)
    [data["session_id"], data]
  end
end

# unpacked_cookie_data =》get_cookie 方法调用栈

def get_cookie(req)
  cookie_jar(req)[@key] # @key值就是所有session的键值对设置到cookie中的name
end

~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/cookies.rb
def [](name)
  if data = @parent_jar[name.to_s]
    parse name, data # 通过调用CookieJar#[]中的方法取出处理过的session键值对，然后通过调用对应的解析方式解析出正确的键值对。
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面是从 cookie 中取出 session 的数据的过程，其实只是读取 cookie 数据的过程，只不过读取的值需要解密和验证一下数据的正确性。&lt;/p&gt;
&lt;h4 id="设置Session数据到Cookie"&gt;设置 Session 数据到 Cookie&lt;/h4&gt;
&lt;p&gt;app 执行 call 方法后，在一些 middleware 中设置的 cookie 或 session。执行 call 方法后，通过 commit_session 方法调用，把 session 中的数据处理后放到 cookie 中去。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.rvm/gems/ruby-2.4.3/gems/rack-2.0.6/lib/rack/session/abstract/id.rb
def commit_session(req, res)
  session_data = session.to_hash.delete_if { |k,v| v.nil? } # 把session中@delegate的数据返回来
  data = write_session(req, session_id, session_data, options) # session里的数据
  ...
  else
    cookie = Hash.new
    cookie[:value] = data
    cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
    cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
    set_cookie(req, res, cookie.merge!(options)) # 放到cookie里面去
  end
end

~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/session/cookie_store.rb
def set_cookie(request, session_id, cookie)
  cookie_jar(request)[@key] = cookie
end

def cookie_jar(request)
  request.cookie_jar.signed_or_encrypted # 代理到EncryptedCookieJar类去处理session的键值对
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;set_cookie&lt;/code&gt;方法中调用的&lt;code&gt;cookie_jar&lt;/code&gt;方法会去检查头部中是否设置了&lt;code&gt;cookie_jar&lt;/code&gt;，如果没有设置，就会通过实例化 Cookies::CookieJar 设置到头部，从而在需要的时候可以使用。Cookies::CookieJar 类主要是用来存储一些 cookies 的键值对，同时为 cookie 值的处理方式提供一个接口，可以通过那些接口对 cookie 进行签名、加密等各种处理方式。&lt;code&gt;set_cookie&lt;/code&gt;方法的调用会回到上面设置平常 cookie 的调用过程，其中 cookie 是已经经过加密，签名的处理过程了，直接设置返回给浏览器就可以了。至此从一个请求过来，把浏览器中 cookie 的 session 部分加载到 session 实例，到重新把 session 的键值对设置到 cookie 中就形成了一个闭环。&lt;/p&gt;
&lt;h3 id="Cookie对数据的处理方式"&gt;Cookie 对数据的处理方式&lt;/h3&gt;
&lt;p&gt;上面是 cookie 和 session 之间的交互关系，下面讨论下 cookie 设置到 http 头部的数据的几种格式。一种是 Cookie 直接对数据进行设置，如&lt;code&gt;cookies[:user_id] = 2&lt;/code&gt;。另外一种是为了防止别有用心的人修改 cookie 的值而设置的，可以用签名的方式设置 cookie，防止此类情况，如 cookies.signed[:user_id] = 2。但是上面签名方式是通过 base64 encode 的，别人可以 decode 出来查看数据，所以第三种方式是通过给数据加密的方式去设置 Cookie，这时候别人没有办法查看，也没有办法更改你的数据。下面介绍下这三种数据的设置方式。&lt;/p&gt;
&lt;h4 id="直接设置Cookie"&gt;直接设置 Cookie&lt;/h4&gt;
&lt;p&gt;这种方式比较简单，可以直接通过 cookies 调用方法去设置 cookie。如&lt;code&gt;cookies[:user_id] = 1&lt;/code&gt;。该数值会直接显示在浏览器上，并且还可以更改数值后提交到服务器端。这种方式的实现是直接调用 CookieJar 的实例方法&lt;code&gt;[]=&lt;/code&gt;来把键值对设置到&lt;code&gt;@cookies&lt;/code&gt;实例变量中的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/cookies.rb, line 265
def []=(name, options)
  if options.is_a?(Hash)
    options.symbolize_keys!
    value = options[:value]
  else
    value = options
    options = { value: value }
  end

  handle_options(options)

  if @cookies[name.to_s] != value || options[:expires]
    @cookies[name.to_s] = value
    @set_cookies[name.to_s] = options
    @delete_cookies.delete(name.to_s)
  end

  value
end
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="用签名的方式设置Cookie"&gt;用签名的方式设置 Cookie&lt;/h4&gt;
&lt;p&gt;这种方式可以防止别人更改 cookie 数据，但是在浏览器上可以查看 cookie 的值。如&lt;code&gt;cookies.signed[:user_id] = 1&lt;/code&gt;，这是在浏览器上显示的 user_id 的值为&lt;code&gt;Mg%3D%3D--2205b41e2e653cc2aba580b6a1213de57a5cc233&lt;/code&gt;，"--"前面的部分为 user_id 用 base64 加密的数值，后面的部分是该值做 hash 计算算出来的结果，主要是为了防止别人篡改前面部分的数据。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/cookies.rb, line 195
def signed
  @signed ||=
    if upgrade_legacy_signed_cookies?
      UpgradeLegacySignedCookieJar.new(self)
    else
      SignedCookieJar.new(self)
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过判断是否同时存在 secret_key_base 和 secret_token 的值决定执行哪种处理方式。如果是 SignedCookieJar 的处理方式，则生成和解析的方法如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/cookies.rb, line 549
def parse(name, signed_message)
  deserialize name, @verifier.verified(signed_message)
end

def commit(options)
  options[:value] = @verifier.generate(serialize(options[:value]))

  raise CookieOverflow if options[:value].bytesize &amp;gt; MAX_COOKIE_SIZE
end
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="用加密的方式设置Cookie"&gt;用加密的方式设置 Cookie&lt;/h4&gt;
&lt;p&gt;第三种方式是上面两种方式的加强，生成的数据在浏览器上既更改不了，也查看不了，数据都是加密过的，只有有解密的 key 才可以解出来。通过调用&lt;code&gt;cookies.encrypted[:user_id]=1&lt;/code&gt;来进行加密，生成的数值是&lt;code&gt;VTRqOVpsUnJoaFhnTFpRcUF1OWdOUT09LS0xSXRUWmI5UWQrTXg1MDNXbUhhSC93PT0%3D--2f18602eec2a4816591345caa62ddcce55c3eb83&lt;/code&gt;，&lt;code&gt;--&lt;/code&gt;前面的部分是加密后的值和加密的 iv 变量 encode 的值，后面部分是前面部分 hash 计算的值。也是为了防止别人更改前面部分的值。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/cookies.rb, line 585
def parse(name, encrypted_message)
  deserialize name, @encryptor.decrypt_and_verify(encrypted_message)
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
  nil
end

def commit(options)
  options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]))

  raise CookieOverflow if options[:value].bytesize &amp;gt; MAX_COOKIE_SIZE
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="在响应中设置Set-Cookie Header"&gt;在响应中设置 Set-Cookie Header&lt;/h3&gt;
&lt;p&gt;上面的过程是准备 cookies 数据的过程，准备完成后是怎么用符合浏览器的格式去设置到响应的 http 头部去呢？其实也是通过 middleware 的方式去设置的，具体如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.rvm/gems/ruby-2.4.3/gems/actionpack-5.1.6.2/lib/action_dispatch/middleware/cookies.rb

module ActionDispatch
  class Cookies
  def initialize(app)
    @app = app
  end

  def call(env)
    request = ActionDispatch::Request.new env

    status, headers, body = @app.call(env)

    if request.have_cookie_jar?
      cookie_jar = request.cookie_jar
      unless cookie_jar.committed?
        cookie_jar.write(headers)
        if headers[HTTP_HEADER].respond_to?(:join)
          headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
        end
      end
    end

    [status, headers, body]
  end
  end
end

class CookieJar
  def write(headers)
    if header = make_set_cookie_header(headers[HTTP_HEADER])
      headers[HTTP_HEADER] = header
    end
  end

   def make_set_cookie_header(header)
     header = @set_cookies.inject(header) { |m, (k, v)|
       if write_cookie?(v)
         ::Rack::Utils.add_cookie_to_header(m, k, v)
       else
         m
       end
     }
     @delete_cookies.inject(header) { |m, (k, v)|
       ::Rack::Utils.add_remove_cookie_to_header(m, k, v)
     }
    end
  end
end

~/.rvm/gems/ruby-2.4.3/gems/rack-2.0.6/lib/rack/utils.rb
def add_cookie_to_header(header, key, value)
  case value
  when Hash
    domain  = "; domain=#{value[:domain]}"   if value[:domain]
    path    = "; path=#{value[:path]}"       if value[:path]
    max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
    expires = "; expires=" +
      rfc2822(value[:expires].clone.gmtime) if value[:expires]
    secure = "; secure"  if value[:secure]
    httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
    same_site =
      case value[:same_site]
      when false, nil
        nil
      when :lax, 'Lax', :Lax
        '; SameSite=Lax'.freeze
      when true, :strict, 'Strict', :Strict
        '; SameSite=Strict'.freeze
      else
        raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
      end
    value = value[:value]
  end
  value = [value] unless Array === value

  cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&amp;amp;')}#{domain}" \
    "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"

  case header
  when nil, ''
    cookie
  when String
    [header, cookie].join("\n")
  when Array
    (header + [cookie]).join("\n")
  else
    raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面 make_set_cookie_header 方法中遍历&lt;code&gt;@set_cookies&lt;/code&gt;实例变量去调用 add_cookie_to_header 方法组装 cookie 的值，然后拼装成一定格式的字符串设置到 http 头部，其中 key 为&lt;code&gt;Set-Cookie&lt;/code&gt;，值是不同的 cookie 按一定格式拼接后用&lt;code&gt;\n&lt;/code&gt;分隔开的字符串。&lt;/p&gt;</description>
      <author>mr_zou123</author>
      <pubDate>Sat, 04 May 2019 10:36:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/38474</link>
      <guid>https://ruby-china.org/topics/38474</guid>
    </item>
    <item>
      <title>Rack Middleware 的理解</title>
      <description>&lt;p&gt;&lt;a href="https://rack.github.io/" rel="nofollow" target="_blank" title=""&gt;Rack 官网&lt;/a&gt;对于 Rack 的介绍比较简单，只是介绍了 Rack 的作用和基本的使用。不过也可能因为不复杂，所以才用简单的几段话介绍了 Rack。虽然我们不用了解 middleware 的调用原理也可以开发出能使用的 middleware，但是总有点不知所以然的感觉，所以抽空总结了下 Rack 中 middleware 的调用原理。其中可能有不正确的地方，欢迎大家指出&lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;/p&gt;
&lt;h3 id="Rack Middleware的使用"&gt;Rack Middleware 的使用&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;配置 config.ru 文件，定义好要用到的 middleware 和要 run 的 middleware，为什么有些 middleware 需要 run，有些需要 use，下面再详细介绍。下面是一个简单的调用。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ./config.ru&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'200'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text/html'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'get rack\'d'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着执行&lt;code&gt;rackup&lt;/code&gt;命令应用就可以跑起来了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同时还可以把 middleware 定义成一个类，但是要在初始化实例的时候初始化&lt;a href="/app" class="user-mention" title="@app"&gt;&lt;i&gt;@&lt;/i&gt;app&lt;/a&gt;和定义一个 call 方法，并且在 call 方法中需要调用&lt;code&gt;@app.call(env)&lt;/code&gt;，如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# rack_demo.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Timing&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@app.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;elapsed_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Timing: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REQUEST_METHOD'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REQUEST_URI'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;elapsed_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'200'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text/html'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Hello, Rack!'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WEBrick&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Timing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;:Port&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9292&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:Host&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行&lt;code&gt;ruby rack_demo.rb&lt;/code&gt;就可以跑起一个服务了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;middleware 的具体使用和返回格式要求这里不详细介绍，可以参考&lt;a href="https://ruby-china.org/topics/31592" title=""&gt;Ruby Rack 及其应用&lt;/a&gt;。同时上面的两种使用方式的作用原理是一样的。下面再详细分析。&lt;/p&gt;
&lt;h3 id="Rack Middleware实现的原理"&gt;Rack Middleware 实现的原理&lt;/h3&gt;
&lt;p&gt;定义好 config.ru 配置文件后在当前目录执行&lt;code&gt;rackup&lt;/code&gt;命令，会去到 ruby 对应的 bin 目录执行文件。一般在 ruby 安装好后都会有这个二进制文件的，在我本地的位置是 &lt;code&gt;~/.rvm/gems/ruby-2.5.3/bin/rackup&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.rvm/gems/ruby-2.5.3/bin/rackup&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rubygems'&lt;/span&gt;

&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 0.a"&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:activate_bin_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;load&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate_bin_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rack'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rackup'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"rack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;
  &lt;span class="nb"&gt;load&lt;/span&gt; &lt;span class="no"&gt;Gem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bin_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"rack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rackup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面源码会执行后面的 rack gem 下面的 rackup 二进制文件，其中的源码为：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env ruby&lt;/span&gt;
&lt;span class="c1"&gt;# ~/.rvm/gems/ruby-2.5.3/gems/rack-2.0.6/bin/rackup&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"rack"&lt;/span&gt;
&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主要是为了启动 Rack Server，那启动的过程又做了什么东西呢？
调用栈从 &lt;code&gt;def self.start&lt;/code&gt; =&amp;gt; &lt;code&gt;initialize&lt;/code&gt; =&amp;gt; &lt;code&gt;parse_options&lt;/code&gt; 这一系列的调用只是为了初始化一个 Server，然后加上一些默认的 Options 配置，初始化后主要是加了如下的默认配置：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;:environment&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:pid&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:Port&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9292&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:Host&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:AccessLog&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="ss"&gt;:config&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"config.ru"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 config 中的值 config.ru 就是默认的配置文件，然后就是实例执行 run 方法了。run 方法中调用了 wrapped_app 方法，这个方法主要是把那些 middleware 汇总为一个 app 的方法。沿着方法调用栈继续查看，其中&lt;code&gt;build_app&lt;/code&gt;方法比较重要。源码如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;reverse_each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;middleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;
    &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;middleware[options[:environment]]&lt;/code&gt;求得的值是 Rack 中默认的 middleware。遍历的块中每个 middleware 都会去创建一个实例，以 app 变量作为参数传入，这就是为什么每个 middleware 在 initialize 的时候都需要传入一个 app 变量，并初始化赋值给&lt;a href="/app" class="user-mention" title="@app"&gt;&lt;i&gt;@&lt;/i&gt;app&lt;/a&gt;实例变量。上面的迭代遍历过程最后方法返回的 app 会变成如下的链式反应：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#&amp;lt;Rack::ContentLength:0x00007ff72d568200&lt;/span&gt;
 &lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="c1"&gt;#&amp;lt;Rack::Chunked:0x00007ff72d568ef8&lt;/span&gt;
   &lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;#&amp;lt;Rack::CommonLogger:0x00007ff72d569ce0&lt;/span&gt;
     &lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="c1"&gt;#&amp;lt;Rack::ShowExceptions:0x00007ff72e940c28&lt;/span&gt;
       &lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="c1"&gt;#&amp;lt;Rack::Lint:0x00007ff72e941ee8&lt;/span&gt;
         &lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="c1"&gt;#&amp;lt;Rack::TempfileReaper:0x00007ff72e943018&lt;/span&gt;
           &lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="c1"&gt;#&amp;lt;StatusLogger:0x00007ff72d4a0570&lt;/span&gt;
             &lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
              &lt;span class="c1"&gt;#&amp;lt;StatusLoggear:0x00007ff72d4a1088&lt;/span&gt;
               &lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
                &lt;span class="c1"&gt;#&amp;lt;Proc:0x00007ff72d4a2820@/Users/Cain/code/ruby/rack/config.ru:30&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样通过调用&lt;code&gt;app.call&lt;/code&gt;可以调用到所有 middleware 的 call 方法。
如下例子：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#config.ru&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FirstMidd&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@app.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"7"&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecondMidd&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"2"&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@app.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ThirdMidd&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@app.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Top&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"4"&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text/plain'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hello, this is a test."&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;FirstMidd&lt;/span&gt;
&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;SecondMidd&lt;/span&gt;
&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ThirdMidd&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;Top&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发送一个请求&lt;code&gt;curl http://localhost:9292&lt;/code&gt;后，服务器会会按照 &lt;code&gt;=&amp;gt; 1, 2, 3, 4, 5, 6, 7&lt;/code&gt; 的顺序输出。上面的执行顺序体现了一个调用栈的调用过程。
同时上面还有一个 app 参数值的传入，求 app 过程如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.rvm/gems/ruby-2.5.3/gems/rack-2.0.6/lib/rack/server.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_app_and_options_from_config&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:config&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;opt_parser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@options.merge&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ~/.rvm/gems/ruby-2.5.3/gems/rack-2.0.6/lib/rack/builder.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/\.ru$/&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_from_string&lt;/span&gt; &lt;span class="n"&gt;cfgfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_from_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder_script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"(rackup)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"Rack::Builder.new {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;builder_script&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}.to_app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;TOPLEVEL_BINDING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后会调用 new_from_string 这个方法。这个方法就是通过 eval 执行之前 config.ru 中的内容。其中&lt;code&gt;Builder#to_app&lt;/code&gt;方法执行了 &lt;code&gt;app = @use.reverse.inject(app) { |a,e| e[a] }&lt;/code&gt; 把 &lt;code&gt;config.ru&lt;/code&gt; 中 run 方法调用的那些 middleware 实例逐个迭代作为参数嵌入到 app 变量中，app 最终的效果如上面&lt;code&gt;build_app&lt;/code&gt;方法中最后的 app 变量的形式一样。最终这个 app 值作为 build_app 方法的实参调用，最终合并成最终链条。&lt;/p&gt;

&lt;p&gt;上面留了个问题，为什么在 &lt;code&gt;config.ru&lt;/code&gt; 中有些 middleware 需要用 use，而最后会有个 run 方法的调用。因为 run 实例中的 call 方法是在 server 里面最后调用的方法，call 方法按照状态，头部，body 的形式返回。和其它的一些 middleware 初始化&lt;a href="/app" class="user-mention" title="@app"&gt;&lt;i&gt;@&lt;/i&gt;app&lt;/a&gt;，然后在 call 方法中执行&lt;code&gt;@app.call(env)&lt;/code&gt;还是有些区别的，所以在 Rack 里的实现就把 run 的那个 middleware 实例作为最终的 [status, headers, body] 形式返回给前面的 middleware 逐层去处理逻辑了。&lt;/p&gt;
&lt;h3 id="Rack和应用服务器的对接"&gt;Rack 和应用服务器的对接&lt;/h3&gt;
&lt;p&gt;Rack 是应用服务器和应用的连接桥梁，具体是怎么实现的呢？其实 Rack 只是按照一定的规则去找出应用服务器，然后通过执行其中定义好的&lt;code&gt;run&lt;/code&gt;方法，把上面链接好的迭代 app 传入服务器中去执行，从而达到中间桥梁的作用，主要的代码在&lt;code&gt;Rack::Server#start&lt;/code&gt;的时候 run 了应用服务器。如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.rvm/gems/ruby-2.5.3/gems/rack-2.0.6/lib/rack/server.rb&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="n"&gt;wrapped_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;blk&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这行代码中，server 的调用过程如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.rvm/gems/ruby-2.5.3/gems/rack-2.0.6/lib/rack/server.rb&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;server&lt;/span&gt;
  &lt;span class="vi"&gt;@_server&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:server&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vi"&gt;@_server&lt;/span&gt;
    &lt;span class="vi"&gt;@_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;

    &lt;span class="c1"&gt;# We already speak FastCGI&lt;/span&gt;
    &lt;span class="vi"&gt;@ignore_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:Port&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@_server.to_s&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'Rack::Handler::FastCGI'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="vi"&gt;@_server&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果没有配置对应的:server 选项，调用&lt;code&gt;Rack::Handler.get&lt;/code&gt;会返回 nil，然后调用&lt;code&gt;Rack::Handler.default&lt;/code&gt;去查找对应的 server。会通过&lt;code&gt;pick ['puma', 'thin', 'webrick']&lt;/code&gt;按照默认服务器名字的顺序去查找对应的服务器 handler。
如果 require 了 puma，而 puma 因为定义了一个覆盖 Handler 中 default 的方法，如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /Users/Cain/.rvm/gems/ruby-2.5.3/gems/puma-3.12.0/lib/puma/rack_default.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack/handler/puma'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rack::Handler&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Puma&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时 &lt;code&gt;Rack::Handler.default&lt;/code&gt; 返回的就是 &lt;code&gt;Rack::Handler::Puma&lt;/code&gt; 这个类。这时调用&lt;code&gt;server.run wrapped_app, options, &amp;amp;blk&lt;/code&gt;就相当于调用了&lt;code&gt;Rack::Handler::Puma.run&lt;/code&gt;，从而在 puma 中接到可以处理的请求后再执行&lt;code&gt;app.call(env)&lt;/code&gt;，就会出现一系列的链式调用。从而保证 middleware 都可以被调用到 call 方法，然后再次返回处理逻辑。&lt;/p&gt;

&lt;p&gt;总结：Rack 还做了很多的其它处理工作，把 app 和 app server 给串联起来只是其中的一部分。总的来说就是当一个请求过来时，可以通过一个个的 middleware 链式的调用，完成一些列的功能调用。&lt;/p&gt;
&lt;h3 id="Ref:"&gt;Ref:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ruby-china.org/topics/21517" title=""&gt;为什么我们需要 Rack ?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ruby-china.org/topics/31592" title=""&gt;Ruby Rack 及其应用 (上)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>mr_zou123</author>
      <pubDate>Sat, 16 Mar 2019 23:04:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/38243</link>
      <guid>https://ruby-china.org/topics/38243</guid>
    </item>
    <item>
      <title>rvm 安装出现错误</title>
      <description>&lt;p&gt;为什么用 ubuntu 安装 rvm 用这个命令   $：curl -L get.rvm.io | bash -s stable
会出现链接不了的情况 
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   184  100   184    0     0     63      0  0:00:02  0:00:02 --:--:--    98
  0     0    0     0    0     0      0      0 --:--:--  0:02:10 --:--:--     0curl: (7) Failed connect to raw.github.com:443; 连接超时&lt;/p&gt;</description>
      <author>mr_zou123</author>
      <pubDate>Mon, 07 Oct 2013 22:07:39 +0800</pubDate>
      <link>https://ruby-china.org/topics/14570</link>
      <guid>https://ruby-china.org/topics/14570</guid>
    </item>
    <item>
      <title>respond_to 方法求解</title>
      <description>&lt;p&gt;请教大家一个问题：在 ruby on rails 的 controller 里的方法中的 respond_to do |format| 这句什么意思？&lt;/p&gt;</description>
      <author>mr_zou123</author>
      <pubDate>Sat, 31 Aug 2013 09:04:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/13771</link>
      <guid>https://ruby-china.org/topics/13771</guid>
    </item>
    <item>
      <title>rails 应该怎么学</title>
      <description>&lt;p&gt;以前都是按照参考书的脚手架来学的，现在做完啦这些后不知道怎么学了，主要是看不到那些知识点，请问一下大家，接下来怎么脱离脚手架去开发一个自己的网站。&lt;/p&gt;</description>
      <author>mr_zou123</author>
      <pubDate>Fri, 26 Jul 2013 17:06:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/12806</link>
      <guid>https://ruby-china.org/topics/12806</guid>
    </item>
  </channel>
</rss>
