<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>academus (Robert)</title>
    <link>https://ruby-china.org/academus</link>
    <description>https://githuber.cn</description>
    <language>en-us</language>
    <item>
      <title>Ruby Rack 及其应用 (下)</title>
      <description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://huiming.io/2016/11/10/ruby-rack.html" rel="nofollow" target="_blank" title=""&gt;Ruby Rack 及其应用（上）&lt;/a&gt;（左边链接到我的博客，这个是&lt;a href="https://ruby-china.org/topics/31592" title=""&gt;站内链接&lt;/a&gt;）对 Rack 的定义、基本原理和构建方法做了介绍，并且提到 Rails、Sinatra 等 web 框架都是在 Rack 之上构建的。现在让我们来看几个 Rack 作为中间件的典型例子：这也是 Rack 应用最活跃的领域。&lt;/p&gt;
&lt;h2 id="如何使用Rack中间件"&gt;如何使用 Rack 中间件&lt;/h2&gt;
&lt;p&gt;在开始之前让我们先了解一下如何使用中间件，这样你就可以动手尝试一下后面的例子。在之前的文章里我已经说明了如何在&lt;code&gt;config.ru&lt;/code&gt;里配置中间件，这对于任何基于 Rack 的 web 框架，如 Rails、Sinatra，都是可行的。但 Rails 和 Sinatra 也有自己的方式来配置中间件。你应该全面了解这些方法，因为以不同的方式配置中间件，得到的中间件栈可能是不同的——有时候中间件在栈内的顺序很重要。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails 可以通过&lt;code&gt;config.middleware&lt;/code&gt;来配置中间件，可以在&lt;code&gt;application.rb&lt;/code&gt;或者&lt;code&gt;environments/&amp;lt;environment&amp;gt;.rb&lt;/code&gt;文件中进行配置，具体请参考&lt;a href="http://guides.rubyonrails.org/rails_on_rack.html#configuring-middleware-stack" rel="nofollow" target="_blank" title=""&gt;Rails Guide&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;Sinatra 的方式比较简单，直接在 Rack 应用中使用&lt;code&gt;use&lt;/code&gt;来配置即可，与&lt;code&gt;config.ru&lt;/code&gt;十分相似，具体请参考&lt;a href="http://www.sinatrarb.com/intro.html#Rack%20Middleware" rel="nofollow" target="_blank" title=""&gt;Sinatra README&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外，要注意：在&lt;code&gt;config.ru&lt;/code&gt;中配置的中间件处于中间件栈的上层，在 Rails 或 Sinatra 应用中配置的中间件处于下层，用户请求自上而下通过栈内的中间件，任何一个中间件都可以终止用户请求而不向下传递它。&lt;/p&gt;
&lt;h2 id="Auth"&gt;Auth&lt;/h2&gt;
&lt;p&gt;Rack 中间件可以用来做 HTTP 鉴权（authentication and authorization）。考虑一个简单的例子：假设你有一个 Rack app，只限管理员使用。那么你可以使用&lt;code&gt;Rack::Auth::Basic&lt;/code&gt;这个中间件，例如：&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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'admin_app'&lt;/span&gt;

&lt;span class="n"&gt;use&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;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Basic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my auth realm'&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;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# Your method returns true when passing. Otherwise the middleware returns&lt;/span&gt;
  &lt;span class="c1"&gt;# 400 to client.&lt;/span&gt;
  &lt;span class="n"&gt;your_auth_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&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;run&lt;/span&gt; &lt;span class="no"&gt;AdminApp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Rack::Auth::Basic&lt;/code&gt;的实现在 rack gem（lib/rack/auth/basic.rb）里，在此不必&lt;code&gt;require&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;它使用&lt;a href="https://en.wikipedia.org/wiki/Basic_access_authentication" rel="nofollow" target="_blank" title=""&gt;HTTP Basic Auth&lt;/a&gt;做鉴权，在生产环境下要结合 HTTPS 使用才安全。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;配置了中间件以后，AdminApp 不用做任何改变就被“用户名／密码”保护了起来，不论它是 Rails、Sinatra 或者别的什么基于 Rack 的应用。&lt;/p&gt;

&lt;p&gt;这个例子虽然简单，但值得我们分析一下&lt;code&gt;Rack::Auth::Basic&lt;/code&gt;的实现——如果我们想实现一个自己的鉴权中间件或者我们不想用 Basic Auth 的话。&lt;/p&gt;

&lt;p&gt;它的实现也很简单，包括以下几个文件，其中需要说明的地方我用中文做了注释，英文注释是原有的。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/rack/auth/basic.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack/auth/abstract/handler'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack/auth/abstract/request'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rack&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Auth&lt;/span&gt;
    &lt;span class="c1"&gt;# Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617.&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;# Initialize with the Rack application that you want protecting,&lt;/span&gt;
    &lt;span class="c1"&gt;# and a block that checks if a username and password pair are valid.&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Basic&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;AbstractHandler&lt;/span&gt;

      &lt;span class="c1"&gt;# 每个Rack都要响应的call方法&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;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Basic&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Request&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;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# unauthorized 方法返回401，要求客户端以指定的Auth方法（此处是Basic Auth）&lt;/span&gt;
        &lt;span class="c1"&gt;# 提供用户名／密码。我们可以在这里指定其他Auth方法，如OAuth&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unauthorized&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provided?&lt;/span&gt;
        &lt;span class="c1"&gt;# bad_request 方法返回400，提示客户端Auth方法错误，即非Basic Auth&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bad_request&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basic?&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="c1"&gt;# 鉴权成功后把username保存在环境变量里，以便其他的Rack中间件和应用访问&lt;/span&gt;
          &lt;span class="c1"&gt;# 这是一种常用的在Rack中间件和应用之间传递信息的方式&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;'REMOTE_USER'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username&lt;/span&gt;
          &lt;span class="c1"&gt;# 调用下一个Rack的call方法&lt;/span&gt;
          &lt;span class="k"&gt;return&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="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;unauthorized&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="kp"&gt;private&lt;/span&gt;

      &lt;span class="c1"&gt;# 参考下面unauthorized方法的实现了解challenge的作用&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;challenge&lt;/span&gt;
        &lt;span class="s1"&gt;'Basic realm="%s"'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;realm&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;valid?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@authenticator.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#*)&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;Request&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AbstractRequest&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;basic?&lt;/span&gt;
          &lt;span class="s2"&gt;"basic"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;scheme&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;credentials&lt;/span&gt;
          &lt;span class="vi"&gt;@credentials&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"m*"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/:/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;def&lt;/span&gt; &lt;span class="nf"&gt;username&lt;/span&gt;
          &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&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;end&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;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/rack/auth/abstract/handler.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rack&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Auth&lt;/span&gt;
    &lt;span class="c1"&gt;# Rack::Auth::AbstractHandler implements common authentication functionality.&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;# +realm+ should be set for all handlers.&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AbstractHandler&lt;/span&gt;

      &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:realm&lt;/span&gt;

      &lt;span class="c1"&gt;# 每个Rack中间件的initialize方法的第一个参数都是app，即在中间件栈中下一级的&lt;/span&gt;
      &lt;span class="c1"&gt;# 中间件或应用，其他的参数可选。这些参数都由Rack Builder传入，比如有&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# use Rack::Auth::Basic, 'my auth realm' do |username, password|&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="c1"&gt;# end&lt;/span&gt;
      &lt;span class="c1"&gt;# run AdminApp&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# 则app=AdminApp, realm='my auth realm', authenticator=block&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="n"&gt;realm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&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;authenticator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@realm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@authenticator&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;realm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authenticator&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="kp"&gt;private&lt;/span&gt;

      &lt;span class="c1"&gt;# 上面已经有了unauthorized方法的应用，需要说明的是此处challenge是一个方法：&lt;/span&gt;
      &lt;span class="c1"&gt;# Ruby方法的缺省值不必是常量，而且取值是在所在方法被调用时发生的。&lt;/span&gt;
      &lt;span class="c1"&gt;# 另外，HTTP server通过通过WWW-Authenticate header指定Auth的方法，具体可参考&lt;/span&gt;
      &lt;span class="c1"&gt;# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unauthorized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;www_authenticate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&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="no"&gt;CONTENT_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'WWW-Authenticate'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;www_authenticate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;[]&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;def&lt;/span&gt; &lt;span class="nf"&gt;bad_request&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&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="no"&gt;CONTENT_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;[]&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/rack/auth/abstract/request.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rack&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Auth&lt;/span&gt;
    &lt;span class="c1"&gt;# 这个类主要用于解析HTTP客户端请求的Authorization header，从中提取Auth方法&lt;/span&gt;
    &lt;span class="c1"&gt;# 和用户名、密码等信息。参考这里了解更多关于Authorization&lt;/span&gt;
    &lt;span class="c1"&gt;# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AbstractRequest&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;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&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;provided?&lt;/span&gt;
        &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;authorization_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&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;parts&lt;/span&gt;
        &lt;span class="vi"&gt;@parts&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="vi"&gt;@env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;authorization_key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;def&lt;/span&gt; &lt;span class="nf"&gt;scheme&lt;/span&gt;
        &lt;span class="vi"&gt;@scheme&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&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;params&lt;/span&gt;
        &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="kp"&gt;private&lt;/span&gt;

      &lt;span class="c1"&gt;# 按照CGI的方式，HTTP客户端请求的header都会被冠以“HTTP_”前缀、全部大写、保存在env里，&lt;/span&gt;
      &lt;span class="c1"&gt;# 因此Authorization就成了HTTP_AUTHORIZATION&lt;/span&gt;
      &lt;span class="no"&gt;AUTHORIZATION_KEYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTP_AUTHORIZATION'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'X-HTTP_AUTHORIZATION'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'X_HTTP_AUTHORIZATION'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authorization_key&lt;/span&gt;
        &lt;span class="vi"&gt;@authorization_key&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;AUTHORIZATION_KEYS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&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="o"&gt;|&lt;/span&gt; &lt;span class="vi"&gt;@env.has_key&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，我们不一定要实现一个自己的 Auth 中间件，可以借助于以下 Gems（也是中间件）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/plataformatec/devise" rel="nofollow" target="_blank" title=""&gt;devise&lt;/a&gt; 针对 Rails&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/omniauth/omniauth" rel="nofollow" target="_blank" title=""&gt;omniauth&lt;/a&gt; 对于任何 Rack 应用&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果你了解了原理，使用别人开发的中间件也会更得心应手。&lt;/p&gt;
&lt;h2 id="Session"&gt;Session&lt;/h2&gt;
&lt;p&gt;Rack 中间件还可以实现 HTTP session。以 Sinatra 为例，文档上说可以这样启用 session：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sinatra/base'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sinatra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="ss"&gt;:sessions&lt;/span&gt; &lt;span class="c1"&gt;# 启用session&lt;/span&gt;

  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/sessions'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="s2"&gt;"value = "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:value&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt; &lt;span class="c1"&gt;# 使用session方法读&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/sessions/:value'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# 使用session方法写&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;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sinatra/base'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sinatra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="c1"&gt;# 启用session&lt;/span&gt;
  &lt;span class="n"&gt;use&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;Session&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:secret&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&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::Session::Cookie&lt;/code&gt;就是用于实现 session 的中间件，&lt;code&gt;use&lt;/code&gt;是 Sinatra 用于配制中间件的方法。另外，用于读写 session 变量的&lt;code&gt;session&lt;/code&gt;方法也很简单：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/sinatra/base.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Sinatra&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Helpers&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;
      &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&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;其中request#session的实现是：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/rack/request.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rack&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="vi"&gt;@env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'rack.session'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;              &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&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;@env['rack.session']&lt;/code&gt;是怎么来的，让我们了解一下&lt;code&gt;Rack::Session::Cookie&lt;/code&gt;的实现你就明白了。以下需要注意的地方我用中文做了注释，英文注释是原有的，也要注意。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/rack/session/cookie.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rack&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Session&lt;/span&gt;
    &lt;span class="c1"&gt;# HTTP session的主要行为都是在Abstract::ID里实现的，子类只需要实现session对象的&lt;/span&gt;
    &lt;span class="c1"&gt;# 保存和读取。除了cookie，还可以把它保存在Redis之类的数据库里，甚至直接保存在内存，&lt;/span&gt;
    &lt;span class="c1"&gt;# 如Rack::Session::Pool。&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cookie&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Abstract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ID&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_session&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;sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&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;set_session&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;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session&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="c1"&gt;# ...&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;destroy_session&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;sid&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="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/rack/session/abstract/id.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rack&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Session&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Abstract&lt;/span&gt;
      &lt;span class="c1"&gt;# 注意这个常量：Rack中间件把session对象保存在env的这个key下，也就是说，&lt;/span&gt;
      &lt;span class="c1"&gt;# 其他的Rack中间件和应用只有通过这个key才能访问session。&lt;/span&gt;
      &lt;span class="no"&gt;ENV_SESSION_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'rack.session'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
      &lt;span class="no"&gt;ENV_SESSION_OPTIONS_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'rack.session.options'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

      &lt;span class="c1"&gt;# 这就是我们在上面的Sinatra示例中通过session方法所访问的对象&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionHash&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# ID sets up a basic framework for implementing an id based sessioning&lt;/span&gt;
      &lt;span class="c1"&gt;# service. Cookies sent to the client for maintaining sessions will only&lt;/span&gt;
      &lt;span class="c1"&gt;# contain an id reference. Only #get_session and #set_session are&lt;/span&gt;
      &lt;span class="c1"&gt;# required to be overwritten.&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# All parameters are optional.&lt;/span&gt;
      &lt;span class="c1"&gt;# * :key determines the name of the cookie, by default it is&lt;/span&gt;
      &lt;span class="c1"&gt;#   'rack.session'&lt;/span&gt;
      &lt;span class="c1"&gt;# * :path, :domain, :expire_after, :secure, and :httponly set the related&lt;/span&gt;
      &lt;span class="c1"&gt;#   cookie options as by Rack::Response#add_cookie&lt;/span&gt;
      &lt;span class="c1"&gt;# * :skip will not a set a cookie in the response nor update the session state&lt;/span&gt;
      &lt;span class="c1"&gt;# * :defer will not set a cookie in the response but still update the session&lt;/span&gt;
      &lt;span class="c1"&gt;#   state if it is used with a backend&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ID&lt;/span&gt;
        &lt;span class="c1"&gt;# 注意：下面这个key虽然与ENV_SESSION_KEY的值相同，但意义不同：前者用于设置cookie，&lt;/span&gt;
        &lt;span class="c1"&gt;# 我们可以而且应当给它赋一个有意义的值，如“example.com”；后者用于在env中存取&lt;/span&gt;
        &lt;span class="c1"&gt;# seesion对象，我们无法、也不应该改变它的值。&lt;/span&gt;
        &lt;span class="no"&gt;DEFAULT_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;:key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;           &lt;span class="s1"&gt;'rack.session'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;:path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;          &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;:domain&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;:expire_after&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;:secure&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;        &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;:httponly&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;      &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;:defer&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;         &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;:renew&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;         &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;:sidbits&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;       &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;:cookie_only&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;   &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;:secure_random&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SecureRandom&lt;/span&gt; &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:default_options&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="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="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="vi"&gt;@default_options&lt;/span&gt; &lt;span class="o"&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;class&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_OPTIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&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="c1"&gt;# ...&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;context&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="k"&gt;end&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;context&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;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="vi"&gt;@app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;prepare_session&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;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="n"&gt;app&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="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;commit_session&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;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="kp"&gt;private&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prepare_session&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;session_was&lt;/span&gt;                  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ENV_SESSION_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="c1"&gt;# session对象在此建立并保存在env里，但session可以是lazy loading的，&lt;/span&gt;
          &lt;span class="c1"&gt;# 只在读取／写入时才访问实际的session存储。&lt;/span&gt;
          &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ENV_SESSION_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session_class&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="nb"&gt;self&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;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ENV_SESSION_OPTIONS_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@default_options.dup&lt;/span&gt;
          &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ENV_SESSION_KEY&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;merge!&lt;/span&gt; &lt;span class="n"&gt;session_was&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;session_was&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# Acquires the session from the environment and the session id from&lt;/span&gt;
        &lt;span class="c1"&gt;# the session options and passes them to #set_session. If successful&lt;/span&gt;
        &lt;span class="c1"&gt;# and the :defer option is not true, a cookie will be added to the&lt;/span&gt;
        &lt;span class="c1"&gt;# response with the session id.&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;commit_session&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;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="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ENV_SESSION_KEY&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="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;
          &lt;span class="c1"&gt;# ...&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# ...&lt;/span&gt;

        &lt;span class="c1"&gt;# Allow subclasses to prepare_session for different Session classes&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;session_class&lt;/span&gt;
          &lt;span class="no"&gt;SessionHash&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# All thread safety and session retrieval procedures should occur here.&lt;/span&gt;
        &lt;span class="c1"&gt;# Should return [session_id, session].&lt;/span&gt;
        &lt;span class="c1"&gt;# If nil is provided as the session id, generation of a new valid id&lt;/span&gt;
        &lt;span class="c1"&gt;# should occur within.&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_session&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;sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'#get_session not implemented.'&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# All thread safety and session storage procedures should occur here.&lt;/span&gt;
        &lt;span class="c1"&gt;# Must return the session id if the session was saved successfully, or&lt;/span&gt;
        &lt;span class="c1"&gt;# false if the session could not be saved.&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_session&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;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&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="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'#set_session not implemented.'&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# All thread safety and session destroy procedures should occur here.&lt;/span&gt;
        &lt;span class="c1"&gt;# Should return a new session id or nil if options[:drop]&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy_session&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;sid&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="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'#destroy_session not implemented'&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;end&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::Session::Abstract::ID&lt;/code&gt;为基础，我们很容实现自己的 session 中间件。另外，如果你想用 Redis 做存贮，可以考虑&lt;a href="https://github.com/redis-store/redis-rack" rel="nofollow" target="_blank" title=""&gt;redis-rack&lt;/a&gt;这个 Gem.&lt;/p&gt;
&lt;h2 id="Log"&gt;Log&lt;/h2&gt;
&lt;p&gt;Rack 中间件还可以做日志，这很容实现。让我来举两个简单的例子。&lt;/p&gt;

&lt;p&gt;其一，在异常错误时做记录或者输出诊断信息：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExceptionCatcher&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="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="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="c1"&gt;# You can log it anywhere you like ...&lt;/span&gt;
    &lt;span class="c1"&gt;# And output any diagnostic info you prefer&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;当你在 Rails 或者 Sinatra dev server 上做开发时，经常可以看到类似的诊断输出，实际上都是通过这样的中间件完成的。顺便说，Sinatra 使用的中间件是&lt;code&gt;Sinatra::ShowExceptions&lt;/code&gt;，Rails 也有对应的，不妨查看一下它的中间件栈。&lt;/p&gt;

&lt;p&gt;其二，对每个 HTTP 请求做记录，就像 Apache 的 access 日志那样。不过你可以记得更多一些，比如完成一次请求的耗时。在这方面&lt;code&gt;Rack::CommonLogger&lt;/code&gt;已经做得不错了，不妨参考一下它的实现。&lt;/p&gt;

&lt;p&gt;另外值得指出的是，当你要输出日志的时候，你需要一个 IO 输出对象，这时你有几个选择：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;env['rack.errors']：一个 error stream 对象，该对象支持&lt;code&gt;puts&lt;/code&gt;和&lt;code&gt;flush&lt;/code&gt;方法。按照 Rack 规范，该对象必须由 Rack 服务器（如 Phusion Passenger）提供。&lt;/li&gt;
&lt;li&gt;evn['rack.logger']："A common object interface for logging messages."，支持&lt;code&gt;info&lt;/code&gt;、&lt;code&gt;debug&lt;/code&gt;等方法，但不是 Rack 服务器必须提供的。&lt;code&gt;Rack::Logger&lt;/code&gt;中间件利用 env['rack.errors'] 提供了一个简单的实现。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后，欢迎你关注&lt;a href="http://huiming.io/" rel="nofollow" target="_blank" title=""&gt;我的博客&lt;/a&gt;，了解更多技术资讯  ^_^&lt;/p&gt;</description>
      <author>academus</author>
      <pubDate>Wed, 24 May 2017 10:14:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/33055</link>
      <guid>https://ruby-china.org/topics/33055</guid>
    </item>
    <item>
      <title>Ruby Rack 及其应用 (上)</title>
      <description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;你可能听说过 Rails、Sinatra 这些 Ruby Web 框架，也可能尝试过其中一、两个，但如果你还不了解 Rack 甚至根本没听说过它，那么你的 Ruby Web 开发还停留在表面：Ruby Rack 是前面这些 Ruby Web 框架的基础，Rails 和 Sinatra 都建立在它之上；不了解 Rack 的原理就无法真正理解你的 Ruby Web 应用的架构与工作机制、对一些复杂的问题也无能无力。任何一个正经的 Ruby Web 开发者都应该了解、掌握 Rack。&lt;/p&gt;

&lt;p&gt;本文将深入浅出地介绍 Ruby Rack 和它的一些典型应用，如 Profiler、Logger 和 Session 等。&lt;/p&gt;
&lt;h2 id="Ruby Rack"&gt;Ruby Rack&lt;/h2&gt;
&lt;p&gt;我在《Web 全栈技术指南》中简单介绍过 Ruby Rack[^1]。下面的内容是它的扩充与深入。&lt;/p&gt;
&lt;h3 id="什么是Ruby Rack"&gt;什么是 Ruby Rack&lt;/h3&gt;
&lt;p&gt;Ruby Rack 是一个接口，用于 Ruby Web 应用与应用服务器之间的交互，如图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/83f3aedb5f845d138733ef197a1345f5.png!large" title="" alt="Ruby Rack"&gt;&lt;/p&gt;

&lt;p&gt;最左边的 User Agent 就是浏览器等客户端，它发起 HTTP 请求；中间的 Rack Server 是应用服务器 [^2]，它响应 HTTP 请求，并调用我们的 Rack 应用；最右边是我们的应用程序——它可能是一个 Rails 或者 Sinatra 应用。Rack 服务器和 Rack 应用程序之间通过 Rack 接口交互。&lt;/p&gt;

&lt;p&gt;那么 Rack 接口是怎样的？就像这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# hello.rb - v0&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个最小的可以工作的 Rack 应用程序，它揭示了 Rack 接口：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一个响应&lt;code&gt;call&lt;/code&gt;方法的对象（任何类型的对象都可以，上面只是以 proc 为例）&lt;/li&gt;
&lt;li&gt;接受一个 Hash 类型的环境变量作为输入参数（它包含了全部的 HTTP 请求信息）&lt;/li&gt;
&lt;li&gt;返回一个包含三个元素的数组，依次是：

&lt;ol&gt;
&lt;li&gt;HTTP 应答代码（status code）&lt;/li&gt;
&lt;li&gt;一个 Hash 类型的对象，包含 HTTP 应答头部信息（header）&lt;/li&gt;
&lt;li&gt;一个响应&lt;code&gt;each&lt;/code&gt;方法的对象，其结果将作为 HTTP 应答消息的主体（body）&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;很简单不是么？（难的我都放在后面了，^_-）&lt;/p&gt;

&lt;p&gt;只要再加两行代码，这个迷你的 Web 服务就能正式运行起来：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# hello.rb - v1&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack'&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="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;8090&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;在最后一行，我们用 Webrick[^3] 这个 Rack 服务器来 run 我们的 Rack 应用。&lt;/p&gt;

&lt;p&gt;要运行上面的代码，先安装 Gem rack（如过你安装过 Rails 或者 Sinatra 那么它已经作为依赖被安装过了）：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem install rack
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假设以上代码保存在文件&lt;code&gt;hello.rb&lt;/code&gt;中，执行&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby hello.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就把我们的迷你服务器启动了。在浏览器中访问&lt;code&gt;http://localhost:8090&lt;/code&gt;，快试试！&lt;/p&gt;
&lt;h3 id="Rack Middleware"&gt;Rack Middleware&lt;/h3&gt;
&lt;p&gt;Rack 不是那么简单：现在让我们了解一下强大的 Rack 中间件（middleware）。&lt;/p&gt;

&lt;p&gt;以下是一个中间件的例子 [^4]：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# timing.rb - v1&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以这么使用它 [^5]：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# hello.rb - v2&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./timing.rb'&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;8090&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;快试试！看看现在我们的 Rack 应用有什么变化。&lt;/p&gt;

&lt;p&gt;现在我来解释一下上面的程序。&lt;/p&gt;

&lt;p&gt;Rack 中间件就是一个类，如上面的&lt;code&gt;Timing&lt;/code&gt;，其对象响应一个&lt;code&gt;call&lt;/code&gt;方法，这个方法的输入、输出规格与一般 Rack 应用一样。因此&lt;code&gt;Timing.new(app)&lt;/code&gt;可以作为一个 Rack 应用直接传递给&lt;code&gt;Rack::Handler::WEBrick.run&lt;/code&gt;。实际上，中间件可以这样一层套一层地层层嵌套下去，最后仍得出一个可以 call 的 Rack 应用。&lt;/p&gt;

&lt;p&gt;Rack 中间件可以实现非常强大的功能。在上面的例子中，我们的 Timing 中间件为每一次调用计时，并把结果打印出来。这相当于一个 profiler。实际上中间件能做的事情更多：它可以检查内嵌应用程序&lt;code&gt;@app&lt;/code&gt;的输入、输出，还可以修改它们。因此它还可以用于鉴权（authentication/authorization）、日志，或者给内嵌应用提供一些额外的功能，如 Session 等等。稍后我们会看到两个实际的例子。&lt;/p&gt;
&lt;h3 id="rackup和Rack::Builder"&gt;rackup 和 Rack::Builder&lt;/h3&gt;
&lt;p&gt;rackup 和 Rack::Builder 都是 Gem rack 提供的工具，方便我们使用、构造 Rack 应用。&lt;/p&gt;

&lt;p&gt;仍以前面的 hello Rack 和 Timing 中间件为例，实际上，我们一般这样定义我们的 Rack 应用：&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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./hello.rb'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./timing.rb'&lt;/span&gt;

&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Timing&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;Hello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上代码保存在一个名为&lt;code&gt;config.ru&lt;/code&gt;的文件中——它是 rackup 工具的缺省配置文件。&lt;/p&gt;

&lt;p&gt;其中 hello.rb 的内容是：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# hello.rb - v3&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hello&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;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="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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们定义了一个类&lt;code&gt;Hello&lt;/code&gt;，它有一个&lt;code&gt;call&lt;/code&gt;方法（回忆一下 Rack 的定义：任何响应&lt;code&gt;call&lt;/code&gt;方法的对象）。&lt;/p&gt;

&lt;p&gt;我们不需要再编写初始化 Rack 中间件和启动 Rack 服务器的代码——rackup 工具会为我们完成。&lt;/p&gt;

&lt;p&gt;一切就绪以后，在命令行执行（要在包含 config.ru 的目录下）：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rackup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;啊哈，我们的迷你服务器又启动了！&lt;/p&gt;

&lt;p&gt;rackup 默认使用 Webrick 服务器，你也可以通过参数指定其他服务器。了解更多参数选项：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rackup -h
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想知道 rackup 是如何构造 Rack 应用、配置中间件的，你需要了解 Rack::Builder（Gem rack 安装目录下的 lib/rack/builder.rb）。具体代码这里就不做分析了。下面再举几个例子说明一下 config.ru 如何配置 Rack 应用和中间件。&lt;/p&gt;

&lt;p&gt;如果你要使用多个中间件，可以：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.ru - multi-middlewares&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./app.rb'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./middleware1.rb'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./middleware2.rb'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./middleware3.rb'&lt;/span&gt;

&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Middleware1&lt;/span&gt;
&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Middleware2&lt;/span&gt;
&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Middleware3&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;App&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rack::Builder 将依次应用这些中间件到&lt;code&gt;App&lt;/code&gt;上，得出一个最终的 Rack 应用，效果如同以下代码：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rack_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Middleware1&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="no"&gt;Middleware2&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="no"&gt;Middleware3&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="no"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你还可以在 config.ru 中配置路由，如：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.ru - routes&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./main.rb'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./admin.rb'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./m1.rb'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./m2.rb'&lt;/span&gt;

&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;m1&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;Main&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="s1"&gt;'/admin'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;m2&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;Admin&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样所有以&lt;code&gt;/admin/&lt;/code&gt;开头的请求都会交由&lt;code&gt;Admin&lt;/code&gt;处理，其余则由&lt;code&gt;Main&lt;/code&gt;处理。这种配置实际上开启了一种“Rack 组合”模式——由几个不同的 Rack 应用组成一个新的 Rack 应用。比如说：把一个 Rails 应用和一个 Sinatra 应用（它们都是标准的 Rack 应用）组合成一个新的 Rack 应用——脑洞很大，但完全可行！&lt;/p&gt;

&lt;p&gt;另外，Rack 中间件是可以接受参数的——甚至可以带有 code block，比如：&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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./hello.rb'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./timing.rb'&lt;/span&gt;

&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Timing&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;true&lt;/span&gt;&lt;span class="p"&gt;,&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;"Timing is being initialized!"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;Hello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 timing.rb 内容如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# timing.rb - v2&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="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&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;b&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="vi"&gt;@pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:pid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;block_given?&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="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@pid&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_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;/code&gt;&lt;/pre&gt;&lt;h3 id="Rails/Sinatra on Rack"&gt;Rails/Sinatra on Rack&lt;/h3&gt;
&lt;p&gt;Rails 和 Sinatra 都是标准的 Rack 应用框架——你可能已经注意到了，它们的项目根目录下一般都有一个 config.ru 文件。你可能会想：我从没编辑过这个文件，大概也就没有使用过中间件吧？错了！Rails 和 Sinatra 都可以在它们的应用程序内配置中间件，并且在缺省情况下已经为你配置了一大堆：&lt;/p&gt;

&lt;p&gt;在一个 Rails 项目的根目录下运行：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/rails middleware
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看看 Rails 为你配置的中间件栈有多深 [^6]：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use #&amp;lt;ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838&amp;gt;
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run Rails.application.routes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相比而言 Sinatra 要轻便许多：它有条件地配置了 4～7 个中间件（针对版本 v1.4.7），在 lib/sinatra/base.rb 中：&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;setup_default_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ExtendedRack&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;ShowExceptions&lt;/span&gt;       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;show_exceptions?&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&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;MethodOverride&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;method_override?&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&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;Head&lt;/span&gt;
  &lt;span class="n"&gt;setup_logging&lt;/span&gt;    &lt;span class="n"&gt;builder&lt;/span&gt;
  &lt;span class="n"&gt;setup_sessions&lt;/span&gt;   &lt;span class="n"&gt;builder&lt;/span&gt;
  &lt;span class="n"&gt;setup_protection&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，不论这些 Rack 应用框架如何组织、定义自己的中间件栈，你都可以在 config.ru 中使用 Rack::Builder 所支持的标准语法来配置你的中间件——虽然一般情况下你不必这么做，但这样做有一个好处：你在 config.ru 中配置的中间件处于你的中间件栈顶部 [^7]，也就是说，它最先响应服务器的请求、最后给出答案，因此具有最大的权威。&lt;/p&gt;
&lt;h3 id="Rack env"&gt;Rack env&lt;/h3&gt;
&lt;p&gt;以为 Rack 就这么结束了？并没有！——我之前说过，Rack 没那么简单。前面我们只提了一下&lt;code&gt;call&lt;/code&gt;接受一个环境变量&lt;code&gt;env&lt;/code&gt;作为输入，并提到它包含了全部的 HTTP 请求信息，但并没有仔细讲讲它。现在是时候了——它很重要！&lt;/p&gt;

&lt;p&gt;让我们检查一下 env 都包含些啥：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack'&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="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="sx"&gt;%Q(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&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="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="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;8090&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个简单的 Rack 应用会把 env 的内容都打印出来。照前面的样子启动它，然后访问它，你就能看到：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GATEWAY_INTERFACE=CGI/1.1
HTTP_ACCEPT=*/*
HTTP_HOST=localhost:8090
HTTP_USER_AGENT=curl/7.35.0
HTTP_VERSION=HTTP/1.1
PATH_INFO=/
QUERY_STRING=
REMOTE_ADDR=127.0.0.1
REMOTE_HOST=127.0.0.1
REQUEST_METHOD=GET
REQUEST_PATH=/
REQUEST_URI=http://localhost:8090/
SCRIPT_NAME=
SERVER_NAME=localhost
SERVER_PORT=8090
SERVER_PROTOCOL=HTTP/1.1
SERVER_SOFTWARE=WEBrick/1.3.1 (Ruby/2.3.0/2015-12-25)
rack.errors=#&amp;lt;IO:0x0000000220dad0&amp;gt;
rack.hijack=#&amp;lt;Proc:0x000000024176c8@/home/user/.rvm/gems/ruby-2.3.0/gems/rack-1.6.4/lib/rack/handler/webrick.rb:76 (lambda)&amp;gt;
rack.hijack?=true
rack.hijack_io=
rack.input=#&amp;lt;StringIO:0x000000024180a0&amp;gt;
rack.multiprocess=false
rack.multithread=true
rack.run_once=false
rack.url_scheme=http
rack.version=[1, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是我从 localhost 上用 curl 访问的输出，你的也应该差不多。除了那些大写的 CGI[^8] 变量，还有一些 rack.xxx 变量，这些都是由 Rack 服务器设置并传递给 Rack 应用程序的。&lt;/p&gt;

&lt;p&gt;CGI 变量大都可以顾名思义，前面的 Timing 中间件作为一个示例也用到了&lt;code&gt;REQUEST_METHOD&lt;/code&gt;和&lt;code&gt;REQUEST_URI&lt;/code&gt;，这里就不详细介绍了，感兴趣的读者可以参考脚注 [^8]。下面对 rack.xxx 变量做一些介绍：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rack.input&lt;/code&gt; 一个 IO 对象，可以读取 raw HTTP request。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rack.errors&lt;/code&gt; 一个 IO 对象，用于错误输出。一般地，Rack 服务器会把它输出到服务器日志文件。它也是 Rack::Logger 和 Rack::CommonLogger 的输出对象。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rack.hijack&lt;/code&gt;、&lt;code&gt;rack.hijack?&lt;/code&gt;和&lt;code&gt;rack.hijack_io&lt;/code&gt;可以实现 websocket。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rack.multiprocess&lt;/code&gt;和&lt;code&gt;rack.multithread&lt;/code&gt;: 这两个对象指示了 Rack 应用的运行环境是否是多进程、多线程。这里需要着重说明一下：Rack 服务器可以根据负载情况同时启用 Rack 应用的多个实例，既有可能通过多进程（每个进程一个实例），也有可能通过多线程（一个进程，多个线程，每线程一个实例），还可能把二者结合起来（多进程，同时每个进程内多线程实例）。服务器具体通过什么方式启动应用，每种服务器都不一样，你需要查看服务器的文档说明。比如 Phusion Passeger 可以使用多进程或者混合模式（在企业版中）；Unicorn 多进程；Thin 多线程（可配置）。一般来说使用多进程方式比较安全：如果要使用多线程，你不但要保证你的 Rack 应用是线程安全的，还要保证你用到的所有中间件都是线程安全的。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rack.run_once&lt;/code&gt; 这个变量说明服务器是否只运行你的 Rack 应用实例一次就把它释放掉。这就是说服务器会对每个 HTTP 请求构造一个新的 Rack 应用实例（包括所有的中间件初始化工作）。一般来说只有 CGI 服务器会这样做 [^9]（你肯定听说过 CGI 服务器效率不高吧？）。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rack.url_scheme&lt;/code&gt; http 或 https&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rack.version&lt;/code&gt; Rack Spec 的版本（不是 Gem Rack 的版本）。我一直还没告诉你：Rack 不但是一个接口、一个 Gem 的名字，还是一个&lt;a href="http://www.rubydoc.info/github/rack/rack/file/SPEC" rel="nofollow" target="_blank" title=""&gt;规范&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一般而言你不必直接操作这些 rack.xxx 变量（也不应该这么做，除非你十分清楚这么做的后果，像作者这样^_-），但是你应该清楚它们的意义，这有助于你深刻理解 Rack 以及处理一些复杂问题。另外，Rack env 不但可以用于从 Rack 服务器向 Rack 应用和中间件传递一些信息，还可以用于在 Rack 中间件之间或者中间件与应用之间传递消息。在本文的下半部我们将看到这是如何实现的，以及这样做的意义。&lt;/p&gt;

&lt;p&gt;Rack 的介绍到此为止，本文下半部将介绍一些 Rack 技术的常见应用，如 Profiler、Logger 和 Session 等中间件，了解它们是如何工作的。同时，欢迎你关注我的&lt;a href="http://huiming.io" rel="nofollow" target="_blank" title=""&gt;博客&lt;/a&gt;，获得更多技术资讯。&lt;/p&gt;

&lt;p&gt;[^1]: &lt;a href="https://getfullstack.com/web_server/server_programming/ruby.html" rel="nofollow" target="_blank" title=""&gt;Web 服务器-&amp;gt;编程语言与技术-&amp;gt;Ruby&lt;/a&gt;
[^2]: 常见的 Rack 应用服务器有 Phusion Passenger，Unicorn，Thin，Puma 和 Webrick 等等。
[^3]: Webrick 一般用于开发环境，你的生产环境应该使用 Phusion Passenger 或者 Unicorn 等高性能的 Rack 服务器。
[^4]: 我喜欢直接从代码开始，我也建议读者手工输入并运行全部的代码示例，并且反复强调一点：技术文章不要只去读，要做！
[^5]: 一般我们不这么用，后面的 rackup 一节会展示通常的用法，但二者的本质是一样的，只是表现形式不同。
[^6]: 更多关于 Rails on Rack 的信息可参考：&lt;a href="http://guides.rubyonrails.org/rails_on_rack.html" rel="nofollow" target="_blank" title=""&gt;Rails on Rack&lt;/a&gt;
[^7]: 实际上 Rack 服务器也可以（而且并不少见）给 Rack 应用加上一些额外的中间件，用于输出 DEBUG 日志等一些工作。这些中间件在所有中间件栈的位置比 config.ru 中的还要“高”。
[^8]: CGI 即通用网关接口（Common Gateway Interface），我在《Web 全栈技术指南》的&lt;a href="http://getfullstack.com/web_server/server_programming/cgi.html" rel="nofollow" target="_blank" title=""&gt;Web 服务器-&amp;gt;编程语言与技术-&amp;gt;CGI&lt;/a&gt;一节做过介绍。
[^9]: 细心的读者可能会问：Rack 应用难道不是对每个请求构造一个新的实例么？比如一个从 Sinatra::Base 继承来的类，对每次请求都会生成新的实例，成员变量也都重新初始化了。其实并没有！以 Sinatra 为例，它只是每次从初始化好的、无状态的 Rack 应用对象 dup 一个实例，用完就释放，下次再 dup 一个新的。具体你要看看 Sinatra 或者 Rails 的代码是如何做到的。&lt;/p&gt;</description>
      <author>academus</author>
      <pubDate>Mon, 14 Nov 2016 18:30:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/31592</link>
      <guid>https://ruby-china.org/topics/31592</guid>
    </item>
    <item>
      <title>Linode Tokyo 机房不能访问了</title>
      <description>&lt;p&gt;这两天的事，ping 不通，HTTP/SSH 访问也不通，目前只有网页 Lish 可以访问，而且经常断线。有遇到同样问题的朋友吗？我该怎么办？换 IP？换机房？还是？&lt;/p&gt;</description>
      <author>academus</author>
      <pubDate>Sat, 15 Mar 2014 16:43:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/17905</link>
      <guid>https://ruby-china.org/topics/17905</guid>
    </item>
    <item>
      <title>Ruby Conf 大量照片</title>
      <description>&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/44c677bc373771741c16568426f3c742.jpg" title="" alt=""&gt;
&lt;img src="//l.ruby-china.com/photo/b31cab00bf7cfa48b53eb8907fa7904e.jpg" title="" alt=""&gt;
&lt;img src="//l.ruby-china.com/photo/922ede3a37179de066e91003c0739ad5.jpg" title="" alt=""&gt;
&lt;img src="//l.ruby-china.com/photo/8bc52e47ce2c23fbe55a96953f1e2733.jpg" title="" alt=""&gt;
&lt;img src="//l.ruby-china.com/photo/47574c4ef012b00e39b3a027b5ca0000.jpg" title="" alt=""&gt;
&lt;img src="//l.ruby-china.com/photo/02595fa2041756dd8e6a52d4a0ee2f8c.jpg" title="" alt=""&gt;
&lt;img src="//l.ruby-china.com/photo/8b7def39543a451b505c2533ad3dd8b3.jpg" title="" alt=""&gt;
&lt;img src="//l.ruby-china.com/photo/4669eb1b43f881d42961e6d35149643a.jpg" title="" alt=""&gt;
&lt;img src="//l.ruby-china.com/photo/817506d002d785e36a2c9d6f5db7ade7.jpg" title="" alt=""&gt;
&lt;img src="//l.ruby-china.com/photo/8430580f8b8a9714f20a1f11c6a5866c.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;关注我的围脖 &lt;a href="http://weibo.com/acadamus" rel="nofollow" target="_blank"&gt;http://weibo.com/acadamus&lt;/a&gt;&lt;/p&gt;</description>
      <author>academus</author>
      <pubDate>Sun, 18 Nov 2012 21:48:28 +0800</pubDate>
      <link>https://ruby-china.org/topics/6878</link>
      <guid>https://ruby-china.org/topics/6878</guid>
    </item>
  </channel>
</rss>
