<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>kayakjiang (jgm)</title>
    <link>https://ruby-china.org/kayakjiang</link>
    <description>技多不压身</description>
    <language>en-us</language>
    <item>
      <title>那种表名是业务 +日期的表你们会用 AR 去查询吗?</title>
      <description>&lt;p&gt;我以前做的项目里有大量的这样的表 &lt;code&gt;a_20120101&lt;/code&gt;, &lt;code&gt;a_20120102&lt;/code&gt;, &lt;code&gt;a_20120103&lt;/code&gt;, 简单来说表名就是 &lt;code&gt;业务名_日期&lt;/code&gt;, 比如有统计用户佣金的表，统计用户等级的表，统计用户销售额的表等等，这些表加起来不下千个，这些表里的记录通过 user_id 和 users 表关联，这里不讨论这样的设计有没有问题，这已经是一个客观事实没办法改变了，现在的问题就是让大家实现这么一个功能：输入开始日期，结束日期，输入用户 id, 查询出这个用户在日期范围内所有的 a_ 相关的数据。我们当时解决这个问题的方法就是手写拼 sql, 确实有点痛苦，我后来实现过一个 sql builder 叫 sqlknit &lt;a href="https://github.com/baya/sqlknit" rel="nofollow" target="_blank"&gt;https://github.com/baya/sqlknit&lt;/a&gt; 去实现拼 sql 的功能，弄完以后发现还不如手写 sql.  你们会怎么做？最好给出代码。&lt;/p&gt;

&lt;p&gt;相关的话题是这个，&lt;a href="https://ruby-china.org/topics/39399" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/39399&lt;/a&gt;&lt;/p&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Sat, 04 Jan 2020 00:09:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/39403</link>
      <guid>https://ruby-china.org/topics/39403</guid>
    </item>
    <item>
      <title>去他的 RTFM, 只要 @kayakjiang 我有问必答，绝不敷衍</title>
      <description>&lt;p&gt;前几天和深圳一家用 ruby 的公司电话聊了将近一个小时，快结束时这家公司的负责人问我：你觉得 ruby 慢吗？我：不慢，rails 目前还是最好的 web 开发框架，绝大部分的性能问题都是数据库那边造成的。他又继续问：那为什么感觉国内 ruby 的开发者越来越少，想用 ruby 的公司也越来越少？我有点答不上来，只好说不是 ruby 语言本身的问题，至少我工作这么多年，还没有遇到哪怕一家公司是因为 ruby 的慢导致公司关门的。他赞同我的观点，瞬间有了种惺惺相惜的感觉，我估计他也是混 ruby-china 的，可惜不知道他在 ruby-china 的 id.
这几天我在思考他提的问题，我觉得 ruby 主要的问题是新人太少了，也许很多想进入 IT 领域的人根本不知道有 ruby? 也许我们的社区对新人还是不够友好？&lt;/p&gt;
&lt;h3 id="RubyConf China"&gt;RubyConf China&lt;/h3&gt;
&lt;p&gt;这个是含金量最高的推广了，虽然受众主要是业内人事，但是给了一个广阔明亮的场地，让大家相互了解下都在研究什么好玩的东西，一起搞搞事情，国内用 ruby 的公司也可以出来宣传下，但是非常可惜没有继续办下去。&lt;/p&gt;
&lt;h3 id="李笑来的全栈营"&gt;李笑来的全栈营&lt;/h3&gt;
&lt;p&gt;这个全栈营对 rails 的推广效果其实很猛，就连我那位在长郡中学教书的大嫂当时都在转全栈营的学习课程，完全两个世界的人啊，要不是她的课比较紧张，估计都会打算学习下 rails 了。5 万块的价格可能有点贵，但我觉得是市场定价没有强买强卖，遗憾的是这个全栈营没有继续做下去。&lt;/p&gt;
&lt;h3 id="我的有问必答"&gt;我的有问必答&lt;/h3&gt;
&lt;p&gt;只要你 &lt;a href="/kayakjiang" class="user-mention" title="@kayakjiang"&gt;&lt;i&gt;@&lt;/i&gt;kayakjiang&lt;/a&gt; 无论什么技术问题，我有问必答，我的回答会迟到，但是不会缺席，也绝不会用 RTFM 或者 google 来回应你，你不用考虑任何提问题的方法，步骤，也不用担心自己提的问题是不是太简单或者初级，just do it 只要把你的问题和疑惑贴上来并 &lt;a href="/kayakjiang" class="user-mention" title="@kayakjiang"&gt;&lt;i&gt;@&lt;/i&gt;kayakjiang&lt;/a&gt; 就行了，唯一的要求是不要像这个人 &lt;a href="https://ruby-china.org/EricWJP" rel="nofollow" target="_blank"&gt;https://ruby-china.org/EricWJP&lt;/a&gt;,  别骂人就行了，回答错了，讨论下指正下就好了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2019/5f2e5e31-21be-4b2e-b294-4e058696259a.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;RTFM[&lt;a href="https://en.wikipedia.org/wiki/RTFM" rel="nofollow" target="_blank"&gt;https://en.wikipedia.org/wiki/RTFM&lt;/a&gt;] 是 read the fucking manual 的意思，还有许多其它的变种，咱不和它们玩：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RTBM ("read the bloody manual") (In some countries, e.g., the UK and Australia, this is a fractionally more polite alternative with identical meaning[5])&lt;/li&gt;
&lt;li&gt;RTFA ("read the fucking/featured article"—common on news forums such as Fark.com[6] and Slashdot, where using "TFA" instead of "the article" has become a meme)&lt;/li&gt;
&lt;li&gt;RTDA ("read the damn article")&lt;/li&gt;
&lt;li&gt;RTDM ("read the damn menu")&lt;/li&gt;
&lt;li&gt;RTDM ("read the damn manual")&lt;/li&gt;
&lt;li&gt;WABM ("write a better manual" – an answer complaining that the manual is not written well)[7]&lt;/li&gt;
&lt;li&gt;RTFE ("read the fucking error")&lt;/li&gt;
&lt;li&gt;RTFM41 ("read the fucking manual for once")&lt;/li&gt;
&lt;li&gt;RTFW ("read the fucking wiki")&lt;/li&gt;
&lt;li&gt;RTFC ("read the fucking chart" – a response that Physicians give to coders who submit inappropriate queries)&lt;/li&gt;
&lt;li&gt;RTFS ("read the fucking source" or "read the fucking standard")[8]&lt;/li&gt;
&lt;li&gt;RTFB ("read the fucking binary")[9]&lt;/li&gt;
&lt;li&gt;RTFQ ("read the fucking question")&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Mon, 29 Apr 2019 13:37:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/38464</link>
      <guid>https://ruby-china.org/topics/38464</guid>
    </item>
    <item>
      <title>怎样在技术论坛里对技术问题进行高效沟通</title>
      <description>&lt;p&gt;一年前我在这个坛子里写了一篇怎么用 Rails 写 API 的文章：&lt;a href="https://ruby-china.org/topics/25822" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/25822&lt;/a&gt;, 写这种入门的文章很费时费力，我要把自己放到一个新人的角度上去考虑很多问题，需要顾及很多细节，同时还要冒着被同行看穿底裤的危险:), 但是这篇文章收获了很多赞，也确实帮助了很多新人，这让我满足，直到现在还不时有人在那篇文章下问我一些问题，我很乐意帮忙解决这些问题，这些问题本来很简单，但是解决的过程很曲折，比如今天有位同学问了这样一个问题：&lt;/p&gt;

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

&lt;p&gt;我第一眼看到这样的问题是懵圈的，没有日志，没有出错的信息，于是我不得不向这位同学请教出错的详细信息是什么，出错相关的日志在哪里，然后又是一阵折腾，最后终于解决问题。如果这位同学能够在提问的时候把错误详情，日志等信息一并提过来，这个问题估计一个来回就能解决了。&lt;/p&gt;

&lt;p&gt;比如还有同学问我：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;新人请教下
bundle exe 命令和 --no-assets 参数为何无法识别？
本人 ruby 版本 2.2.4, rails 4.2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我的懒癌一发作，我是不想去请教到底报了什么出错信息。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;还有同学说某个方法改进下可能会更好，但是这位同学就是不一次性告诉我该怎么改进，而我又实在是懒地打字去请教。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;上面的这些问题如果是面对面交流，可能算不上问题，因为毕竟声速快，多动下嘴皮子也不累。&lt;/p&gt;

&lt;p&gt;说了一些问题，我也举一些正面的例子：&lt;/p&gt;

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

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

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

&lt;p&gt;对于这些同学提的建议和改进我除了感谢就是感动，因为他们提的建议和问题一目了然，不需要我多问，我直接去改就行了。&lt;/p&gt;

&lt;p&gt;有些同学提的问题我没有回复，不是因为我对这些同学有什么意见，而是因为我懒癌一发作就不想多请教，多打字。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;高效沟通的方法就是把对方看作一个随时会懒癌发作的人，把问题尽量描述清楚，把各种信息给足对方。&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Mon, 14 Nov 2016 21:48:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/31594</link>
      <guid>https://ruby-china.org/topics/31594</guid>
    </item>
    <item>
      <title>微信小程序开发和 Rails 开发的相似之处</title>
      <description>&lt;p&gt;最近参照微信小程序的&lt;a href="https://github.com/weui/weui-design?t=20161107" rel="nofollow" target="_blank" title=""&gt;官方基础视觉样式库：https://github.com/weui/weui-design?t=20161107&lt;/a&gt;撸了一套微信小程序的 UI, 在练习的过程中体会到微信小程序开发和 Rails 开发有不少相似之处。&lt;/p&gt;
&lt;h3 id="微信小程序和 Rails 程序的相似之处"&gt;微信小程序和 Rails 程序的相似之处&lt;/h3&gt;
&lt;p&gt;Rails 程序&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/fe23609ecba8a66e5adcac4705f7af81.png!large" title="" alt="client-rails.png"&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;在 Rails 程序中，客户端的请求 (request) 会到达对应的 &lt;code&gt;Controller&lt;/code&gt; 实例，然后由这个 &lt;code&gt;Controller&lt;/code&gt; 实例中的对应的 &lt;code&gt;action&lt;/code&gt; 来处理请求，
处理后的数据会通过 &lt;code&gt;render&lt;/code&gt; 方法响应给客户端。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;微信小程序&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/592e24bb5b3e02c8a1ce7bf297dac1e9.png!large" title="" alt="view-appservice.png"&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;在微信小程序中，&lt;code&gt;View&lt;/code&gt; 对象发送事件 (event) 到对应的 &lt;code&gt;Page&lt;/code&gt; 对象中，然后由这个 &lt;code&gt;Page&lt;/code&gt; 对象中的对应的 &lt;code&gt;event_handler&lt;/code&gt; 来处理事件，
处理后的数据会通过 &lt;code&gt;setData&lt;/code&gt; 方法响应给 &lt;code&gt;View&lt;/code&gt;对象。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="微信小程序练习预览"&gt;微信小程序练习预览&lt;/h3&gt;
&lt;p&gt;代码在 &lt;a href="https://github.com/baya/weui-base-guide-practice" rel="nofollow" target="_blank" title=""&gt;https://github.com/baya/weui-base-guide-practice&lt;/a&gt;, 一共有 36 个页面，欢迎下载玩耍。&lt;/p&gt;

&lt;p&gt;首页：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/4d4283e7b42fee65e059e941114bddfa.png!large" title="" alt="weiui-index"&gt;&lt;/p&gt;

&lt;p&gt;表单错误页面：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/9a5fbcd406307adf1738a8f8a22b0452.png!large" title="" alt="weiui-form-error"&gt;&lt;/p&gt;

&lt;p&gt;列表页面：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/d5eea31508fd97448e926e792f585a3c.png!large" title="" alt="weiui-list"&gt;&lt;/p&gt;

&lt;p&gt;搜索中页面：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/16b67e4824fb2bf43f622d6360da956d.png!large" title="" alt="weiui-searching"&gt;&lt;/p&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Wed, 09 Nov 2016 19:47:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/31560</link>
      <guid>https://ruby-china.org/topics/31560</guid>
    </item>
    <item>
      <title>35 * 2 = 70</title>
      <description>&lt;p&gt;已经解决&lt;/p&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Wed, 09 Nov 2016 13:04:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/31558</link>
      <guid>https://ruby-china.org/topics/31558</guid>
    </item>
    <item>
      <title>Web 安全和 Rails</title>
      <description>&lt;p&gt;这几天遇到一些涉及 web 安全的问题，然后在自己的博客中翻到了这篇两年前的文章，觉得有不错的参考价值，在阅读过程中，我不断的在想外面的世界这么精彩，写这篇文章的家伙为什么会窝在自己的电脑前没日没夜的做试验，写这么长的一篇东西，这也许就是程序员的一种原始的创作欲望吧。文章比较长，demo: &lt;a href="https://github.com/baya/websafe" rel="nofollow" target="_blank" title=""&gt;https://github.com/baya/websafe&lt;/a&gt; 代码可能更适合阅读。&lt;/p&gt;

&lt;p&gt;本文主要描述了 &lt;strong&gt;XSS&lt;/strong&gt;， &lt;strong&gt;CSRF&lt;/strong&gt;， &lt;strong&gt;Session Fixation&lt;/strong&gt; 和 &lt;strong&gt;Brute Force&lt;/strong&gt; 等四种威胁 WEB 安全的攻击手段的概念原理以及相关预防方法。
对于怎么预防 &lt;strong&gt;XSS&lt;/strong&gt; 攻击，本文给出了九种方法，比如  &lt;strong&gt;HTML Escape Before Inserting Untrusted Data into HTML Element Content&lt;/strong&gt; 等，
并结合 rails 对各种预防方法进行了一系列试验，尤其是对 &lt;strong&gt;HTTPOnly&lt;/strong&gt;  和 &lt;strong&gt;Content Security Policy&lt;/strong&gt; 两种方法不厌其烦地进行了十次试验。
同样对于怎么防御 &lt;strong&gt;CSRF&lt;/strong&gt;攻击，本文从其原理着手，结合 rails 进行了一系列有针对性的攻防试验，以帮助我们理解 &lt;strong&gt;CSRF&lt;/strong&gt; 攻击的原理并积累预防 &lt;strong&gt;CSRF&lt;/strong&gt; 攻击的经验。&lt;/p&gt;

&lt;p&gt;平时开发项目时会注意一些比较基本的安全问题，比如数据库里不能用明文保存密码，线上日志过滤掉密码等敏感信息，把任何来自用户的数据看作不安全数据，防止 SQL 注入，防止用户在网页上运行脚本等。
我想现在是时候开始对 WEB 安全方面的知识做一个比较系统的学习了，那就从 &lt;strong&gt;XSS, CSRF, Session Fixation, Brute Force Attack&lt;/strong&gt; 等开始吧。&lt;/p&gt;
&lt;h2 id="XSS"&gt;XSS&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;XSS&lt;/strong&gt; 全称 Cross Site Scripting 即&lt;strong&gt;跨站点脚本攻击&lt;/strong&gt;, 本来 Cross Site Scripting 的缩写应该是 &lt;strong&gt;CSS&lt;/strong&gt;, 但是 &lt;strong&gt;CSS&lt;/strong&gt; 在开发者心中早已经根深蒂固地表示为
&lt;strong&gt;Cascading Style Sheet&lt;/strong&gt; 即层叠样式表的缩写了，还好 Cross 有&lt;strong&gt;十字架&lt;/strong&gt;的意思，而 &lt;strong&gt;X&lt;/strong&gt; 这个字母又像十字架，所以 &lt;strong&gt;XSS&lt;/strong&gt; 这个缩写在某种意义上来说是非常贴近原意的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;XSS&lt;/strong&gt; 简单的来说就是在那些本身善良，受信任的目标网站上注入恶意代码，通过这些恶意代码，攻击者可以实施盗取用户数据，盗取用户的 session 等等危害行为。那攻击者是怎么注入恶意代码的呢？
一个应用或多或少需要接收用户输入的数据，同时如果你的应用在输出用户数据时没有对它们进行验证或者编码或者采取的相关措施不够仔细严格，那么就会给攻击者以可乘之机，攻击者会在很多让你意想不到的地方向你的应用注入恶意代码。&lt;/p&gt;
&lt;h3 id="见招拆招"&gt;见招拆招&lt;/h3&gt;
&lt;p&gt;这些招数来自于 &lt;a href="https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet" rel="nofollow" target="_blank" title=""&gt;https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet&lt;/a&gt;, 为方便以后查阅，我根据自己的理解对其作了一些试验和梳理：&lt;/p&gt;
&lt;h4 id="招数#0 Never Insert Untrusted Data Except in Allowed Locations"&gt;招数#0 &lt;strong&gt;Never Insert Untrusted Data Except in Allowed Locations&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;这招非常狠，我根本不让你插入任何我不信任的数据，纵你再狡猾你也无法对我发起 &lt;strong&gt;XSS&lt;/strong&gt; 攻击了，这就像把电脑网线拔掉来阻止电脑被黑一样。比如在下面的地方，我们不放任何不可信数据：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
任何 HTML 元素里 &amp;lt;body&amp;gt;...不放不可信数据..&amp;lt;/body&amp;gt;

script 标签里 &amp;lt;script&amp;gt;...不放不可信数据..&amp;lt;/script&amp;gt;

HTML 注释里 &amp;lt;!--...不放不可信数据...--&amp;gt;

元素的属性名称里 &amp;lt;div ...不放不可信数据...=test /&amp;gt;

标签名称里 &amp;lt;不放不可信数据 ... href="/test" /&amp;gt;

CSS 里 &amp;lt;style&amp;gt;...不放不可信数据 ...&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如前面所说这招杀伤力过大，如果死板执行的话，会导致我们的应用基本不可用，但是&lt;strong&gt;招数#0&lt;/strong&gt;的最重要的意义是提醒我们插入任何用户数据之前请三思：必须插入这种数据吗？这种数据可信吗？若不可信，
怎么让它变的可信或者至少无害呢？后面的 1-5 招都是在这一基础上进行的：如果我们要插入不可信的数据，那么我们必须让这些不可信数据至少变得无害。
为了方便后面的试验，我建立了一个叫 websafe 的 Rails 项目，代码在此：&lt;a href="https://github.com/baya/websafe" rel="nofollow" target="_blank" title=""&gt;websafe&lt;/a&gt;，后面的所有试验如果没有特殊说明，都是在此项目里进行的。&lt;/p&gt;
&lt;h4 id="招数#1 HTML Escape Before Inserting Untrusted Data into HTML Element Content"&gt;招数#1 &lt;strong&gt;HTML Escape Before Inserting Untrusted Data into HTML Element Content&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;HTML 转义一般来说是进行下面的一些操作，从而阻止可执行内容的产生。&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt; --&amp;gt; &lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nt"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;
&amp;gt; --&amp;gt; &lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;
" --&amp;gt; &lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;
' --&amp;gt; &lt;span class="ni"&gt;&amp;amp;#x27;&lt;/span&gt;
/ --&amp;gt; &lt;span class="ni"&gt;&amp;amp;#x2F;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们看一段 erb 代码，&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Html Escape&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dangerous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"safe"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"safe"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 p.dangerous 是危险的，因为它输出的是原始的用户数据，p.safe 是安全的因为它对用户数据进行了 HTML 转义，如果&lt;/p&gt;

&lt;p&gt;&lt;a href="/evil_user_data" class="user-mention" title="@evil_user_data"&gt;&lt;i&gt;@&lt;/i&gt;evil_user_data&lt;/a&gt; = 'alert(&amp;amp;quot;see you&amp;amp;quot;)'&lt;/p&gt;

&lt;p&gt;那么 p.dangerous 会导致浏览器执行 &lt;code&gt;alert("see you")&lt;/code&gt;, p.safe 则会包含一个经过转义的无害的数据：&lt;/p&gt;

&lt;p&gt;&amp;lt;script&amp;gt;alert("see you")&amp;lt;/script&amp;gt;&lt;/p&gt;

&lt;p&gt;早期 Rails 的版本中 &lt;code&gt;&amp;lt;%= @evil_user_data %&amp;gt;&lt;/code&gt; 等价于 &lt;code&gt;&amp;lt;%=raw @evil_user_data %&amp;gt;&lt;/code&gt;，我们必须显示的使用 &lt;code&gt;h&lt;/code&gt; 方法进行 HTML 转义才能达到
安全的目的，不过现在的 rails 已经改正了这一做法，现在 &lt;code&gt;&amp;lt;%= @evil_user_data %&amp;gt;&lt;/code&gt; 和 &lt;code&gt;&amp;lt;%=h @evil_user_data %&amp;gt;&lt;/code&gt; 是等价的，也就是说在现代的 Rails
中只要我们不显式的去调用 &lt;code&gt;raw&lt;/code&gt;，Rails 能够帮助我们做好 &lt;strong&gt;HTML Escape&lt;/strong&gt; 这一工作的。&lt;/p&gt;
&lt;h4 id="招数#2 Attribute Escape Before Inserting Untrusted Data into HTML Common Attributes"&gt;招数#2 &lt;strong&gt;Attribute Escape Before Inserting Untrusted Data into HTML Common Attributes&lt;/strong&gt;
&lt;/h4&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;AttributeEscapeController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;lt;script&amp;gt;alert(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;XSS&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)&amp;lt;/script&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="vi"&gt;@evil_user_data_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;lt;script&amp;gt;alert(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;XSS&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)&amp;lt;/script&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\"\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="vi"&gt;@evil_user_data_3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"javascript:var a='XSS'; alert(a)"&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 erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Attribute Escape&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dangerous1"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello Danger&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dangerous2"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data_2&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt; &lt;span class="na"&gt;Danger&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="na"&gt;p&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dangerous3"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data_3&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;XSS&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;br/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"safe"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello Safe&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"safe"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello Safe&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;p.dangerous1, p.dangerous2 和 a.dangerous3 会被恶意数据侵入，变成：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dangerous"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;see you&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;""&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;Hello Danger&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dangerous"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;see you&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;""&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;Hello Danger&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dangerous3"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"javascript:var a='XSS'; alert(a)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;XSS&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意 a.dangerous3 仅通过 &lt;code&gt;h&lt;/code&gt; 方法还是不够的，我们需要去掉 'javascript' 这种能够引起恶意代码执行的字符串，所以我们有 a.safe2,&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"safe2"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;%=h @evil_user_data_3.gsub('javascript', '') %&amp;gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello Safe&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;br/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="招数#3 JavaScript Escape Before Inserting Untrusted Data into JavaScript Data Values"&gt;招数#3 &lt;strong&gt;JavaScript Escape Before Inserting Untrusted Data into JavaScript Data Values&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;当我们需要在 JavaScript 代码里插入用户数据时，我们需要进行 JavaScript Escape, 现在我们进行一次 XSS 攻击试验，&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;JsEscapeController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;;alert(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;XSS&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Js Escape&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;user_dangerous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;user_safe1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;user_safe2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;user_safe3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;escape_javascript&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;user_safe4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="vi"&gt;@evil_user_data1.to_json&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;user_dangerous 会引入 &lt;strong&gt;XSS&lt;/strong&gt; 攻击，它会变成：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;user_dangerous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;XSS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="c1"&gt;//";&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;user_safe1, user_safe2, user_safe3, user_safe4 虽然可能导致数据不正确，但至少都是安全的。&lt;/p&gt;
&lt;h4 id="招数#3.1 HTML escape JSON values in an HTML context and read the data with JSON.parse"&gt;招数#3.1 &lt;strong&gt;HTML escape JSON values in an HTML context and read the data with JSON.parse&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;基于这一方法，如果我们需要响应浏览器 JSON 数据，那么我们应该保证响应头的 &lt;strong&gt;Content-Type&lt;/strong&gt; 为 &lt;strong&gt;application/json&lt;/strong&gt; 而不是 &lt;strong&gt;text/html&lt;/strong&gt;, 否则浏览器
会直接执行响应报文里地 javascript 代码。试验如下，&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;JsonEscapeController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Message&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;No HTTP resource was found that matches the request URI 'dev.net.ie/api/pay/.html?HouseNumber=9&amp;amp;AddressLine
   =The+Gardens&amp;lt;script&amp;gt;alert(XSS)&amp;lt;/script&amp;gt;&amp;amp;AddressLine2=foxlodge+woods&amp;amp;TownName=Meath'.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MessageDetail&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;No type was found
   that matches the controller named 'pay'.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;

    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="vi"&gt;@evil_user_data&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 plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

{"Message":"No HTTP resource was found that matches the request URI 'dev.net.ie/api/pay/.html?HouseNumber=9&amp;amp;AddressLine
   =The+Gardens&amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&amp;amp;AddressLine2=foxlodge+woods&amp;amp;TownName=Meath'.","MessageDetail":"No type was found
   that matches the controller named 'pay'."}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种响应会导致浏览器执行代码 &lt;code&gt;alert('XSS')&lt;/code&gt;, 也就是说我们的应用被 XSS 了。但是如果我们将响应头的 &lt;strong&gt;Content-Type&lt;/strong&gt; 设置为 &lt;strong&gt;application/json&lt;/strong&gt;
就可以避免这种侵入，在 Rails 中可以像下面这样做：&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;JsonEscapeController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@evil_user_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Message&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;No HTTP resource was found that matches the request URI 'dev.net.ie/api/pay/.html?HouseNumber=9&amp;amp;AddressLine
   =The+Gardens&amp;lt;script&amp;gt;alert(XSS)&amp;lt;/script&amp;gt;&amp;amp;AddressLine2=foxlodge+woods&amp;amp;TownName=Meath'.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MessageDetail&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;No type was found
   that matches the controller named 'pay'.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;

    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@evil_user_data.to_json&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 plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

"{\"Message\":\"No HTTP resource was found that matches the request URI 'dev.net.ie/api/pay/.html?HouseNumber=9\u0026AddressLine\n   =The+Gardens\u003cscript\u003ealert('XSS')\u003c/script\u003e\u0026AddressLine2=foxlodge+woods\u0026TownName=Meath'.\",\"MessageDetail\":\"No type was found\n   that matches the controller named 'pay'.\"}"
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="招数#4 CSS Escape And Strictly Validate Before Inserting Untrusted Data into HTML Style Property Values"&gt;招数#4 &lt;strong&gt;CSS Escape And Strictly Validate Before Inserting Untrusted Data into HTML Style Property Values&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;往 HTML 样式的属性里插入不可信数据时需要进行 CSS Escape 并且对数据进行严格校验，比如 URLs 只能以 "http"开头而不是"javascript"，同时各属性值永远不要以
"expression" 开头。原文中给了两个例子：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ background-url : "javascript:alert(1)"; }  // and all other URLs
{ text-size: "expression(alert('XSS'))"; }   // only in IE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一个例子在 chrome 和 safari 浏览器里做过测试，不会引导致 XSS，应该是现代的浏览器已经把这个漏洞堵住了。第二个例子，没有 IE 就没试验了。&lt;/p&gt;
&lt;h4 id="招数#5 URL Escape Before Inserting Untrusted Data into HTML URL Parameter Values"&gt;招数#5 &lt;strong&gt;URL Escape Before Inserting Untrusted Data into HTML URL Parameter Values&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;向 HTML URL 的参数里插入不可信数据之前，需要对不可信数据做 URL Escape。在 Rails 里可以通过 CGI.escape 或者 URI.encode 做这个工作。&lt;/p&gt;
&lt;h4 id="招数#6 Sanitize HTML Markup with a Library Designed for the Job"&gt;招数#6 &lt;strong&gt;Sanitize HTML Markup with a Library Designed for the Job&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;在 Rails 中可以使用方法 &lt;code&gt;sanitize&lt;/code&gt; 做这方面的工作。&lt;code&gt;sanitize&lt;/code&gt; 使用白名单制度，只有被允许的标签和属性才能作为可信数据插入到页面中。&lt;/p&gt;
&lt;h4 id="招数#7 Prevent DOM-based XSS"&gt;招数#7 &lt;strong&gt;Prevent DOM-based XSS&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;DOM-based XSS&lt;/strong&gt; 就是在操作 DOM 环境时向受害者的浏览器注入恶意代码。文中给出了一个攻击例子，&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;Select your language:
&lt;span class="nt"&gt;&amp;lt;select&amp;gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;OPTION value=2&amp;gt;English&amp;lt;/OPTION&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;OPTION value=1&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/OPTION&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在我的试验中，预想访问 &lt;code&gt;http://localhost:3000/dom_base_xss?default=&amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&lt;/code&gt; 会生成恶意代码：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XSS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是浏览器会自动把 &lt;code&gt;&amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&lt;/code&gt; 转义为 "%3Cscript%3Ealert(%27xss%27)%3C/script%3E", 所以试验没有成功，但是我们还是需要在 DOM-based XSS 这方面引起重视，毕竟道高一尺，魔高一丈。&lt;/p&gt;
&lt;h4 id="加强版招数#1 Use HTTPOnly cookie flag"&gt;加强版招数#1 Use HTTPOnly cookie flag&lt;/h4&gt;
&lt;p&gt;在 HTTP 的 Set-Cookie 响应头里增加一个 HTTPOnly 标识就可以使用 HTTPOnly cookie 了，当然必须浏览器支持，否则浏览器会忽略此标识，如果浏览器支持 HTTPOnly 标识，
那么客户端的代码就无法访问到这些受到保护的 cookie。我觉得这是一个非常有用的功能，这样即使站点被 XSS 污染，攻击者也拿不到 cookie 这类非常重要的数据，从而
能最大程度地减少站点和用户的损失。现在我们用一个实际例子来试验设置 HTTPOnly cookie flag。&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;HttponlyFlagController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user_name"&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="ss"&gt;value: &lt;/span&gt;&lt;span class="s2"&gt;"david"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;httponly: &lt;/span&gt;&lt;span class="kp"&gt;true&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;false&lt;/span&gt;
    &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user_name"&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="ss"&gt;value: &lt;/span&gt;&lt;span class="s2"&gt;"david"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;httponly: &lt;/span&gt;&lt;span class="kp"&gt;false&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;httponly_flag/true.html.erb&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;HTTPOnly True&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;c_start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_start&lt;/span&gt;&lt;span class="o"&gt;!=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;c_start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;c_start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nx"&gt;c_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;c_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_end&lt;/span&gt;&lt;span class="o"&gt;==-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;c_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;unescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;c_end&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;return&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置 HTTPOnly flag 时，其试验结果如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/bfed9c611f376ee2b57cbb9ed9bee37c.png!large" title="" alt="HTTPOnly True"&gt;&lt;/p&gt;

&lt;p&gt;我们看到 cookies["user_name"] 受到保护，客户端无法访问到 cookies["user_name"]。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;httponly_flag/false.html.erb&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;HTTPOnly False&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

 &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;c_start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_start&lt;/span&gt;&lt;span class="o"&gt;!=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;c_start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;c_start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nx"&gt;c_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;c_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_end&lt;/span&gt;&lt;span class="o"&gt;==-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;c_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;unescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;c_end&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;return&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;未设置 HTTPOnly flag 时，试验结果如下图所示，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/62b1ec6ac83e26d6b003a602d99b3330.png!large" title="" alt="HTTPOnly False"&gt;&lt;/p&gt;

&lt;p&gt;我们看到客户端此时可以访问 cookies["admin_name"]。&lt;/p&gt;

&lt;p&gt;我们再看看 cookies['user_name'] 和 cookies['admin_name'] 的属性，注意看最后一条属性：'脚本可访问'&lt;/p&gt;

&lt;p&gt;cookies['user_name']:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;名字：   user_name

内容：   david

域：  localhost

路径：   /

发送用途： 各种连接

脚本可访问：  否（仅 Http）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;cookies['admin_name']:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;名字：   admin_name

内容：   jim

域：  localhost

路径：   /

发送用途： 各种连接

脚本可访问：  是
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="加强版招数#2 Implement Content Security Policy"&gt;加强版招数#2 Implement Content Security Policy&lt;/h4&gt;
&lt;p&gt;Web 世界在经历 &lt;strong&gt;XSS&lt;/strong&gt; 这个魔鬼多年的折磨和迫害之后， &lt;strong&gt;CSP&lt;/strong&gt; 的出现可以说是大势所趋，民心所向。&lt;strong&gt;CSP&lt;/strong&gt; 之前的各种抵抗 &lt;strong&gt;XSS&lt;/strong&gt; 的方案虽然有效果，并且广泛使用，但是这些方案无论
从哪个角度来看都像是在缝缝补补，并且在 &lt;strong&gt;XSS&lt;/strong&gt; 疯狂的攻击下疲于应付，始终摆脱不了道高一尺，魔高一仗的咒语。而 &lt;strong&gt;CSP&lt;/strong&gt; 的出现，让我们终于拥有了强大而系统化的武器去抗击 &lt;strong&gt;XSS&lt;/strong&gt; 的侵害。&lt;/p&gt;

&lt;p&gt;在响应头 &lt;strong&gt;Content-Security-Policy&lt;/strong&gt; 里添加一些 &lt;strong&gt;CSP&lt;/strong&gt; 相关的指令就可以实现 &lt;strong&gt;CSP&lt;/strong&gt; 了，有些浏览器支持 &lt;strong&gt;X-WebKit-CSP&lt;/strong&gt; 或者 &lt;strong&gt;X-Content-Security-Policy&lt;/strong&gt;, 不过向
前看，现代的浏览器都会支持 &lt;strong&gt;Content-Security-Policy&lt;/strong&gt; 头，而忽略掉那些带前缀的 &lt;strong&gt;CSP&lt;/strong&gt; 头，所以我们应该使用 &lt;strong&gt;Content-Security-Policy&lt;/strong&gt; 头，在这里我们的 &lt;strong&gt;CSP&lt;/strong&gt; 头如无
说明即指 &lt;strong&gt;Content-Security-Policy&lt;/strong&gt; 头。&lt;strong&gt;CSP&lt;/strong&gt; 在控制页面加载资源的粒度上为我们开发者提供了丰富的指令，我们可以先快速了解下这些指令：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;default-src&lt;/strong&gt;, 用于为其他指令配置一个默认的资源列表;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;script-src&lt;/strong&gt;,  用于约束受保护的资源可以执行哪些脚本;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;object-src&lt;/strong&gt;,  用于约束受保护的资源可以从哪些地方加载插件;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;style-src&lt;/strong&gt;,   用于约束受保护的资源可以应用哪些样式;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;img-src&lt;/strong&gt;,     用于约束受保护的资源可以从哪些地方加载图片;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;media-src&lt;/strong&gt;,   用于约束受保护的资源可以从哪些地方加载视频和声频;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;frame-src&lt;/strong&gt;,   用于约束受保护的资源可以从哪些地方嵌入帧 (frame);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;font-src&lt;/strong&gt;,    用于约束受保护的资源可以从哪些地方加载字体;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;connect-src&lt;/strong&gt;, 用于约束受保护的资源可以使用脚本交互的方式 (比如 XMLHttpRequest, WebSocket, EventSource) 打开哪些 URI;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如我们前面所看到的，&lt;strong&gt;XSS&lt;/strong&gt; 攻击最主要的威胁来自于内联脚本的嵌入，所以我们着重研究下指令 &lt;strong&gt;script-src&lt;/strong&gt;, 并做一系列和 &lt;strong&gt;script-src&lt;/strong&gt; 指令有关的试验，在试验之前我们需要说明两点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CSP&lt;/strong&gt; 指令基于白名单，也就是说只有匹配的资源才会允许加载，执行;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CSP&lt;/strong&gt; 指令之间用 ";" 号分开，指令的值用空格分开，比如，&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Content-Security-Policy: script-src self https://apis.google.com; report-uri /my_csp_report_parser;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在试验之前我们认识一个新指令：&lt;strong&gt;report-uri&lt;/strong&gt;, 这个指令的作用是指定一个 &lt;strong&gt;URI&lt;/strong&gt;, 当用户代理发现有违背 &lt;strong&gt;CSP&lt;/strong&gt; 的事件时就会向这个 &lt;strong&gt;URI&lt;/strong&gt; 发送报告。&lt;/p&gt;

&lt;p&gt;测试环境：Rails-4.1.5, Chrome38, 为了能够接收到浏览器发送的 &lt;strong&gt;CSP&lt;/strong&gt; 违规报告，我们的每一次试验都会设置 &lt;strong&gt;report-uri&lt;/strong&gt; 为：'/csp_report'。&lt;/p&gt;

&lt;p&gt;与 '/csp_report' 对应的控制器是 &lt;code&gt;CspReportController&lt;/code&gt;, 在此控制器里我们会打印出 &lt;strong&gt;CSP&lt;/strong&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;CspReportController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="n"&gt;skip_before_filter&lt;/span&gt; &lt;span class="ss"&gt;:verify_authenticity_token&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"received csp report: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&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;raw_post&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="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&gt;'ok'&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;h5 id="试验一 script-src 'none'"&gt;试验一 &lt;code&gt;script-src 'none'&lt;/code&gt;
&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;'none' 的意思是禁止加载任何脚本，无论此脚本来自本站还是外站，也不允许执行任何内联脚本。&lt;/p&gt;
&lt;/blockquote&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;Csp::ScriptSrcController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;none&lt;/span&gt;
    &lt;span class="n"&gt;set_csp&lt;/span&gt; &lt;span class="s2"&gt;"script-src 'none'"&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;set_csp&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP: script-src 'none'&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I will be disabled by csp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试验的结果是，&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;'&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 被阻止加载;&lt;/li&gt;
&lt;li&gt;'&lt;a href="http://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js" rel="nofollow" target="_blank"&gt;http://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js&lt;/a&gt;' 被阻止加载;&lt;/li&gt;
&lt;li&gt;内联脚本被阻止执行;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我们可以通过 Chrome 的终端查看这个结果，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/82c2dbe762f8a82569e2d8283229d50a.png!large" title="" alt="csp_test_script_src_none"&gt;&lt;/p&gt;

&lt;p&gt;同时我们可以在 '/csp_report' 后台看到相关的 &lt;strong&gt;CSP&lt;/strong&gt; 违规报告，如下所示，&lt;/p&gt;

&lt;p&gt;加载 '&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 时触发的违规报告：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'none'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'none'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/assets/application.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们注意到 "blocked-uri"=&amp;gt;"&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;", 即来自 "&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;" 的脚本资源被阻止加载。&lt;/p&gt;

&lt;p&gt;加载 '&lt;a href="http://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js" rel="nofollow" target="_blank"&gt;http://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js&lt;/a&gt;' 时触发的违规报告：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'none'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'none'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://upcdn.b0.upaiyun.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们注意到 "blocked-uri"=&amp;gt;"&lt;a href="http://upcdn.b0.upaiyun.com" rel="nofollow" target="_blank"&gt;http://upcdn.b0.upaiyun.com&lt;/a&gt;", 即来自 "upcdn.b0.upaiyun.com" 的脚本资源被阻止加载。&lt;/p&gt;

&lt;p&gt;执行内联脚本时触发的违规报告，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'none'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'none'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="试验二 script-src 'self'"&gt;试验二 &lt;code&gt;script-src 'self'&lt;/code&gt;
&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;'self' 表示允许加载同源的脚本，不允许加载不同源的脚本，不允许执行内联脚本。&lt;/p&gt;
&lt;/blockquote&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;Csp::ScriptSrcController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;self&lt;/span&gt;
    &lt;span class="n"&gt;set_csp&lt;/span&gt; &lt;span class="s2"&gt;"script-src 'self'"&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;set_csp&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP: script-src 'self'&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I will be disabled by csp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试验的结果是，&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;'&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 可以正常加载;&lt;/li&gt;
&lt;li&gt;'&lt;a href="http://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js" rel="nofollow" target="_blank"&gt;http://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js&lt;/a&gt;' 被阻止加载;&lt;/li&gt;
&lt;li&gt;内联脚本被阻止执行;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;相关 &lt;strong&gt;CSP&lt;/strong&gt; 违规报告如下所示，&lt;/p&gt;

&lt;p&gt;加载 '&lt;a href="http://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js" rel="nofollow" target="_blank"&gt;http://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.3.min.js&lt;/a&gt;' 时触发的违规报告：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/self"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://upcdn.b0.upaiyun.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&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="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/self"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="试验三 script-src 'self' ${EXTERNAL-SCRIPT-LIST}"&gt;试验三 &lt;code&gt;script-src 'self' ${EXTERNAL-SCRIPT-LIST}&lt;/code&gt;
&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;既可以加载同源脚本，也可以加载外部的与资源列表相匹配的脚本，但是不允许执行内联脚本。&lt;/p&gt;
&lt;/blockquote&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;Csp::ScriptSrcController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;self_ext&lt;/span&gt;
    &lt;span class="n"&gt;set_csp&lt;/span&gt; &lt;span class="s2"&gt;"script-src 'self' http://example.com *.google.com"&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;set_csp&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 erb"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP: script-src 'self external'&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//example.com/a1.js"&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//example.com/a2.js"&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//evil.example.com/b1.js"&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//apis.google.com/d1.js"&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//apis.google.com/d2.js"&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//apis.google.com/d3.js"&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I will be disabled by csp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以通过 Chrome 的 console 查看试验结果，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/4f0ba076343fb2a0407d78083b05d003.png!large" title="" alt="csp_script_src_self_external"&gt;&lt;/p&gt;

&lt;p&gt;试验结果：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;'&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 匹配 'self', 浏览器会对正常请求此脚本。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;'&lt;a href="http://example.com/a1.js" rel="nofollow" target="_blank"&gt;http://example.com/a1.js&lt;/a&gt;', '&lt;a href="http://example.com/a2.js" rel="nofollow" target="_blank"&gt;http://example.com/a2.js&lt;/a&gt;' 匹配 '&lt;a href="http://example.com" rel="nofollow" target="_blank"&gt;http://example.com&lt;/a&gt;', 浏览器会正常请求这些脚本。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;'&lt;a href="http://evil.example.com/b1.js" rel="nofollow" target="_blank"&gt;http://evil.example.com/b1.js&lt;/a&gt;', 不匹配任何 script-src 指令，浏览器拒绝请求此脚本&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;'&lt;a href="https://plus.google.com/d1.js" rel="nofollow" target="_blank"&gt;https://plus.google.com/d1.js&lt;/a&gt;', '&lt;a href="https://plus.google.com/d2.js" rel="nofollow" target="_blank"&gt;https://plus.google.com/d2.js&lt;/a&gt;' 和 '&lt;a href="https://plus.google.com/d3.js" rel="nofollow" target="_blank"&gt;https://plus.google.com/d3.js&lt;/a&gt;' 匹配 '*.google.com', 浏览器正常请求这些脚本。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;内联脚本不匹配任何 script-src 指令，浏览器拒绝执行内联脚本。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;CSP&lt;/strong&gt; 违规报告：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;加载 '&lt;a href="http://evil.example.com/b1.js" rel="nofollow" target="_blank"&gt;http://evil.example.com/b1.js&lt;/a&gt;' 触发的违规报告，&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/self-ext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' http://example.com *.google.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"script-src 'self' http://example.com *.google.com; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://evil.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;执行内联脚本时触发的违规报告，&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/self-ext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' http://example.com *.google.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"script-src 'self' http://example.com *.google.com; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="试验四 script-src 'self' 'nonce-$RANDOM'"&gt;试验四 &lt;code&gt;script-src 'self' 'nonce-$RANDOM'&lt;/code&gt;
&lt;/h5&gt;
&lt;p&gt;阻止内联脚本的执行能够很大程度上减轻 &lt;strong&gt;XSS&lt;/strong&gt; 对站点的攻击，但是完全阻止执行内联脚本也是很困难或者说不现实的，所以 &lt;strong&gt;script-src&lt;/strong&gt; 指令引入了 &lt;strong&gt;nonce&lt;/strong&gt; 值，通过设置 &lt;strong&gt;nonce&lt;/strong&gt; 值，我们
可以指定我们认为安全的内联脚本被允许执行。&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;Csp::ScriptSrcController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;self_nonce&lt;/span&gt;
    &lt;span class="vi"&gt;@rand&lt;/span&gt; &lt;span class="o"&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="n"&gt;set_csp&lt;/span&gt; &lt;span class="s2"&gt;"script-src 'self' 'nonce-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@rand&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;set_csp&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 erb"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP: script-src 'self-nonce'&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"blocked"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I will be blocked by csp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"blocked"&lt;/span&gt; &lt;span class="na"&gt;nonce=&lt;/span&gt;&lt;span class="s"&gt;"123"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I will be blocked by csp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"allowed"&lt;/span&gt; &lt;span class="na"&gt;nonce=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@rand&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Allowed because nonce is valid.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以通过 Chrome 终端查看试验结果，&lt;/p&gt;

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

&lt;p&gt;试验结果：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;script.blocked 都会被阻止执行，第二个 script.blocked 虽然有 nonce 值，但是与服务器生成的 nonce 值不匹配，所以仍然会被阻止执行。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;script.allowed 被允许执行&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;'&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 匹配 'self', 浏览器会对正常请求此脚本。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;CSP&lt;/strong&gt; 违规报告：&lt;/p&gt;

&lt;p&gt;script.blocked 触发了两次，但是只生成了一份违规报告，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/self-nonce"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"script-src 'self' 'nonce-ca36b262e9019c9e320ac0628f0fa727'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"script-src 'self' 'nonce-ca36b262e9019c9e320ac0628f0fa727'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="试验五 script-src 'unsafe-inline'"&gt;试验五 &lt;code&gt;script-src 'unsafe-inline'&lt;/code&gt;
&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;'unsafe-inline' 匹配内联脚本，即允许执行内联脚本，即使允许执行，也通过 unsafe 提醒开发者内联脚本是
不安全的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;与 'nonce' 可以指定某一块的内联脚本允许执行不同，'unsafe-inline' 可以让整个页面的内联脚本被允许执行。&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;Csp::ScriptSrcController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unsafe_inline&lt;/span&gt;
    &lt;span class="n"&gt;set_csp&lt;/span&gt; &lt;span class="s2"&gt;"script-src 'unsafe-inline'"&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;set_csp&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 erb"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP: script-src 'unsafe-inline'&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"allowed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I be allowed by 'unsafe-inline'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试验结果：&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;script.allowed 匹配 'unsafe-inline'，被允许执行;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;'&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 被阻止加载;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;CSP&lt;/strong&gt; 违规报告：&lt;/p&gt;

&lt;p&gt;加载 '&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 触发违规报告，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/unsafe-inline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'unsafe-inline'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'unsafe-inline'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/assets/application.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="试验六 default-src 'self'"&gt;试验六 &lt;code&gt;default-src 'self'&lt;/code&gt;
&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;如果没有指定 script-src, 浏览器会使用 default-src 'self' 进行匹配。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在试验六我们看到加载 '&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 会触发违规报告，
于是我们想通过设置 &lt;code&gt;default-src 'self'&lt;/code&gt;, 试验能否加载 '&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;'&lt;/p&gt;

&lt;p&gt;我们先做第一个试验，只设置 &lt;code&gt;default-src 'self'&lt;/code&gt;, 而不设置 &lt;code&gt;script-src&lt;/code&gt;,&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;Csp::DefaultSrcController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;self&lt;/span&gt;
    &lt;span class="n"&gt;set_csp&lt;/span&gt; &lt;span class="s2"&gt;"default-src 'self'"&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;set_csp&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP: default-src 'self'&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//evil.example.com/b1.js"&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"blocked"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I will be blocked by csp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试验结果：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;'&lt;a href="http://evil.example.com/b1.js" rel="nofollow" target="_blank"&gt;http://evil.example.com/b1.js&lt;/a&gt;' 被阻止加载;&lt;/li&gt;
&lt;li&gt; script.blocked 被阻止执行;&lt;/li&gt;
&lt;li&gt;'&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 可以被正常加载；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;第二个试验，设置 &lt;code&gt;default-src 'self'; script-src 'unsafe-inline''&lt;/code&gt;,&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;Csp::MiscController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default_script_src&lt;/span&gt;
    &lt;span class="n"&gt;set_csp&lt;/span&gt; &lt;span class="s2"&gt;"default-src 'self'; script-src 'unsafe-inline'"&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;set_csp&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 erb"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP: default-src 'self'; script-src 'unsafe-inline'&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"allowed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I be allowed by 'unsafe-inline'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试验结果，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/84700d3e663dd4a25cf2ce838191ea34.png!large" title="" alt="csp-default-script-src"&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;'&lt;a href="http://localhost:3000/assets/application.js" rel="nofollow" target="_blank"&gt;http://localhost:3000/assets/application.js&lt;/a&gt;' 不被允许加载，即使设置了 &lt;code&gt;default-src 'self'&lt;/code&gt;, 这说明一旦设置了 &lt;code&gt;script-src&lt;/code&gt; 的值，&lt;code&gt;default-src&lt;/code&gt; 设置的值对 &lt;code&gt;script-src&lt;/code&gt; 会失效;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;script.allowed 允许执行；&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id="试验七 script-src 'unsafe-eval'"&gt;试验七 &lt;code&gt;script-src 'unsafe-eval'&lt;/code&gt;
&lt;/h5&gt;
&lt;p&gt;一些侵入的文本可以把 eval(), new Function(), setTimeout([string], ...), and setInterval([string], ...) 等作为载体执行一些恶意任务，首先我们试验下 &lt;strong&gt;CSP&lt;/strong&gt; 是否是默认阻止这些载体的，&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;Csp::ScriptSrcController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;block_unsafe_eval&lt;/span&gt;
    &lt;span class="n"&gt;set_csp&lt;/span&gt; &lt;span class="s2"&gt;"script-src 'unsafe-iinline'"&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;set_csp&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP: script-src 'block unsafe-eval'&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"allowed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

 &lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alert('XSS')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"allowed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document.querySelector('a').style.display = 'none';&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clock()&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;myFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return a * b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clock&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试验结果：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/315627d2d42c8906a6ffa726b3763b04.png!large" title="" alt="block_unsafe_eval"&gt;&lt;/p&gt;

&lt;p&gt;我们看到 eval, setTimeout, setInterval, new Function() 都被阻止执行了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSP&lt;/strong&gt; 违规报告，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/block-unsafe-eval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' 'unsafe-inline'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"script-src 'self' 'unsafe-inline'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"source-file"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/block-unsafe-eval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"line-number"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"column-number"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;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="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/block-unsafe-eval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' 'unsafe-inline'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"script-src 'self' 'unsafe-inline'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"source-file"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/block-unsafe-eval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"line-number"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"column-number"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;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="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/block-unsafe-eval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' 'unsafe-inline'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"script-src 'self' 'unsafe-inline'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"source-file"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/block-unsafe-eval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"line-number"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"column-number"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;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="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/block-unsafe-eval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' 'unsafe-inline'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"script-src 'self' 'unsafe-inline'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"source-file"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/script-src/block-unsafe-eval"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"line-number"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"column-number"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;报告中通过 'line-number', 'column-number' 帮我们指出了违规处的具体的行号和列号。&lt;/p&gt;

&lt;p&gt;如果我们需要在脚本中执行 eval(), new Function(), setTimeout([string], ...), and setInterval([string], ...) 等方法，那么我们可以设置 &lt;code&gt;script-src 'unsafe-eval''&lt;/code&gt;，现在我们再做一个试验，&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;Csp::ScriptSrcController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unsafe_eval&lt;/span&gt;
    &lt;span class="n"&gt;set_csp&lt;/span&gt; &lt;span class="s2"&gt;"script-src 'self' 'unsafe-inline' 'unsafe-eval'"&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;set_csp&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP: script-src 'unsafe-eval'&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"allowed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

 &lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alert('XSS')&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"allowed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document.querySelector('a').style.display = 'none';&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clock()&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;myFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return a * b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clock&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试验结果：&lt;/p&gt;

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

&lt;p&gt;我们看到 eval(), new Function(), setTimeout([string], ...), and setInterval([string], ...) 等方法可以执行，后台也没有收到任何违规报告。&lt;/p&gt;
&lt;h5 id="试验八 Content-Security-Policy-Report-Only: default-src 'self'"&gt;试验八 &lt;code&gt;Content-Security-Policy-Report-Only: default-src 'self'&lt;/code&gt;
&lt;/h5&gt;
&lt;p&gt;如果你对 &lt;strong&gt;CSP&lt;/strong&gt; 不够熟悉，那么在开始应用 &lt;strong&gt;CSP&lt;/strong&gt; 时很可能会出问题，并且造成站点的可用性大打折扣，前期我们可以使用 &lt;strong&gt;Content-Security-Policy-Report-Only&lt;/strong&gt; 代替&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content-Security-Policy&lt;/strong&gt;, 这样浏览器不会阻止哪些违规的资源，但是会将违规报告发到我们指定的接收地址，这样有助于我们监控哪些地方可能会受到 &lt;strong&gt;XSS&lt;/strong&gt;攻击，并采取适当的
措施增强应用安全。现在我们来做一个试验来熟悉 &lt;strong&gt;Content-Security-Policy-Report-Only&lt;/strong&gt; 的工作机制。&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;Csp::ReportOnlyController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="n"&gt;set_csp_report_only&lt;/span&gt; &lt;span class="s2"&gt;"script-src 'self'"&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;set_csp_report_only&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Security-Policy-Report-Only'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csp_str&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"; report-uri /csp_report"&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 erb"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;CSP-Report-Only: Index&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"//evil.example.com/a1.js"&lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
 &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I should be blocked by csp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试验结果：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/2c73fd449f3a389b1f2e48ba266f501d.png!large" title="" alt="csp_report_only_test"&gt;&lt;/p&gt;

&lt;p&gt;我们可以到虽然浏览器识别出了违规的地方，但是并没有阻止它们继续执行，同时我们可以收到违规报告，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/report-only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://evil.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;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="s2"&gt;"csp-report"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"document-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/csp/report-only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"referrer"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"violated-directive"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"original-policy"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self'; report-uri /csp_report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"blocked-uri"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s2"&gt;"status-code"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在可以对预防 &lt;strong&gt;XSS&lt;/strong&gt; 攻击做一个小结了，&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;如果是 rails 的项目，在视图中慎重使用 &lt;code&gt;raw&lt;/code&gt; 方法能够防止大部分的恶意代码侵入页面;&lt;/li&gt;
&lt;li&gt;尽可能地使用 &lt;strong&gt;HTTPOnly&lt;/strong&gt; 头，保住底裤；&lt;/li&gt;
&lt;li&gt;鼓励用户使用现代化的浏览器特别是支持 &lt;strong&gt;Content-Security-Policy&lt;/strong&gt; 的浏览器，并且我们自己也尽最大努力提供合适的 &lt;strong&gt;Content-Security-Policy&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;对自己的站点多做些 &lt;strong&gt;XSS&lt;/strong&gt; 攻击，争取在攻击者之前发现漏洞；&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="CSRF"&gt;CSRF&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;CSRF&lt;/strong&gt; 全称 &lt;strong&gt;Cross-Site Request Forgery&lt;/strong&gt;, 意思是跨站点请求伪造。&lt;strong&gt;CSRF&lt;/strong&gt; 的攻击原理比较好理解，比如我在站点 &lt;strong&gt;A&lt;/strong&gt; 登录过了，然后我用同一个浏览器去访问站点 &lt;strong&gt;B&lt;/strong&gt; 的某个页面，
很不幸，此页面上被某些攻击者添加了一些恶意代码或者链接，如果我在 &lt;strong&gt;A&lt;/strong&gt; 上的会话 (session) 没有过期，那么这些恶意代码或者链接就可能以我的名义绕过认证去作一些恶意的操作等。&lt;strong&gt;CSRF&lt;/strong&gt; 攻击的过程
大致如下图所示，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/18f9fe516002ca231098c0a0acfb67da.png!large" title="" alt="CSRF 攻击过程"&gt;&lt;/p&gt;

&lt;p&gt;通过实际的动手操作，能够加深我们对概念的理解，所以我觉得很有必要自己动手模拟下 &lt;strong&gt;CSRF&lt;/strong&gt; 的攻防，现在我们开始&lt;/p&gt;
&lt;h3 id="模拟 CSRF 攻防"&gt;模拟 CSRF 攻防&lt;/h3&gt;
&lt;p&gt;试验环境：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;操作系统：Mac OSX&lt;/li&gt;
&lt;li&gt;服务器：Pow&lt;/li&gt;
&lt;li&gt;rails-4.1.5&lt;/li&gt;
&lt;li&gt;浏览器：Chrome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用 Pow 是为了可以使用类似于 webapp1.dev, webaap2.dev 的域名访问本地应用，详细的介绍可以参考:
&lt;a href="http://pow.cx/manual.html" rel="nofollow" target="_blank" title=""&gt;Pow 用户手册&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;首先建立两个站点，一个用作被攻击的站点：V, 另外一个用作发起攻击的站点 A, 为此我们将 &lt;a href="https://github.com/baya/websafe" rel="nofollow" target="_blank" title=""&gt;websafe&lt;/a&gt;
这个项目分别拷贝为 'websafe-csrf-victim' 和 'websafe-csrf-attacker'&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/workspace
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; websafe websafe-csrf-victim
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; websafe websafe-csrf-attacker

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/.pow
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; ~/workspace/websafe-csrf-victim
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; ~/workspace/websafe-csrf-attacker

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时我们就已经建立好了两个用于试验的站点了。&lt;/p&gt;
&lt;h4 id="用例1"&gt;用例 1&lt;/h4&gt;
&lt;p&gt;站点 V(在此试验中可以通过 'websafe-csrf-victim.dev' 访问站点 V, 在后面的描述中，站点 V 即代表
'websafe-csrf-victim.dev'), 有一个地址：'/csrf/admin/admin_user/unsafe_create' 可以用于创建管理员，
访问此地址的请求目前是支持 GET 方法的，比如可以 GET 请求 '&lt;a href="http://websafe-csrf-victim.dev/csrf/admin/admin_user/unsafe_create?admin%5Blogin%5D=admin002&amp;amp;admin%5Bpassword%5D=111111" rel="nofollow" target="_blank"&gt;http://websafe-csrf-victim.dev/csrf/admin/admin_user/unsafe_create?admin%5Blogin%5D=admin002&amp;amp;admin%5Bpassword%5D=111111&lt;/a&gt;\'
来创建一个帐号为 'admin002', 密码为 '111111' 的管理员，虽然创建管理员之前会做用户认证，但是攻击者通过在站点 A(在此试验中可以通过 'websafe-csrf-attacker.dev' 访问站点 A, 在后面的描述中，站点 A 即代表
'websafe-csrf-attacker.dev') 上创建一个叫 hook 的页面来进行 &lt;strong&gt;CSRF&lt;/strong&gt; 从而绕过站点 V 的验证来创建管理员。&lt;/p&gt;

&lt;p&gt;hook 页面的 url: '&lt;a href="http://websafe-csrf-attacker.dev/csrf/attacker/hook" rel="nofollow" target="_blank"&gt;http://websafe-csrf-attacker.dev/csrf/attacker/hook&lt;/a&gt;'&lt;/p&gt;

&lt;p&gt;hook 页面的内容：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;攻击者在这个页面发起 CSRF 攻击&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;下面这个 image 标签会创建一个叫 "admin002", 密码为 "111111" 的管理员&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="s2"&gt;"http://websafe-csrf-victim.dev/csrf/admin/admin_user/unsafe_create?admin%5Blogin%5D=admin002&amp;amp;admin%5Bpassword%5D=111111"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;站点 V 有一个初始的管理员，帐号是 'admin', 密码是 'verycomplex', 可以执行 &lt;code&gt;bundle exec rake seed:admin_user&lt;/code&gt; 创建这个初始管理员。当 'admin' 成功登录站点 V 后，如果 'admin' 使用同一浏览器访问
'&lt;a href="http://websafe-csrf-attacker.dev/csrf/attacker/hook" rel="nofollow" target="_blank"&gt;http://websafe-csrf-attacker.dev/csrf/attacker/hook&lt;/a&gt;' 后，站点 V 会在 'admin' 没有察觉的情况下增加一个新的管理员，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/3f585fcd6f037467d7705620951575bd.png!large" title="" alt="csrf attacker hook"&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;AdminUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 2&lt;/span&gt;
&lt;span class="no"&gt;AdminUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; #&amp;lt;AdminUser id: 7, login: "admin002", password: "111111", created_at: "2014-10-19 15:38:18", updated_at: "2014-10-19 15:38:18"&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果一个请求会改变资源的状态，比如下单请求，创建用户请求等，那么建议使用 POST 方法去完成这个请求，这样就不容易像上面的用例 1 一样被轻易的 &lt;strong&gt;CSRF&lt;/strong&gt;, 所以我们在用例 2 中，将创建管理员的请求改为 POST 请求。&lt;/p&gt;
&lt;h4 id="用例2"&gt;用例 2&lt;/h4&gt;
&lt;p&gt;在此用例中，攻击者把 hook 页面的内容变成了，&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;攻击者在这个页面发起 CSRF 攻击&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;下面这个 image 标签会创建一个叫 "admin002", 密码为 "111111" 的管理员&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="s2"&gt;"http://websafe-csrf-victim.dev/csrf/admin/admin_user/create?admin%5Blogin%5D=admin002&amp;amp;admin%5Bpassword%5D=111111"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们注意到 unsafe_create 方法被改为了 create 方法。
当然在真正的攻击中，攻击者不会告诉受害者这是一个 &lt;strong&gt;CSRF&lt;/strong&gt; 页面，这里为了更清楚的演示试验过程，就加上了一些说明。我们看到攻击者使用一个新的链接 '&lt;a href="http://websafe-csrf-victim.dev/csrf/admin/admin_user/create" rel="nofollow" target="_blank"&gt;http://websafe-csrf-victim.dev/csrf/admin/admin_user/create&lt;/a&gt;', 此链接的也是创建一个管理员，但是要求 POST 方法，其对应的路由和控制器内容如下，&lt;/p&gt;

&lt;p&gt;路由内容，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:csrf&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s1"&gt;'admin_user/create'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'admin_user#create'&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Csrf::AdminUserController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="n"&gt;skip_before_filter&lt;/span&gt; &lt;span class="ss"&gt;:verify_authenticity_token&lt;/span&gt;

  &lt;span class="n"&gt;before_filter&lt;/span&gt; &lt;span class="ss"&gt;:auth_admin&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="n"&gt;admin_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;AdminUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt; &lt;span class="n"&gt;admin_user_params&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;admin_user&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&gt;'ok'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
    &lt;span class="k"&gt;end&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;admin_user_params&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;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auth_admin&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&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;def&lt;/span&gt; &lt;span class="nf"&gt;current_admin&lt;/span&gt;
    &lt;span class="vi"&gt;@current_admin&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;AdminUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &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;:auid&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="n"&gt;helper_method&lt;/span&gt; &lt;span class="ss"&gt;:current_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;skip_before_filter :verify_authenticity_token&lt;/code&gt;, 这段代码可以让我们的 create 方法不会去验证浏览器请求应用时发送过来的一个防止 &lt;strong&gt;CSRF&lt;/strong&gt; 的 token, 这样我们的试验才可以进行下去。按照用例 1 的步骤，V 的管理员 'admin' 登录站点 V 后，使用同一浏览器访问攻击者设置的 'hook' 页面并不会增加一个新的管理员，这说明在应用设计中，对会改变资源状态的请求设置其请求方法为 POST 方法，能够抵挡一些比较初级简陋的 &lt;strong&gt;CSRF&lt;/strong&gt; 攻击，如果攻击者动点心思在 'hook' 页面上增加一些恶意脚本，那么站点 V 的防线又被攻破了。&lt;/p&gt;

&lt;p&gt;增加了恶意代码的 hook 页面的内容，&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;攻击者在这个页面发起 CSRF 攻击&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;下面这个 image 标签会创建一个叫 "admin002", 密码为 "111111" 的管理员&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="s2"&gt;"http://websafe-csrf-victim.dev/csrf/admin/admin_user/create?admin%5Blogin%5D=admin002&amp;amp;admin%5Bpassword%5D=111111"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/images/harmless.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"400"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"400"&lt;/span&gt; &lt;span class="na"&gt;onmouseover=&lt;/span&gt;&lt;span class="s"&gt;"
 var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://websafe-csrf-victim.dev/csrf/admin/admin_user/create?admin%5Blogin%5D=admin002&amp;amp;admin%5Bpassword%5D=111111';
  f.submit();
  return false;
     "&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们注意到攻击者在页面上增加了一张本身无害的图片，但是他给这张图片增加了一个 'onmouseover' 事件，通过此事件，当受害者把鼠标移动到图片上时，脚本会创建一个 form 表单然后在受害者毫无察觉的情况下 POST 请求 '&lt;a href="http://websafe-csrf-victim.dev/csrf/admin/admin_user/create" rel="nofollow" target="_blank"&gt;http://websafe-csrf-victim.dev/csrf/admin/admin_user/create&lt;/a&gt;' 地址，最后创建一个管理员。&lt;/p&gt;

&lt;p&gt;hook 页面在浏览器上的显示内容：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/6e1eaf9f1f9027e8abb87ce57b214d96.png!large" title="" alt="csrf image post"&gt;&lt;/p&gt;
&lt;h4 id="用例3"&gt;用例 3&lt;/h4&gt;
&lt;p&gt;我们注意到在用例 2 中，控制器 'Csrf::AdminUserController' 里有一段代码：&lt;code&gt;skip_before_filter :verify_authenticity_token&lt;/code&gt;, 现在我们把这段代码注释掉，然后重复用例 2 的步骤，这时候我们发现站点 V 抛出一个 ActionController::InvalidAuthenticityToken 的异常，这个异常是什么意思呢？我们首先查看下 V 的布局文件 application.html.erb, 其内容为：&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Websafe&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;stylesheet_link_tag&lt;/span&gt;    &lt;span class="s1"&gt;'application'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;media: &lt;/span&gt;&lt;span class="s1"&gt;'all'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'data-turbolinks-track'&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="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;csrf_meta_tags&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们注意到 &lt;code&gt;csrf_meta_tags&lt;/code&gt; 这段代码，它会帮我们在页面上嵌入一个存储 csrf-token 的 meta 标签，
比如：'',
如果我们使用 &lt;code&gt;form_tag&lt;/code&gt;,  &lt;code&gt;form_for&lt;/code&gt; 之类的表单辅助方法，rails 会自动帮我们生成一个包含 csrf-token 的隐藏 input 标签比如，&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display:none"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"authenticity_token"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"OI3TMyEUCXA00tKNqjovRVf+6iHX49jdlne4S76H1qU="&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的 csrf-token meta 标签用于 jquery-ujs, 这样做 ajax post 请求时，jquery-ujs 会自动在请求头里加入 csrf-token meta 标签的内容，比如，&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"authenticity_token"=&amp;gt;"OI3TMyEUCXA00tKNqjovRVf+6iHX49jdlne4S76H1qU="&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;而 form 表单里的 authenticity_token input 标签会在表单提交时将 authenticity_token 的值发送到服务器以供验证，这样只要是站内的请求就不用担心
不能通过 csrf-token 认证了。&lt;/p&gt;

&lt;p&gt;这个 token 是随机生成的，并且此 token 生成后会被保存到用户的会话中，即 session[:csrf_token], 而攻击者几乎是没有可能通过访问同一个页面来获得这个
token 的，因为 rails 会根据攻击者的会话生成一个不同的 token, 所以我们可以认为攻击者不知道这个 token, 在这种情况下，攻击者即使仿照了一个请求，
也没有办法通过 rails 的 csrf-token 认证了，这样就预防了 &lt;strong&gt;CSRF&lt;/strong&gt; 攻击了。&lt;/p&gt;

&lt;p&gt;我们总结下在 rails 中怎么预防 &lt;strong&gt;CSRF&lt;/strong&gt; 攻击，&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;不使用 GET 方法去请求一些有副作用的操作，比如创建资源，删除资源，更新修改资源等；&lt;/li&gt;
&lt;li&gt;rails 默认会对使用 POST, PUT, DELETE 方法的请求启用 csrf-token 认证，所以我们在遵守第 1 条规矩的时候，使用 rails 的此默认设置就足够抵御大量的 &lt;strong&gt;CSRF&lt;/strong&gt; 攻击了;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="Session Fixation"&gt;Session Fixation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Session Fixation&lt;/strong&gt; 的攻击过程大致如下，&lt;/p&gt;

&lt;p&gt;假设攻击者对站点 A 进行攻击&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;攻击者通过加载站点 A 的登录页面创建了一个合法的 session id, 然后从 A 的响应的 cookie 里取出此 session id 用于后面的操作;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;攻击者通过一定的方法保持这个 session id 不过期，比如不断地访问站点 A;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;同时攻击者可能通过某种方式让受害者的浏览器去使用这个 session id, 比如攻击这在站点 A 的某个页面注入了恶意代码，
&lt;code&gt;&amp;lt;script&amp;gt;document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";&amp;lt;/script&amp;gt;&lt;/code&gt;
很显然这里用到了 &lt;strong&gt;XSS&lt;/strong&gt; 攻击，当受害者访问到这个页面时，她浏览器的 session id 会被强制地使用被攻击者掌握的 session id 了；&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;受害者登录站点 A, 这时候攻击者和受害者就共享同一个 session id 了，这就意味着攻击者可以使用和受害者一样的身份使用站点 A 了，并且受害者对攻击没有丝毫察觉;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在 rails 中预防 &lt;strong&gt;Session Fixation&lt;/strong&gt; 攻击要注意以下几点，&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;1.做好预防 &lt;strong&gt;XSS&lt;/strong&gt; 攻击的工作，因为 &lt;strong&gt;Session Fixation&lt;/strong&gt; 攻击的第三步就用到了 &lt;strong&gt;XSS&lt;/strong&gt;, 而怎么预防 &lt;strong&gt;XSS&lt;/strong&gt; 攻击我们在前面已经说了很多了;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2.使用 &lt;code&gt;reset_session&lt;/code&gt; 方法， &lt;code&gt;reset_session&lt;/code&gt; 方法会创建一个新的 session，这样攻击者持有的那个 session id 就失效了，所以每次用户登录成功后，我们先执行 &lt;code&gt;reset_session&lt;/code&gt;, 然后再填充新的用户 session, 如下所示：&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Csrf::SessionController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authenticate_admin&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@admin&lt;/span&gt;
      &lt;span class="n"&gt;reset_session&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;:auid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@admin.id&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&gt;'登录成功'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
    &lt;span class="k"&gt;end&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;authenticate_admin&lt;/span&gt;
    &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;AdminUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;login: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:login&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&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="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;nil&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;ul&gt;
&lt;li&gt;3.根据用户浏览器器的信息来判断是否有 &lt;strong&gt;Session Fixation&lt;/strong&gt; 攻击，比如用户访问的 ip 发生了变化;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;根据我的理解做好第 1，第 2 点就能很好的预防 &lt;strong&gt;Session Fixation&lt;/strong&gt; 攻击了，但是第 3 点也是非常有意义的，
比如，如果频繁的发现用户通过不同的 ip 或者浏览器登录，这预示着可能有 &lt;strong&gt;Session Fixation&lt;/strong&gt; 攻击了。&lt;/p&gt;
&lt;h2 id="Brute Force Attack"&gt;Brute Force Attack&lt;/h2&gt;
&lt;p&gt;这是一种使用自动程序暴力破解的攻击手段，假设某个站点 A 有一个登录页面，当用户填写了错误的登录名后，页面上会显示 '用户名不存在的'，那么攻击者
就很可能使用这个登录页面获得一批有效的用户名，然后配合密码字典等破解用户的帐号。我们预防 &lt;strong&gt;Brute Force&lt;/strong&gt; 攻击可以从两个方面着手，&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;阻止自动程序的暴力请求，比如我们可以在登录页面或者其他需要识别自动程序的页面上增加验证码;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;模糊出错信息，比如如果用户登录失败，无论是因为用户名错误还是密码错误，我们都可以显示为'用户名或者密码错误';&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;&lt;h3 id="试验用到的代码"&gt;试验用到的代码&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/baya/websafe" rel="nofollow" target="_blank" title=""&gt;websafe&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="XSS"&gt;XSS&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet" rel="nofollow" target="_blank" title=""&gt;XSS Cheat Sheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://guides.rubyonrails.org/security.html" rel="nofollow" target="_blank" title=""&gt;Security On Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet" rel="nofollow" target="_blank" title=""&gt;XSS Cross Site Scripting Prevention Cheat Sheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.howtocreate.co.uk/crosssite.html" rel="nofollow" target="_blank" title=""&gt;http://www.howtocreate.co.uk/crosssite.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="HTTPOnly"&gt;HTTPOnly&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.owasp.org/index.php/HTTPOnly" rel="nofollow" target="_blank" title=""&gt;https://www.owasp.org/index.php/HTTPOnly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Content Security Policy"&gt;Content Security Policy&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.owasp.org/index.php/Content_Security_Policy" rel="nofollow" target="_blank" title=""&gt;https://www.owasp.org/index.php/Content_Security_Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.html5rocks.com/en/tutorials/security/content-security-policy/" rel="nofollow" target="_blank" title=""&gt;http://www.html5rocks.com/en/tutorials/security/content-security-policy/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Content_Security_Policy" rel="nofollow" target="_blank" title=""&gt;http://en.wikipedia.org/wiki/Content_Security_Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.twitter.com/2013/csp-to-the-rescue-leveraging-the-browser-for-security" rel="nofollow" target="_blank" title=""&gt;https://blog.twitter.com/2013/csp-to-the-rescue-leveraging-the-browser-for-security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/twitter/secureheaders" rel="nofollow" target="_blank" title=""&gt;https://github.com/twitter/secureheaders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/blog/1477-content-security-policy" rel="nofollow" target="_blank" title=""&gt;https://github.com/blog/1477-content-security-policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.w3.org/TR/CSP/" rel="nofollow" target="_blank" title=""&gt;http://www.w3.org/TR/CSP/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://content-security-policy.com/" rel="nofollow" target="_blank" title=""&gt;http://content-security-policy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://benvinegar.github.io/csp-talk-2013/#1" rel="nofollow" target="_blank" title=""&gt;http://benvinegar.github.io/csp-talk-2013/#1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://w3c.github.io/webappsec/specs/content-security-policy/" rel="nofollow" target="_blank" title=""&gt;https://w3c.github.io/webappsec/specs/content-security-policy/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Tue, 20 Sep 2016 20:34:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/31114</link>
      <guid>https://ruby-china.org/topics/31114</guid>
    </item>
    <item>
      <title>大规模 I18n 实践</title>
      <description>&lt;p&gt;这篇文章主要涉及到以下一些内容：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;有时候我们用点心，会发现客户的建议是很明智的。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;将用户的输入标准化。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用 excel 文件作为用户操作翻译对象的 UI。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用 redis 作为翻译数据的存储后端。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;特例：处理邮件的翻译。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这篇文章的标题看起来有点唬人，这个大规模并不是说为了做这个 I18n 动用了很多机器，也不是说处理了大规模的并发请求，而是指 I18n 涉及到的国家和地区很多，
翻译的内容也很多，这两多已经多到不能用 Rails 默认的方法来处理 I18n 了，既然不能使用 Rails 默认的方法来处理了，那么我们就需要自己动脑筋设计新的方法和程序来处理
这个问题，在这篇文章中将介绍我们是如何在实际开发中处理这个大规模的 I18n 问题。&lt;/p&gt;

&lt;p&gt;和我以往的文章一样这篇帖子提供了一个一步步构造出的可以运行的 demo,&lt;/p&gt;

&lt;p&gt;本文 demo 代码在 &lt;a href="https://github.com/baya/my-i18n-demo" rel="nofollow" target="_blank" title=""&gt;https://github.com/baya/my-i18n-demo&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="问题概述与挑战"&gt;问题概述与挑战&lt;/h2&gt;
&lt;p&gt;我们的项目是一个跨国电子商务网站，下面的图表示的是我们支持的国家和语言，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/396c9ce59664cffca897029084c91c46.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Rails 项目中默认以 yml 文件存储翻译内容，比如说 config/locales/en.yml, &lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Files in the config/locales directory are used for internationalization&lt;/span&gt;
&lt;span class="c1"&gt;# and are automatically loaded by Rails. If you want to use locales other&lt;/span&gt;
&lt;span class="c1"&gt;# than English, add the necessary files in this directory.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# To use the locales, use `I18n.t`:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#     I18n.t 'hello'&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# In views, this is aliased to just `t`:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#     &amp;lt;%= t('hello') %&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# To use a different locale, set it with `I18n.locale`:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#     I18n.locale = :es&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# This would use the information in config/locales/es.yml.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# To learn more, please read the Rails Internationalization guide&lt;/span&gt;
&lt;span class="c1"&gt;# available at http://guides.rubyonrails.org/i18n.html.&lt;/span&gt;

&lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;world"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种存储方式有五大不足：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;语言很多时需要添加很多翻译文件。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;翻译内容很多时，文件会很大，存储效率不高，访问速度低下。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;相同的语言在不同的国家其实是有差别的，比如英式英语和美式英语其实是有很多差别的，而这种存储方式不太好处理这种情况。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;不能线上更新翻译内容，如果需要增加或者修改翻译需要更新 yml 文件，然后重新部署应用。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;yml 文件对翻译人员不友好，最终将翻译集成到项目的大部分工作都落在了我们开发人员身上。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;针对上述不足，我们对症下药，实施下面的解决方法：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;将翻译的存储由 yml 文件改为 redis。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用 lang-country 取代 lang 作为 locale, 比如 en-US, en-UK 等。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用 excel 文件作为用户提交翻译的 UI, 我们在后台写程序解析 excel 文件，然后自动将翻译内容导入到 redis 中。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在开始实践之前，我们首先建立自己的 demo 项目 my-i18n-demo:&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails new my-i18n-demo
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="1. 使用 redis 作为翻译的存储后端"&gt;1. 使用 redis 作为翻译的存储后端&lt;/h2&gt;&lt;h3 id="1.1 建立 redis.yml 文件"&gt;1.1 建立 redis.yml 文件&lt;/h3&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/redis.yml&lt;/span&gt;

&lt;span class="na"&gt;i18n&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hiredis&lt;/span&gt;
  &lt;span class="na"&gt;thread_safe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="1.2 建立 locales.yml 文件"&gt;1.2 建立 locales.yml 文件&lt;/h3&gt;
&lt;p&gt;为了简单起见，我们在 demo 中只演示简体中文 (zh-s), 繁体中文 (zh-t), 西班牙语 (es), 法语 (fr), 日语 (jp), 意大利语 (it), 俄语 (ru), 德语 (de), 英语 (en) 等几种语言。&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/locales.yml&lt;/span&gt;

&lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;zh-s&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CN&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CA&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;US&lt;/span&gt;
  &lt;span class="na"&gt;zh-t&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;HK&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TW&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CA&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;US&lt;/span&gt;
  &lt;span class="na"&gt;es&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ES&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MX&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;US&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CO&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CR&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DO&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;EC&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PE&lt;/span&gt;
  &lt;span class="na"&gt;de&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DE&lt;/span&gt;
  &lt;span class="na"&gt;fr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FR&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CA&lt;/span&gt;
  &lt;span class="na"&gt;it&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IT&lt;/span&gt;
  &lt;span class="na"&gt;jp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;JP&lt;/span&gt;
  &lt;span class="na"&gt;ru&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RU&lt;/span&gt;
  &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;US&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GB&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CA&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;locales.yml 文件的结构是：语言下面是一组使用该语言的国家和地区。&lt;/p&gt;
&lt;h3 id="1.3 配置 redis 为翻译的存储后端"&gt;1.3 配置 redis 为翻译的存储后端&lt;/h3&gt;
&lt;p&gt;首先我们需要在 Gemfile 里引入 3 个 gem:&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"hiredis"&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'cached_key_value_store'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;gem hiredis 是对用 c 写的 hiredis 做的一个 ruby 封装，c 写的 hiredis 是 redis 的一个 c 客户端库，gem hiredis
最主要的作用就是提高对大批量的 redis 回复的解析速度。&lt;/p&gt;

&lt;p&gt;cached_key_value_store 这个 gem 可以缓存一些比较慢的翻译请求，提高系统的响应速度。&lt;/p&gt;

&lt;p&gt;最后不要忘记 &lt;code&gt;bundle install&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;然后创建 redis.rb 文件，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/redis.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'redis/connection/hiredis'&lt;/span&gt;

&lt;span class="no"&gt;I18N_LOCALES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'config'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'locales.yml'&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="s1"&gt;'locales'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;I18n&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Backend&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KeyValue&lt;/span&gt;
     &lt;span class="c1"&gt;# 设定了合法的 locales&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;available_locales&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="no"&gt;I18N_LOCALES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;lang&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;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;a&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="vg"&gt;$i18n_redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Redis&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;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/redis.yml"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s1"&gt;'i18n'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Backend&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CachedKeyValueStore&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="vg"&gt;$i18n_redis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样配置 redis 的工作就全部完成了，接下来我们调试下以 redis 为存储后端的 I18n 是否正常工作。&lt;/p&gt;
&lt;h3 id="1.4 调试 I18n 是否正常工作"&gt;1.4 调试 I18n 是否正常工作&lt;/h3&gt;
&lt;p&gt;首先我们从网上找一份常用的国际化数据，比如 &lt;a href="https://github.com/svenfuchs/rails-i18n" rel="nofollow" target="_blank" title=""&gt;https://github.com/svenfuchs/rails-i18n&lt;/a&gt;,
我们使用 &lt;a href="https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/zh-CN.yml" rel="nofollow" target="_blank" title=""&gt;zh-CN.yml&lt;/a&gt; 作为调试数据。我们把 zh-CN.yml 改名为 zh-s-CN.yml, 并将 zh-s-CN.yml 里的 zh-CN 改为 zh-s-CN,&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/locales/zh-s-CN.yml&lt;/span&gt;
&lt;span class="s"&gt;+ zh-s-CN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调试的步骤如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;将 zh-s-CN.yml 导入到 redis。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;进入 rails console, 然后查看 zh-s-CN 的翻译是否正确。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="1.4.1 将 zh-s-CN.yml 导入到 redis"&gt;1.4.1 将 zh-s-CN.yml 导入到 redis&lt;/h4&gt;
&lt;p&gt;我们先将 zh-CN.yml 文件下载到 config/locales&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;wget https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/zh-CN.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着我们启动 redis，启动 redis 之前，我们需要用到一个 redis 的配置文件 redis.conf, 可以从 &lt;a href="https://raw.githubusercontent.com/baya/my-conf/master/redis.conf" rel="nofollow" target="_blank"&gt;https://raw.githubusercontent.com/baya/my-conf/master/redis.conf&lt;/a&gt; 下载，我们将 redis.conf 放到 /usr/local/etc/ 目录下，根据 redis.conf 里面 &lt;code&gt;dir /usr/local/var/db/redis/&lt;/code&gt; 的配置，如果没有 /usr/local/var/db/redis/ 这个目录，我们需要创建 /usr/local/var/db/redis/ 这个
目录，&lt;code&gt;$ mkdir -p /usr/local/var/db/redis/&lt;/code&gt;, 现在可以启动 redis 了，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;redis-server /usr/local/etc/redis.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们写一个 rake task 用于将 zh-s-CN.yml 导入到 redis，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/tasks/load_translations.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:data&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:load_translations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:locale&lt;/span&gt;&lt;span class="p"&gt;]&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;:environment&lt;/span&gt;&lt;span class="p"&gt;]&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;t&lt;/span&gt;&lt;span class="p"&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;locale&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="s1"&gt;'locale'&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'yml'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&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="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'config/locales'&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="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store_translations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:escape&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="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行 data:load_translations task 导入翻译数据，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rake data:load_translations[zh-s-CN]
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="1.4.2 进入 rails console 调试"&gt;1.4.2 进入 rails console 调试&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'zh-s-CN'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'date.abbr_day_names'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"周日"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"周一"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"周二"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"周三"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"周四"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"周五"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"周六"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'date.day_names'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"星期日"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"星期一"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"星期二"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"星期三"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"星期四"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"星期五"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"星期六"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'datetime.distance_in_words.about_x_hours'&lt;/span&gt;&lt;span class="p"&gt;)&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;:one&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"大约一小时"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:other&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"大约 %{count} 小时"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'datetime.distance_in_words.about_x_hours.other'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"大约 1 小时"&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'errors.format'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"%{attribute}%{message}"&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'errors.messages.greater_than'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"必须大于 0"&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cool, 工作正常。&lt;/p&gt;
&lt;h2 id="2. 用户线上提交翻译"&gt;2. 用户线上提交翻译&lt;/h2&gt;
&lt;p&gt;我们开发过一个功能很完备的 web 界面让用户去提交和管理翻译数据，但是用户经常抱怨不会使用这个界面，后来我们发现用户是使用 excel 文件来编辑
和保存翻译好的数据，于是我们想何不直接提供一个上传文件的界面，让用户直接上传 excel 文件，然后我们在后台将文件内容导入到 redis 里不就行了？
后来我们开发了这样一个上传 excel 文件到界面，果然很好用，用户不再抱怨了。现在我们在 my-i18n-demo 中实现这个功能。&lt;/p&gt;
&lt;h3 id="2.1 标准化用户输入"&gt;2.1 标准化用户输入&lt;/h3&gt;
&lt;p&gt;在和用户讨论后，我们决定每条翻译记录的第 1 列为 locale, 第 2 列为翻译 key, 第 3 列为翻译 key 的英文内容，第 4 列为翻译内容。样本文件如下图所示，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/035f4d9b281b8ad168e12a972f27266b.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="2.2 写代码"&gt;2.2 写代码&lt;/h3&gt;
&lt;p&gt;设置路由：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&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;namespace&lt;/span&gt; &lt;span class="ss"&gt;:admin&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;resources&lt;/span&gt; &lt;span class="ss"&gt;:translation_files&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;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成控制器：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g controller admin/translation_files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们建立 TranslationFile 模型来处理上传的翻译文件，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/translation_file.rb&lt;/span&gt;

&lt;span class="c1"&gt;# encoding: utf-8&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TranslationFile&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:errors&lt;/span&gt;

  &lt;span class="no"&gt;Spreadsheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client_encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'UTF-8'&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;load_to_backend&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="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&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="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_backend&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&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;initialize&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="vi"&gt;@file&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;
    &lt;span class="vi"&gt;@errors&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;def&lt;/span&gt; &lt;span class="nf"&gt;to_backend&lt;/span&gt;
    &lt;span class="n"&gt;dict&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;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store_translations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;escape: &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="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="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backtrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&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;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="vi"&gt;@errors&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&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;book&lt;/span&gt;
    &lt;span class="vi"&gt;@book&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Spreadsheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@file&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;sheet&lt;/span&gt;
    &lt;span class="vi"&gt;@sheet&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;worksheet&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="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@dict.nil&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="vi"&gt;@dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;row&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;locale&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="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;
        &lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\s/&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="n"&gt;key&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="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\s/&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="vi"&gt;@dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;locale&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="vi"&gt;@dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;locale&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="vi"&gt;@dict&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;因为我们需要用到 spreadsheet 这个 gem 来处理 xls 文件，所以我们需要在 Gemfile 增加 spreadsheet,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'spreadsheet'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TranslationFilesController 的代码，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/admin/translation_files_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Admin::TranslationFilesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@locales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;available_locales&lt;/span&gt;
    &lt;span class="vi"&gt;@key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'hello_world'&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;new&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;create&lt;/span&gt;
    &lt;span class="n"&gt;file&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="ss"&gt;:file&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;tempfile&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TranslationFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_to_backend&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s1"&gt;'index'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;";"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s1"&gt;'new'&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;当我们成功提交 hello_world.xls 文件后，我们可以看到 Hello World 的各个语言的翻译，&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/2cc4d6e7a7d998f49e79af15ea5f686a.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="特例: 邮件的翻译"&gt;特例：邮件的翻译&lt;/h2&gt;
&lt;p&gt;在邮件的翻译过程中，我们没有使用 I18n, 而是根据不同的 locale, 建立对应的邮件模版，比如 zh-s-CN 的欢迎邮件，我们就建立一个
叫 &lt;code&gt;_zh_s_cn_welcome_mail.html.erb&lt;/code&gt; 的邮件模版，这样做是因为邮件的翻译内容一般是大段的，并且这大段的内容又会因为不同的 locale 而产生不
一些细微的差异，最重要的原因是邮件是发给客户的，如果邮件内容出现错误我们是没有办法把已经发送的邮件撤销重新发送，所以我们需要尽最大
的努力确保邮件内容不会因为在线上修改翻译内容时而出现错误。&lt;/p&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Tue, 05 Jan 2016 16:15:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/28616</link>
      <guid>https://ruby-china.org/topics/28616</guid>
    </item>
    <item>
      <title>使用 Rails 构建 API 实践</title>
      <description>&lt;p&gt;我是来鼓吹使用 Rails 写 API 的。&lt;/p&gt;

&lt;p&gt;原文在此：&lt;a href="https://labs.kollegorna.se/blog/2015/04/build-an-api-now/" rel="nofollow" target="_blank"&gt;https://labs.kollegorna.se/blog/2015/04/build-an-api-now/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文有一个很大的缺陷就是读者无法按照它的步骤一步一步的去实现一个可以运行的 demo, 这对经验丰富的开发
者可能不算是一个问题，但是对刚刚接触这方面知识的新手来说却是一个很大的遗憾，软件开发方面的知识重在
实践，只有动手做了，才能对知识掌握地更加牢靠，才能更好地在工作中去使用这些知识，所以我对原文的内容
做了一些补充修整，以期能够让读者边读边思考边实践。&lt;/p&gt;

&lt;p&gt;原文的 demo 是一个类微博应用，为简单起见我们只使用 User 和 Micropost 模型，并且我们不使用 ActiveModel::Serializer, 而是使用 Jbuilder 作为 json 模版。&lt;/p&gt;

&lt;p&gt;首先建立一个项目：build-an-api-rails-demo&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails new build-an-api-rails-demo
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="加入第一个 API resource"&gt;加入第一个 API resource&lt;/h2&gt;&lt;h3 id="BaseController"&gt;BaseController&lt;/h3&gt;
&lt;p&gt;生成控制器：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 我们不需要生成资源文件&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g controller api/v1/base &lt;span class="nt"&gt;--no-assets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/api/v1/base_controller.rb,&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;Api::V1::BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="c1"&gt;# disable the CSRF token&lt;/span&gt;
  &lt;span class="n"&gt;protect_from_forgery&lt;/span&gt; &lt;span class="ss"&gt;with: :null_session&lt;/span&gt;

  &lt;span class="c1"&gt;# disable cookies (no set-cookies header in response)&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:destroy_session&lt;/span&gt;

  &lt;span class="c1"&gt;# disable the CSRF token&lt;/span&gt;
  &lt;span class="n"&gt;skip_before_action&lt;/span&gt; &lt;span class="ss"&gt;:verify_authenticity_token&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="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session_options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:skip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&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;在 BaseController 里我们禁止了 CSRF token 和 cookies&lt;/p&gt;
&lt;h3 id="配置路由:"&gt;配置路由：&lt;/h3&gt;
&lt;p&gt;config/routes.rb,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:api&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:v1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:destroy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# 原文有 microposts, 我们现在把它注释掉&lt;/span&gt;
    &lt;span class="c1"&gt;# resources :microposts, only: [:index, :create, :show, :update, :destroy]&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="Api::V1::UsersController"&gt;Api::V1::UsersController&lt;/h3&gt;
&lt;p&gt;生成控制器：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 我们不需要生成资源文件&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g controller api/v1/users &lt;span class="nt"&gt;--no-assets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/api/v1/users_controller.rb,&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;Api::V1::UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;V1&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# 原文使用 Api::V1::UserSerializer&lt;/span&gt;
    &lt;span class="c1"&gt;# 我们现在使用 app/views/api/v1/users/show.json.jbuilder&lt;/span&gt;
    &lt;span class="c1"&gt;# render(json: Api::V1::UserSerializer.new(user).to_json)&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;app/views/api/v1/users/show.json.jbuilder,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;:activated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:updated_at&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;h3 id="User 模型和 users 表"&gt;User 模型和 users 表&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g model User
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/models/user.rb,&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;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;db/migrate/20150502072954_create_users.rb,&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;CreateUsers&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:users&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;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt; &lt;span class="ss"&gt;:activated&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&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;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rake db:migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;种子数据：&lt;/p&gt;

&lt;p&gt;db/seeds.rb,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                     &lt;span class="p"&gt;{&lt;/span&gt;
                       &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s1"&gt;'test-user-00@mail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'test-user-00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="ss"&gt;activated: &lt;/span&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="ss"&gt;admin: &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="ss"&gt;email: &lt;/span&gt;&lt;span class="s1"&gt;'test-user-01@mail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'test-user-01'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="ss"&gt;activated: &lt;/span&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="ss"&gt;admin: &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;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建种子数据：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exe rake db:seed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们可以测试一下 api 是否正常工作，我们可以先查看下相关 api 的路由，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rake routes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;      &lt;span class="no"&gt;Prefix&lt;/span&gt; &lt;span class="no"&gt;Verb&lt;/span&gt;   &lt;span class="no"&gt;URI&lt;/span&gt; &lt;span class="no"&gt;Pattern&lt;/span&gt;                      &lt;span class="no"&gt;Controller&lt;/span&gt;&lt;span class="c1"&gt;#Action&lt;/span&gt;
&lt;span class="n"&gt;api_v1_users&lt;/span&gt; &lt;span class="no"&gt;GET&lt;/span&gt;    &lt;span class="sr"&gt;/api/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#index&lt;/span&gt;
             &lt;span class="no"&gt;POST&lt;/span&gt;   &lt;span class="sr"&gt;/api/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#create&lt;/span&gt;
 &lt;span class="n"&gt;api_v1_user&lt;/span&gt; &lt;span class="no"&gt;GET&lt;/span&gt;    &lt;span class="sr"&gt;/api/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#show&lt;/span&gt;
             &lt;span class="no"&gt;PATCH&lt;/span&gt;  &lt;span class="sr"&gt;/api/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#update&lt;/span&gt;
             &lt;span class="no"&gt;PUT&lt;/span&gt;    &lt;span class="sr"&gt;/api/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#update&lt;/span&gt;
             &lt;span class="no"&gt;DELETE&lt;/span&gt; &lt;span class="sr"&gt;/api/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="c1"&gt;#destroy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动 rails 服务，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 curl 请求 api,&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://localhost:3000/api/v1/users/1.json
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"email"&lt;/span&gt;:&lt;span class="s2"&gt;"test-user-00@mail.com"&lt;/span&gt;,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"test-user-00"&lt;/span&gt;,&lt;span class="s2"&gt;"activated"&lt;/span&gt;:&lt;span class="s2"&gt;"2015-05-02T07:47:14.697Z"&lt;/span&gt;,&lt;span class="s2"&gt;"admin"&lt;/span&gt;:false,&lt;span class="s2"&gt;"created_at"&lt;/span&gt;:&lt;span class="s2"&gt;"2015-05-02T07:47:14.708Z"&lt;/span&gt;,&lt;span class="s2"&gt;"updated_at"&lt;/span&gt;:&lt;span class="s2"&gt;"2015-05-02T07:47:14.708Z"&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恭喜，我们的 api 工作正常！&lt;/p&gt;
&lt;h2 id="增加认证(Authentication)"&gt;增加认证 (Authentication)&lt;/h2&gt;
&lt;p&gt;认证的过程是这样的：用户把她的用户名和密码通过 HTTP POST 请求发送到我们的 API (在这里我们使用 sessions 端点来处理这个请求), 如果用户名和密码匹配，我们
会把 token 发送给用户。这个 token 就是用来证明用户身份的凭证。然后在以后的每个请求中，我们都通过这个 token 来查找用户，如果没有找到用户则返回 401 错误。&lt;/p&gt;
&lt;h3 id="给 User 模型增加 authentication_token 属性"&gt;给 User 模型增加 authentication_token 属性&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g migration add_authentication_token_to_users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;db/migrate/20150502123451_add_authentication_token_to_users.rb&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;AddAuthenticationTokenToUsers&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:authentication_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&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 shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rake db:migrate
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="生成 authentication_token"&gt;生成 authentication_token&lt;/h3&gt;
&lt;p&gt;app/models/user.rb,&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;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;

 &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;before_create&lt;/span&gt; &lt;span class="ss"&gt;:generate_authentication_token&lt;/span&gt;

 &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_authentication_token&lt;/span&gt;
 &lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&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;authentication_token&lt;/span&gt; &lt;span class="o"&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;base64&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="o"&gt;+&lt;/span&gt;     &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;authentication_token: &lt;/span&gt;&lt;span class="n"&gt;authentication_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="k"&gt;end&lt;/span&gt;
 &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

 &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reset_auth_token!&lt;/span&gt;
 &lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="n"&gt;generate_authentication_token&lt;/span&gt;
 &lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="n"&gt;save&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和原文相比，我给 User 模型增加了一个 &lt;code&gt;reset_auth_token!&lt;/code&gt;  方法，我这样做的理由主要有以下几点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;我觉得需要有一个方法帮助用户重置 authentication token, 而不仅仅是在创建用户时生成 authenticeation token；&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;如果用户的 token 被泄漏了，我们可以通过 &lt;code&gt;reset_auth_token!&lt;/code&gt; 方法方便地重置用户 token;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="sessions endpoint"&gt;sessions endpoint&lt;/h3&gt;
&lt;p&gt;生成 sessions 控制器，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 我们不需要生成资源文件&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g controller api/v1/sessions &lt;span class="nt"&gt;--no-assets&lt;/span&gt;

  create  app/controllers/api/v1/sessions_controller.rb
  invoke  erb
  create    app/views/api/v1/sessions
  invoke  test_unit
  create    &lt;span class="nb"&gt;test&lt;/span&gt;/controllers/api/v1/sessions_controller_test.rb
  invoke  helper
  create    app/helpers/api/v1/sessions_helper.rb
  invoke    test_unit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/api/v1/sessions_controller.rb,&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;Api::V1::SessionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;V1&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;create_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:email&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;@user&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@user.authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:password&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;current_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;
      &lt;span class="c1"&gt;# 我们使用 jbuilder&lt;/span&gt;
      &lt;span class="c1"&gt;# render(&lt;/span&gt;
      &lt;span class="c1"&gt;#   json: Api::V1::SessionSerializer.new(user, root: false).to_json,&lt;/span&gt;
      &lt;span class="c1"&gt;#   status: 201&lt;/span&gt;
      &lt;span class="c1"&gt;# )&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;api_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;401&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="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_params&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;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们还需要做一些原文没有提到的工作：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;给 User 模型增加和 password 相关的属性;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;给数据库中已存在的测试用户增加密码和 authentication token;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;实现和 current_user 相关的方法;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;实现 app/views/api/v1/sessions/create.json.jbuilder;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;配置和 sessions 相关的路由;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="给 User 模型增加和 password 相关的属性"&gt;给 User 模型增加和 password 相关的属性&lt;/h4&gt;
&lt;p&gt;在 Gemfile 里将 &lt;code&gt;gem 'bcrypt'&lt;/code&gt; 这一行的注释取消&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Use ActiveModel has_secure_password&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'bcrypt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 3.1.7'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/models/user.rb,&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;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;has_secure_password&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;给 User 模型增加 password_digest 属性，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g migration add_password_digest_to_users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;db/migrate/20150502134614_add_password_digest_to_users.rb,&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;AddPasswordDigestToUsers&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:password_digest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&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 shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rake db:migrate
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="给数据库中已存在的测试用户增加密码和 authentication token"&gt;给数据库中已存在的测试用户增加密码和 authentication token&lt;/h4&gt;
&lt;p&gt;这个任务可以在 rails console 下完成，&lt;/p&gt;

&lt;p&gt;首先启动 rails console,&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在 rails console 里执行，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123123'&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_auth_token!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="实现和 current_user 相关的方法"&gt;实现和 current_user 相关的方法&lt;/h4&gt;
&lt;p&gt;app/controllers/api/v1/base_controller.rb,&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;Api::V1::BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:current_user&lt;/span&gt;

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

&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="实现 app/views/api/v1/sessions/create.json.jbuilder"&gt;实现 app/views/api/v1/sessions/create.json.jbuilder&lt;/h4&gt;
&lt;p&gt;app/views/api/v1/sessions/create.json.jbuilder,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;json&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;do&lt;/span&gt;
  &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt; &lt;span class="vi"&gt;@user.authentication_token&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="配置和 sessions 相关的路由"&gt;配置和 sessions 相关的路由&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:api&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:v1&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;resources&lt;/span&gt; &lt;span class="ss"&gt;:sessions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:create&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们做一个测试看是否能够顺利地拿到用户的 token, 我们使用下面的用户作为测试用户：&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;email: &lt;/span&gt;&lt;span class="s1"&gt;'test-user-00@mail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'test-user-00'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"user[email]=test-user-00@mail.com&amp;amp;user[password]=123123"&lt;/span&gt; http://localhost:3000/api/v1/sessions.json

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"session"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"test-user-00"&lt;/span&gt;,&lt;span class="s2"&gt;"admin"&lt;/span&gt;:false,&lt;span class="s2"&gt;"token"&lt;/span&gt;:&lt;span class="s2"&gt;"izrFiion7xEe2ccTj0v0mOcuNoT3FvpPqI31WLSCEBLvuz4xSr0d9+VI2+xVvAJjECIoju5MaoytEcg6Md773w=="&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们顺利地拿到了 token。&lt;/p&gt;

&lt;p&gt;我们再做一个验证失败的测试。&lt;/p&gt;

&lt;p&gt;我们使用一个错误的密码：fakepwd&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"user[email]=test-user-00@mail.com&amp;amp;user[password]=fakepwd"&lt;/span&gt; http://localhost:3000/api/v1/sessions.json

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;糟糕系统出错了：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NoMethodError (undefined method `api_error' for #&amp;lt;Api::V1::SessionsController:0x007fead422c178&amp;gt;):
  app/controllers/api/v1/sessions_controller.rb:14:in `create'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原来我们没有实现 api_error 这个方法，那我们现在就实现 api_error 这个方法。&lt;/p&gt;

&lt;p&gt;app/controllers/api/v1/base_controller.rb,&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;Api::V1::BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

 &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;api_error&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;+&lt;/span&gt;   &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;nothing: &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;status: &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;:status&lt;/span&gt;&lt;span class="p"&gt;]&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;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续测试：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"user[email]=test-user-00@mail.com&amp;amp;user[password]=fakepwd"&lt;/span&gt; http://localhost:3000/api/v1/sessions

HTTP/1.1 401 Unauthorized 
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;block
X-Content-Type-Options: nosniff
Content-Type: text/plain&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
Cache-Control: no-cache
X-Request-Id: a5349b47-d756-4830-84f8-0653577f936d
X-Runtime: 0.319768
Server: WEBrick/1.3.1 &lt;span class="o"&gt;(&lt;/span&gt;Ruby/2.1.2/2014-05-08&lt;span class="o"&gt;)&lt;/span&gt;
Date: Sat, 02 May 2015 14:41:55 GMT
Content-Length: 0
Connection: Keep-Alive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时服务器返回了 401 Unauthorized&lt;/p&gt;
&lt;h2 id="Authenticate User"&gt;Authenticate User&lt;/h2&gt;
&lt;p&gt;在前面的测试中，我们已经成功地拿到了用户的 token, 那么现在我们把 token 和 email 发给 API&lt;/p&gt;

&lt;p&gt;看能否成功识别出用户。&lt;/p&gt;

&lt;p&gt;首先在 &lt;code&gt;Api::V1::BaseController&lt;/code&gt; 里实现 &lt;code&gt;authenticate_user!&lt;/code&gt; 方法：&lt;/p&gt;

&lt;p&gt;app/controllers/api/v1/base_controller.rb,&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;Api::V1::BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authenticate_user!&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="n"&gt;token&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;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HttpAuthentication&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token_and_options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="n"&gt;user_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kp"&gt;nil&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;:email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_email&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;user_email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SecurityUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secure_compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authentication_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&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;current_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="o"&gt;+&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;return&lt;/span&gt; &lt;span class="n"&gt;unauthenticated!&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="k"&gt;end&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;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ActionController::HttpAuthentication::Token&lt;/code&gt; 是 rails 自带的方法，可以参考 rails 文档
了解其详情。&lt;/p&gt;

&lt;p&gt;当我们通过 user_email 拿到 user 后，通过 &lt;code&gt;ActiveSupport::SecurityUtils.secure_compare&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;对 user.authentication_token 和从请求头里取到的 token 进行比较，如果匹配则认证成功，否则返回&lt;/p&gt;

&lt;p&gt;&lt;code&gt;unauthenticated!&lt;/code&gt;。这里使用了 &lt;code&gt;secure_compare&lt;/code&gt; 对字符串进行比较，是为了防止时序攻击 (timing attack)&lt;/p&gt;

&lt;p&gt;我们构造一个测试用例，这个测试用例包括以下一些步骤：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;用户登录成功，服务端返回其 email, token 等数据&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;用户请求 API 更新其 name, 用户发送的 token 合法，更新成功&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;用户请求 API 更新其 name, 用户发送的 token 非法，更新失败&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为了让用户能够更新其 name, 我们需要实现 user update API, 并且加入 &lt;code&gt;before_action :authenticate_user!, only: [:update]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;app/controllers/api/v1/users_controller.rb,&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;Api::V1::UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;V1&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:authenticate_user!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="vi"&gt;@user.update_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_params&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;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/views/api/v1/users/update.json.jbuilder,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&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;现在我们进行测试，测试用户是：&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;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s1"&gt;'test-user-00@mail.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'test-user-00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;authentication_token: &lt;/span&gt;&lt;span class="s1"&gt;'izrFiion7xEe2ccTj0v0mOcuNoT3FvpPqI31WLSCEBLvuz4xSr0d9+VI2+xVvAJjECIoju5MaoytEcg6Md773w=='&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"user[name]=gg-user"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Token token=izrFiion7xEe2ccTj0v0mOcuNoT3FvpPqI31WLSCEBLvuz4xSr0d9+VI2+xVvAJjECIoju5MaoytEcg6Md773w==, &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  email=test-user-00@mail.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:3000//api/v1/users/1

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"gg-user"&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们看到 user name 已经成功更新为 gg-user。&lt;/p&gt;

&lt;p&gt;读者们请注意：你们自己测试时需要将 token 换为你们自己生成的 token。&lt;/p&gt;

&lt;p&gt;我们使用一个非法的 token 去请求 API, 看看会发生什么状况。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"user[name]=bb-user"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Token token=invalid token, &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  email=test-user-00@mail.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:3000//api/v1/users/1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器出现错误：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;NoMethodError &lt;span class="o"&gt;(&lt;/span&gt;undefined method &lt;span class="sb"&gt;`&lt;/span&gt;unauthenticated!&lt;span class="s1"&gt;' for #&amp;lt;Api::V1::UsersController:0x007fead6108d80&amp;gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来我们实现 &lt;code&gt;unauthenticated!&lt;/code&gt; 方法。&lt;/p&gt;

&lt;p&gt;app/controllers/api/v1/base_controller.rb,&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;Api::V1::BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unauthenticated!&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="n"&gt;api_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续上面的测试：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"user[name]=bb-user"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Token token=invalid token, &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  email=test-user-00@mail.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:3000//api/v1/users/1

HTTP/1.1 401 Unauthorized 
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;block
X-Content-Type-Options: nosniff
Content-Type: text/plain&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
Cache-Control: no-cache
X-Request-Id: 8cf07968-1fd0-4041-866a-ddea49af11d3
X-Runtime: 0.005578
Server: WEBrick/1.3.1 &lt;span class="o"&gt;(&lt;/span&gt;Ruby/2.1.2/2014-05-08&lt;span class="o"&gt;)&lt;/span&gt;
Date: Sun, 03 May 2015 05:51:52 GMT
Content-Length: 0
Connection: Keep-Alive  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器返回 401 Unauthorized, 并且 user name 没有被更新。&lt;/p&gt;
&lt;h2 id="增加授权(Authorization)"&gt;增加授权 (Authorization)&lt;/h2&gt;
&lt;p&gt;上面的测试有个问题，就是当前登录的用户可以把其他用户的 name 更新，这个应该是不被允许的，所以我们
还需要增加一个权限认证的机制。在这里我们使用 &lt;a href="https://github.com/elabs/pundit" rel="nofollow" target="_blank" title=""&gt;Pundit&lt;/a&gt; 来
实现权限认证。&lt;/p&gt;
&lt;h3 id="安装 pundit"&gt;安装 pundit&lt;/h3&gt;
&lt;p&gt;Gemfile,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'pundit'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/api/v1/base_controller.rb,&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;Api::V1::BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Pundit&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g pundit:install

create  app/policies/application_policy.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 policies 目录放到 rails 的自动加载路径中：&lt;/p&gt;

&lt;p&gt;config/application.rb,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;BuildAnApiRailsDemo&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;autoload_paths&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app/policies'&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="创建和 user 相关的权限机制"&gt;创建和 user 相关的权限机制&lt;/h3&gt;
&lt;p&gt;app/policies/user_policy.rb,&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;UserPolicy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationPolicy&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&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;create?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&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;update?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&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?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&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;Scope&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationPolicy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Scope&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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;h3 id="使用 UserPolicy"&gt;使用 &lt;code&gt;UserPolicy&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;app/controllers/api/v1/users_controller.rb,&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;Api::V1::UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;V1&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;api_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="no"&gt;UserPolicy&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;current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update?&lt;/span&gt;
    &lt;span class="vi"&gt;@user.update_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_params&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;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"user[name]=gg-user"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Token token=izrFiion7xEe2ccTj0v0mOcuNoT3FvpPqI31WLSCEBLvuz4xSr0d9+VI2+xVvAJjECIoju5MaoytEcg6Md773w==, &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  email=test-user-00@mail.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:3000//api/v1/users/2.json

HTTP/1.1 403 Forbidden 
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;block
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意我们测试的 url 地址是 &lt;a href="http://localhost:3000//api/v1/users/2" rel="nofollow" target="_blank"&gt;http://localhost:3000//api/v1/users/2&lt;/a&gt;, 也就是说我们在更新 id 为
2 的那个用户的 name。此时服务器返回的是 403 Forbidden。&lt;/p&gt;

&lt;p&gt;pundit 提供了更简便的 &lt;code&gt;authorize&lt;/code&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;Api::V1::UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;V1&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;# return api_error(status: 403) if !UserPolicy.new(current_user, @user).update?&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="n"&gt;authorize&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update?&lt;/span&gt;
    &lt;span class="vi"&gt;@user.update_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_params&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;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"user[name]=gg-user"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Token token=izrFiion7xEe2ccTj0v0mOcuNoT3FvpPqI31WLSCEBLvuz4xSr0d9+VI2+xVvAJjECIoju5MaoytEcg6Md773w==, &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  email=test-user-00@mail.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:3000//api/v1/users/2.json

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时服务器报 Pundit::NotAuthorizedError 错误，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Pundit::NotAuthorizedError &lt;span class="o"&gt;(&lt;/span&gt;not allowed to update?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以使用 &lt;code&gt;rescue_from&lt;/code&gt; 捕捉 &lt;code&gt;Pundit::NotAuthorizedError&lt;/code&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;Api::V1::BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Pundit&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="n"&gt;rescue_from&lt;/span&gt; &lt;span class="no"&gt;Pundit&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NotAuthorizedError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :deny_access&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deny_access&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="n"&gt;api_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&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;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"user[name]=gg-user"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Token token=izrFiion7xEe2ccTj0v0mOcuNoT3FvpPqI31WLSCEBLvuz4xSr0d9+VI2+xVvAJjECIoju5MaoytEcg6Md773w==, &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  email=test-user-00@mail.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:3000//api/v1/users/2

HTTP/1.1 403 Forbidden 
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;block
X-Content-Type-Options: nosniff
Content-Type: text/plain&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次服务器直接返回 403 Forbidden &lt;/p&gt;
&lt;h2 id="分页"&gt;分页&lt;/h2&gt;
&lt;p&gt;我们现在要实现一个展示用户发的微博的 API, 如果用户的微博数量很多，那么我们应该用上分页。 &lt;/p&gt;
&lt;h3 id="建立 Micropost 模型"&gt;建立 Micropost 模型&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g model Micropost
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;db/migrate/20150503131743_create_microposts.rb,&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;CreateMicroposts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:microposts&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;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="ss"&gt;:content&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:user_id&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&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;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rake db:migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为 id 为 1 的用户创建 100 条微博纪录：&lt;/p&gt;

&lt;p&gt;lib/tasks/data.rake,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:data&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:create_microposts&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;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&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;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;Micropost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"title-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="s2"&gt;"content-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rake data:create_microposts
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Api::V1::MicropostsController"&gt;Api::V1::MicropostsController&lt;/h3&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g controller api/v1/microposts &lt;span class="nt"&gt;--no-assets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置路由：&lt;/p&gt;

&lt;p&gt;config/routes.rb,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:api&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:v1&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;scope&lt;/span&gt; &lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="s1"&gt;'/user/:user_id'&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;resources&lt;/span&gt; &lt;span class="ss"&gt;:microposts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;]&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;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;此时和 microposts 相关的路由如下：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;api_v1_microposts GET    /api/v1/user/:user_id/microposts&lt;span class="o"&gt;(&lt;/span&gt;.:format&lt;span class="o"&gt;)&lt;/span&gt; api/v1/microposts#index
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用 &lt;a href="https://github.com/amatsuda/kaminari" rel="nofollow" target="_blank" title=""&gt;kaminari&lt;/a&gt; 这个 gem 进行分页。&lt;/p&gt;

&lt;p&gt;安装 kaminari,&lt;/p&gt;

&lt;p&gt;Gemfile&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'kaminari'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/models/user.rb&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;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;

 &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:microposts&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/api/v1/microposts_controller.rb&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;Api::V1::MicropostsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;V1&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="vi"&gt;@microposts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;microposts&lt;/span&gt;&lt;span class="p"&gt;)&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/api/v1/base_controller.rb&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;Api::V1::BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:page&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&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;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:per_page&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;per&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:per_page&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;return&lt;/span&gt; &lt;span class="n"&gt;resource&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;app/helpers/application_helper.rb&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ApplicationHelper&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;paginate_meta_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;          &lt;span class="ss"&gt;:current_page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;          &lt;span class="ss"&gt;:next_page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;          &lt;span class="ss"&gt;:prev_page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;          &lt;span class="ss"&gt;:total_pages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;          &lt;span class="ss"&gt;:total_count&lt;/span&gt;&lt;span class="p"&gt;)&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/views/api/v1/microposts/index.json.jbuilder,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paginate_meta&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;paginate_meta_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@microposts&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;microposts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array!&lt;/span&gt; &lt;span class="vi"&gt;@microposts&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;micropost&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;micropost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:content&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;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; GET http://localhost:3000/api/v1/user/1/microposts.json?per_page&lt;span class="o"&gt;=&lt;/span&gt;3

&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"paginate_meta"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"current_page"&lt;/span&gt;:1,
    &lt;span class="s2"&gt;"next_page"&lt;/span&gt;:2,
    &lt;span class="s2"&gt;"prev_page"&lt;/span&gt;:null,
    &lt;span class="s2"&gt;"total_pages"&lt;/span&gt;:34,
    &lt;span class="s2"&gt;"total_count"&lt;/span&gt;:100
    &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="s2"&gt;"microposts"&lt;/span&gt;:[
    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"title"&lt;/span&gt;:&lt;span class="s2"&gt;"title-0"&lt;/span&gt;,&lt;span class="s2"&gt;"content"&lt;/span&gt;:&lt;span class="s2"&gt;"content-0"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:2,&lt;span class="s2"&gt;"title"&lt;/span&gt;:&lt;span class="s2"&gt;"title-1"&lt;/span&gt;,&lt;span class="s2"&gt;"content"&lt;/span&gt;:&lt;span class="s2"&gt;"content-1"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:3,&lt;span class="s2"&gt;"title"&lt;/span&gt;:&lt;span class="s2"&gt;"title-2"&lt;/span&gt;,&lt;span class="s2"&gt;"content"&lt;/span&gt;:&lt;span class="s2"&gt;"content-2"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="API 调用频率限制(Rate Limit)"&gt;API 调用频率限制 (Rate Limit)&lt;/h2&gt;
&lt;p&gt;我们使用 &lt;a href="https://github.com/andreareginato/redis-throttle" rel="nofollow" target="_blank" title=""&gt;redis-throttle&lt;/a&gt; 来实现这个功能。&lt;/p&gt;

&lt;p&gt;Gemfile,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'redis-throttle'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;git: &lt;/span&gt;&lt;span class="s1"&gt;'git://github.com/andreareginato/redis-throttle.git'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;集成到 Rails 中：&lt;/p&gt;

&lt;p&gt;config/application.rb,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# At the top of config/application.rb&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack/redis_throttle'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
  &lt;span class="c1"&gt;# Limit the daily number of requests to 3&lt;/span&gt;
  &lt;span class="c1"&gt;# 为了测试我们把 limit 设置为 3&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;middleware&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;RedisThrottle&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Daily&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;max: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们开始测试，请求 &lt;a href="http://localhost:3000/api/v1/users/1" rel="nofollow" target="_blank"&gt;http://localhost:3000/api/v1/users/1&lt;/a&gt; 4 次看会出现什么结果。&lt;/p&gt;

&lt;p&gt;前面 3 次请求一切正常，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://localhost:3000/api/v1/users/1

HTTP/1.1 200 OK 
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;block
X-Content-Type-Options: nosniff
Content-Type: application/json&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
X-Ratelimit-Limit: 3
X-Ratelimit-Remaining: 0
Etag: W/&lt;span class="s2"&gt;"eb58510a43ebc583cf61de35b6d20093"&lt;/span&gt;
Cache-Control: max-age&lt;span class="o"&gt;=&lt;/span&gt;0, private, must-revalidate
X-Request-Id: bbe7437b-ba6e-4cfd-a4ef-49eec4c611fd
X-Runtime: 0.014384
Server: WEBrick/1.3.1 &lt;span class="o"&gt;(&lt;/span&gt;Ruby/2.1.2/2014-05-08&lt;span class="o"&gt;)&lt;/span&gt;
Date: Thu, 07 May 2015 13:03:31 GMT
Content-Length: 199
Connection: Keep-Alive

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"email"&lt;/span&gt;:&lt;span class="s2"&gt;"test-user-00@mail.com"&lt;/span&gt;,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"gg-user"&lt;/span&gt;,&lt;span class="s2"&gt;"activated"&lt;/span&gt;:&lt;span class="s2"&gt;"2015-05-02T07:47:14.697Z"&lt;/span&gt;,&lt;span class="s2"&gt;"admin"&lt;/span&gt;:false,&lt;span class="s2"&gt;"created_at"&lt;/span&gt;:&lt;span class="s2"&gt;"2015-05-02T07:47:14.708Z"&lt;/span&gt;,&lt;span class="s2"&gt;"updated_at"&lt;/span&gt;:&lt;span class="s2"&gt;"2015-05-03T05:40:24.931Z"&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们注意服务器返回的两个响应头：X-Ratelimit-Limit 和 X-Ratelimit-Remaining,&lt;/p&gt;

&lt;p&gt;X-Ratelimit-Limit 的值一直为 3，表示请求的限制值，&lt;/p&gt;

&lt;p&gt;而 X-Ratelimit-Remaining 每请求一次，其值会减 1，直到为 0。&lt;/p&gt;

&lt;p&gt;第 4 次请求出现 403 Forbidden, 这说明 redis-throttle 起到了其应有的作用。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://localhost:3000/api/v1/users/1 

HTTP/1.1 403 Forbidden 
Content-Type: text/plain&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
Cache-Control: no-cache
X-Request-Id: fd646f00-a6a8-411d-b5e4-24856c63b078
X-Runtime: 0.002375
Server: WEBrick/1.3.1 &lt;span class="o"&gt;(&lt;/span&gt;Ruby/2.1.2/2014-05-08&lt;span class="o"&gt;)&lt;/span&gt;
Date: Thu, 07 May 2015 13:03:33 GMT
Content-Length: 35
Connection: Keep-Alive

403 Forbidden &lt;span class="o"&gt;(&lt;/span&gt;Rate Limit Exceeded&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;redis-throttle 的 redis 连接默认是 redis://localhost:6379/0, 你也可以通过设置环境变量&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ENV['REDIS_RATE_LIMIT_URL']&lt;/code&gt; 来改变 redis-throttle 的 redis 连接。&lt;/p&gt;
&lt;h2 id="CORS"&gt;CORS&lt;/h2&gt;
&lt;p&gt;CORS 是 Cross Origin Resource Sharing 的缩写。简单地说 CORS 可以允许其他域名的网页通过 AJAX 请求你的 API。&lt;/p&gt;

&lt;p&gt;我们可以使用 &lt;code&gt;rack-cors&lt;/code&gt; gem 来帮助我们的 API 实现 CORS。&lt;/p&gt;

&lt;p&gt;Gemfile,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rack-cors'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;config/application.rb,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;BuildAnApiRailsDemo&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert_before&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Rack::Cors"&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;allow&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;origins&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;        &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:headers&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:methods&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;:get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:put&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:patch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:head&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;      &lt;span class="k"&gt;end&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Version 2 API"&gt;Version 2 API&lt;/h2&gt;
&lt;p&gt;随着我们的业务发展，我们的 API 需要做较大的改变，同时我们需要保持 Version 1 API, 所以我们
开始开发 Version 2 API。&lt;/p&gt;
&lt;h3 id="routes"&gt;routes&lt;/h3&gt;
&lt;p&gt;config/routes.rb,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:api&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:v1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:destroy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="c1"&gt;# resources :microposts, only: [:index, :create, :show, :update, :destroy]&lt;/span&gt;
      &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:sessions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="s1"&gt;'/user/:user_id'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:microposts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&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="o"&gt;+&lt;/span&gt;    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:v2&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;resources&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:destroy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;      &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:sessions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;      &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="s1"&gt;'/user/:user_id'&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;resources&lt;/span&gt; &lt;span class="ss"&gt;:microposts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;      &lt;span class="k"&gt;end&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;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="controller"&gt;controller&lt;/h3&gt;
&lt;p&gt;生成 API::V2::UsersController, 其他控制器的生成类似&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g controller api/v2/users &lt;span class="nt"&gt;--no-assets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/api/v2/users_controller.rb,&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;Api::V2::UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;V1&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UsersController&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&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;app/vies/api/v2/users/show.json.jbuilder,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;:activated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:updated_at&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;测试：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://localhost:3000/api/v2/users/1.json

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"email"&lt;/span&gt;:&lt;span class="s2"&gt;"test-user-00@mail.com"&lt;/span&gt;,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"gg-user"&lt;/span&gt;,&lt;span class="s2"&gt;"activated"&lt;/span&gt;:&lt;span class="s2"&gt;"2015-05-02T07:47:14.697Z"&lt;/span&gt;,&lt;span class="s2"&gt;"admin"&lt;/span&gt;:false,&lt;span class="s2"&gt;"created_at"&lt;/span&gt;:&lt;span class="s2"&gt;"2015-05-02T07:47:14.708Z"&lt;/span&gt;,&lt;span class="s2"&gt;"updated_at"&lt;/span&gt;:&lt;span class="s2"&gt;"2015-05-03T05:40:24.931Z"&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;%    
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="文档"&gt;文档&lt;/h2&gt;
&lt;p&gt;原文提到了下面的几种文档工具：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/marsz/swagger-rails" rel="nofollow" target="_blank" title=""&gt;swagger-rails&lt;/a&gt; 和 &lt;a href="https://github.com/richhollis/swagger-docs" rel="nofollow" target="_blank" title=""&gt;swagger-docs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Apipie/apipie-rails" rel="nofollow" target="_blank" title=""&gt;apipie-rails&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/tripit/slate" rel="nofollow" target="_blank" title=""&gt;slate&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;和原文一样，我也喜欢使用 slate 作为文档工具。&lt;/p&gt;
&lt;h3 id="将 slate 集成到项目中"&gt;将 slate 集成到项目中&lt;/h3&gt;
&lt;p&gt;创建 docs 目录，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;app/docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;集成 slate,&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;app/docs

&lt;span class="nv"&gt;$ &lt;/span&gt;git clone git@github.com:tripit/slate.git

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; slate/.git

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;slate

&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置构建目录，app/docs/slate/config.rb&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:build_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'../../../public/docs/'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们开始编写获取用户信息这个 API 的文档。&lt;/p&gt;

&lt;p&gt;app/docs/slate/source/index.md,&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
title: API Reference

language_tabs:
  - ruby

toc_footers:
  - &amp;lt;a href='http://github.com/tripit/slate'&amp;gt;Documentation Powered by Slate&amp;lt;/a&amp;gt;

includes:
  - errors

search: true
---

# 介绍

API 文档

# 获取用户信息

## V1

## HTTP 请求

`GET http://my-site/api/v1/users/&amp;lt;id&amp;gt;`

## 请求参数

参数名 | 是否必需 | 描述
-----| --------| -------
id   |  是      | 用户 id|

## 响应

\```json
{
  "user":
  {
    "id":1,
    "email":"test-user-00@mail.com",
    "name":"test-user-00",
    "activated":"2015-05-02T07:47:14.697Z",
    "admin":false,
    "created_at":"2015-05-02T07:47:14.708Z",
    "updated_at":"2015-05-02T07:47:14.708Z"
   }
}
\```

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：index.md 范例里的 json 代码语法高亮部分有转义字符，直接复制可能没法看到语法高亮效果，在实际使用时需要将 ``` 前面的 '\' 符号去掉。&lt;/p&gt;

&lt;p&gt;build 脚本&lt;/p&gt;

&lt;p&gt;docs_build.sh,&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;cd &lt;/span&gt;app/docs/slate

bundle &lt;span class="nb"&gt;exec &lt;/span&gt;middleman build &lt;span class="nt"&gt;--clean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;build docs,&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x docs_build.sh

&lt;span class="nv"&gt;$ &lt;/span&gt;./docs_build.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以通过 &lt;code&gt;http://localhost:3000/docs/index.html&lt;/code&gt; 访问文档&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/f0e5d0e6e4ebd54562103e7163b5343d.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="给 API 文档添加访问控制"&gt;给 API 文档添加访问控制&lt;/h3&gt;
&lt;p&gt;配置路由：&lt;/p&gt;

&lt;p&gt;routes.rb,&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/docs/index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'docs#index'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建立相关控制器：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle exe rails g controller docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/docs_controller.rb,&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;DocsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="no"&gt;USER_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'doc_reader'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'123123'&lt;/span&gt;

  &lt;span class="n"&gt;before_filter&lt;/span&gt; &lt;span class="ss"&gt;:basic_authenticate&lt;/span&gt;

  &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&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;basic_authenticate&lt;/span&gt;
    &lt;span class="n"&gt;authenticate_or_request_with_http_basic&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;user_name&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="n"&gt;user_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;USER_NAME&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;PASSWORD&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;同时我们需要把 public/docs/index.html 文件转移到 app/views/docs/ 目录下面，我们&lt;/p&gt;

&lt;p&gt;可以更改 docs_build.sh 脚本，注意 docs_build.sh 应该放在项目的根目录下，比如：/path/to/build-an-api-rails-demo/docs_build.sh,&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;app_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$app_dir&lt;/span&gt;/app/docs/slate

bundle &lt;span class="nb"&gt;exec &lt;/span&gt;middleman build &lt;span class="nt"&gt;--clean&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$app_dir&lt;/span&gt;

&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nv"&gt;$app_dir&lt;/span&gt;/public/docs/index.html &lt;span class="nv"&gt;$app_dir&lt;/span&gt;/app/views/docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新 build 文档，&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./docs_build.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器访问 &lt;a href="http://localhost:3000/docs/index.html" rel="nofollow" target="_blank"&gt;http://localhost:3000/docs/index.html&lt;/a&gt;,&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/a783135457f2bfb28ff002c43a2f8d10.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;提示需要输入用户名和密码，我们输入正确的用户名 (doc_reader) 和密码 (123123) 后就可以正常访问文档了，&lt;/p&gt;
&lt;h2 id="项目代码"&gt;项目代码&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/baya/build-an-api-rails-demo" rel="nofollow" target="_blank" title=""&gt;build-an-api-rails-demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;感谢 &lt;a href="/lazybios" class="user-mention" title="@lazybios"&gt;&lt;i&gt;@&lt;/i&gt;lazybios&lt;/a&gt; 同学提的 5 点建议，让本文变的更好。&lt;/p&gt;

&lt;p&gt;感谢 &lt;a href="/night_7th" class="user-mention" title="@night_7th"&gt;&lt;i&gt;@&lt;/i&gt;night_7th&lt;/a&gt; 提的注明 &lt;code&gt;skip_before_filter :verify_authenticity_token&lt;/code&gt; 的建议。&lt;/p&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Sun, 31 May 2015 13:06:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/25822</link>
      <guid>https://ruby-china.org/topics/25822</guid>
    </item>
    <item>
      <title>[北京] Magus Soft 公司招聘 Ruby 工程师 3 名</title>
      <description>&lt;p&gt;香港新空气控股（Magus Soft Holding）致力于投资、发展基于无线互联网络的新技术与新应用。2001 年投资成立北京新空气软件技术有限公司。&lt;/p&gt;

&lt;p&gt;空气的使命是给用户提供移动互联应用综合解决方案，在手机游戏、电信运营商移动增值服务等方面始终保持着行业技术的领先水平，与三大运营商建立了战略合作关系，并在 2012 年成为英特尔的合作伙伴，2013 年加入微软 BizSpark 企业扶植计划，2014 年成为亚马逊 AWS 合作伙伴。&lt;/p&gt;

&lt;p&gt;新空气努力为公众提供便捷的移动生活体验，将移动互联的前沿技术应用于社会生活的方方面面，推出的国内首个银行类客户端产品---“招商银行掌上生活”，连续被评为苹果 APP 最受用户欢迎的财经类应用。&lt;/p&gt;

&lt;p&gt;目前新空气专注于各类行业应用的设计、开发、运营、维护及推广，服务涉及手机银行、手机话费充值、手机彩票、手机影票等等。&lt;/p&gt;

&lt;p&gt;更多信息可以通过 &lt;a href="http://www.magus-soft.com/" rel="nofollow" target="_blank" title=""&gt;http://www.magus-soft.com/&lt;/a&gt; 了解。&lt;/p&gt;

&lt;p&gt;我们可以为您提供：行业领先的视角，快速成长的机会，稳定可观的福利待遇。诚邀您的加入！&lt;/p&gt;

&lt;p&gt;加入 Magus Soft 的 N 条理由：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;最先进的无线互联网技术产品开发团队；&lt;/li&gt;
&lt;li&gt;开放式办公－见解比职务重要；&lt;/li&gt;
&lt;li&gt;员工第一；&lt;/li&gt;
&lt;li&gt;Innovating Like MAD!&lt;/li&gt;
&lt;li&gt;成为无线互联网开发、发行的国际领先企业；&lt;/li&gt;
&lt;li&gt;提供极具竞争力的薪资福利待遇和发展前景；&lt;/li&gt;
&lt;li&gt;组织不定期的员工活动，营造愉快的工作氛围；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;诚邀 Ruby 开发者加入我们，目前采用 Ruby 所开发的增值业务稳定为千万级用户提供不间断服务，在用户数不断攀升的情况下，需要对系统做结构性的重构，需要熟悉掌握 Ruby 语言以及 Rails 开发框架。&lt;/p&gt;

&lt;p&gt;只要您：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;熟悉掌握 Ruby 语言，了解并能使用各个版本的 Rails&lt;/li&gt;
&lt;li&gt;能在 Linux 下完成工作&lt;/li&gt;
&lt;li&gt;对数据库有足够对认识&lt;/li&gt;
&lt;li&gt;了解 HTTP 协议&lt;/li&gt;
&lt;li&gt;有良好的代码书写和编程习惯、非常强的学习能力，有很强的分析和解决问题能力&lt;/li&gt;
&lt;li&gt;在 github.com 有托管项目以及参与开源项目者更佳&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;就有机会：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;为各大银行千万级用户提供服务&lt;/li&gt;
&lt;li&gt;学习接触到最前沿到云计算平台 (AWS EC2,SQS,SNS,SWF,S3,RDS,DynamoDB,EMR 等等...)&lt;/li&gt;
&lt;li&gt;有机会参与项目的设计和决策，拓宽视野&lt;/li&gt;
&lt;li&gt;不仅局限于 Ruby，还能和其他技术团队混合开发&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="公司补充信息"&gt;公司补充信息&lt;/h2&gt;
&lt;p&gt;公司目前的客户群主要有招商银行，建设银行，浦发银行，万达影院，海底捞等。&lt;/p&gt;

&lt;p&gt;公司地址：北京市朝阳区朝阳北路 52 号院 4 号楼底商 3 号二层，大概方位是东五环离传媒大学很近。&lt;/p&gt;
&lt;h2 id="薪资待遇"&gt;薪资待遇&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;工资：8-18K&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;按国家规定五险一金&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="联系方式"&gt;联系方式&lt;/h2&gt;
&lt;p&gt;hr-bj@funguide.com.cn&lt;/p&gt;

&lt;p&gt;Ruby 工程师的简历会第一时间由 HR 转发给我们的技术负责人。&lt;/p&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Fri, 14 Nov 2014 18:00:44 +0800</pubDate>
      <link>https://ruby-china.org/topics/22692</link>
      <guid>https://ruby-china.org/topics/22692</guid>
    </item>
    <item>
      <title>一步一步 DSL</title>
      <description>&lt;p&gt;原文地址： &lt;a href="http://baya.github.io/2013/12/06/%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5dsl/" rel="nofollow" target="_blank"&gt;http://baya.github.io/2013/12/06/%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5dsl/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;原文比较长哈，给个提纲，&lt;/p&gt;

&lt;p&gt;大概是 12 年的 10 月份，想为公司的彩票，话费充值等业务定义一套&lt;a href="http://en.wikipedia.org/wiki/Domain-specific_language" rel="nofollow" target="_blank" title=""&gt;DSL(Domain-specific language)&lt;/a&gt;，这套 DSL 主要用于添加渠道，添加产品，配置订单重试等，于是花了些时间学习了下 Ruby 的 DSL 技术，在学习和实践的过程中，我总结了下 Ruby 中常见的 DSL 风格及其常用技术实现，并且对怎样实践 DSL 形成了一些自己的感受。&lt;/p&gt;
&lt;h3 id="第一部分，Ruby中常见的DSL风格及其技术实现"&gt;第一部分，Ruby 中常见的 DSL 风格及其技术实现&lt;/h3&gt;
&lt;p&gt;我归纳了四种风格，很可能有遗漏或者描述的不够准确，欢迎大家在后面补充指正。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;嵌套风格&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;链式风格&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;类宏风格&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;补丁风格&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于每种风格的 DSL，我给出了一些常见的技术实现，这些技术都能在我们一些常用的 gem 中找到相应的代码实现，比如 &lt;strong&gt;嵌套风格&lt;/strong&gt; 的 DSL 可以通过&lt;code&gt;yield&lt;/code&gt;, &lt;code&gt;instance_eval&lt;/code&gt;, 转储&lt;code&gt;&amp;amp;block&lt;/code&gt;, &lt;code&gt;method_missing&lt;/code&gt;等实现，在 rails_admin, rack, rspec, jbuilder 等 gem 中能找到相关的代码作为例证和学习资料。&lt;/p&gt;
&lt;h3 id="第二部分, Ruby的DSL实践"&gt;第二部分，Ruby 的 DSL 实践&lt;/h3&gt;
&lt;p&gt;在干完了一堆又一堆的脏活，累活后，我感觉世界清静了许多，人也清醒了许多，我只是一个程序员，摆在我面前的现实就是我要尽力在规定的时间内干完活，完成任务，而不是让上级怀疑我在偷懒，所以与其漫无目的去开发一种类似于 rails, activerecord 这种近乎业界标准的 DSL，还不如直面眼前的问题，一步一个脚印，快速而不失优雅的去解决眼前的问题。相对于 rails, activerecord 这种 Big DSL，我提出与之对应的一个概念 Small DSL，针对 Small DSL，我给 &lt;strong&gt;domain&lt;/strong&gt; 做了一个定义，&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;是一个 class&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;可以接收外部数据&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以上面的定义为基础，我写了一个 gem &lt;a href="https://github.com/baya/dun" rel="nofollow" target="_blank" title=""&gt;dun&lt;/a&gt;，这个 gem 里面只包含一个 class  &lt;code&gt;Dun::Land&lt;/code&gt;, 我们可以使用&lt;code&gt;Dun::Land&lt;/code&gt;对 domain 进行封装，详细的用法可以看看&lt;a href="https://github.com/baya/dun/blob/master/README.md" rel="nofollow" target="_blank"&gt;https://github.com/baya/dun/blob/master/README.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;为什么会写这样一个 gem？主要基于下面四点的思考，&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;不要设计精巧的类&lt;/strong&gt;  精巧的东西往往很脆弱。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;真的要很直接&lt;/strong&gt;  定义一个类，就表明你知道你想干的事情是什么，你想干的事情在这个类中就能解决了，而不是山路十八弯，还要牵扯到许多其他地方的代码。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;拒绝过度的设计模式&lt;/strong&gt;  与眼花缭乱的设计模式告别，回归我们最初的 &lt;strong&gt;程序 = 数据结构 ＋ 算法&lt;/strong&gt;，确定一个类，也就确定了一个算法，实现了算法也就解决了问题。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;写大量的并且可能枯燥的测试&lt;/strong&gt;   写的每一个类都要测试，什么样的类好测试呢？ &lt;strong&gt;小的&lt;/strong&gt;， &lt;strong&gt;单一职责的&lt;/strong&gt;， &lt;strong&gt;解耦合的&lt;/strong&gt;， &lt;strong&gt;带输入输出的&lt;/strong&gt;  类好测试，为了更好的测试，你会发现你需要写许许多多的类，而不是把方法都塞到 model, controller 里面去了。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;欢迎大家贴出自己写的感觉比较好的 DSL&lt;/p&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Mon, 06 Jan 2014 13:49:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/16650</link>
      <guid>https://ruby-china.org/topics/16650</guid>
    </item>
    <item>
      <title>写了一个小工具 Gstar,帮助你搜索在 github 上 star 过的项目</title>
      <description>&lt;p&gt;一不小心在 github 上 star 过的项目将近 800 个了，我发现从这 800 个项目里找出我需要的项目不是一件容易的事情了，比如说印象里我有 star 过几个关于中文分词的 project，当我用 github 自带的搜星功能去搜索'中文分词'时，却发现什么也找不到，&lt;/p&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/d411ad021af5edde0bd7dc925a356dbd.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;那就动手自己写一个搜星工具吧。通过 Gstar 搜索'中文分词',&lt;/p&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/b736d40c8a26401dc61c57df5f94056c.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="安装"&gt;安装&lt;/h2&gt;
&lt;p&gt;一。git clone &lt;a href="https://github.com/baya/Gstar.git" rel="nofollow" target="_blank"&gt;https://github.com/baya/Gstar.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;二。cd Gstar/ground&lt;/p&gt;

&lt;p&gt;三。bundle install&lt;/p&gt;

&lt;p&gt;四。rake db:migrate&lt;/p&gt;

&lt;p&gt;五。cp config/github.example.yml config/github.yml&lt;/p&gt;

&lt;p&gt;在 config/github.yml 里有三个配置，其中 login 为必填项，access_token 和 password 选一个填写即可，
login 和 password 分别是你用来登录 github 的用户名和密码，access_token 可以通过下面的步骤得到，&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; 点击 Account Settings，进入帐号设置页面&lt;/li&gt;
&lt;li&gt; 选择左侧栏的 Applications 导航，可以看到 Personal Access Tokens 这个设置，点击右上角的 Create new token 生   成新的 access_token 即可。
&lt;img src="//l.ruby-china.com/photo/a4eb84fb8bd771c08c6c635fe47629b2.png" title="" alt=""&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;六。rake maintain:pull_stars_from_github # 将你 star 过的项目从 github 拉到本地数据库&lt;/p&gt;

&lt;p&gt;七。whenever --update                           # 启动定时任务，每分钟检查一次你是否有新的 star 项目&lt;/p&gt;

&lt;p&gt;八。rackup -p 9292&lt;/p&gt;

&lt;p&gt;九。使用浏览器访问 &lt;a href="http://localhost:9292" rel="nofollow" target="_blank"&gt;http://localhost:9292&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;注意操作三到八都是在 Gstar/ground 目录下进行的。&lt;/p&gt;
&lt;h2 id="功能特点"&gt;功能特点&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 准确快速的搜索&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;github 自带的搜星功能不是特别好用，比如说我想找出我收藏的 (github 的标星功能对我来说就是收藏) 与 markdown 有关的项目，使用 github 自带的搜星功能只能找到一个叫 miclle/Markdown-Editor 的项目，因为 github 是通过项目的名字进行搜索，这导致很多与 markdown 有关的项目被忽略了，但是使用 Gstar 搜索 markdown 能找到 11 个与 markdown 有关的项目，因为 Gstar 对项目名和项目描述都进行搜索，这样比 github 更准确些。&lt;/p&gt;

&lt;p&gt;github 的搜星：&lt;/p&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/68fdc48aebe0f54b9deaa747efe716a2.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;gstar 的搜星：&lt;/p&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/2823bccb219e0797ac1f864e87ff54ab.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.可以修改项目的描述 (description)，实现类似于打标签的功能&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;比如说我给 karmi/tire 这个项目的描述加上"搜索"，那么我就可以通过搜索"搜索"找到 tire 这个项目了。&lt;/p&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/17cd5c44ea98354c05520f24e3804c62.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.自动更新 starred projects&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gstar 自带一个定时任务，每一分钟会去 github 那检查是否有新的 stars，如果有就会拉到本地，建立索引，方便你以后搜索使用。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;参考资料&lt;/h2&gt;&lt;h4 id="full text search"&gt;full text search&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://sqlite.org/fts3.html" rel="nofollow" target="_blank"&gt;http://sqlite.org/fts3.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/copiousfreetime/amalgalite" rel="nofollow" target="_blank"&gt;https://github.com/copiousfreetime/amalgalite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://sqlite.org/rtree.html" rel="nofollow" target="_blank"&gt;http://sqlite.org/rtree.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;参考书籍：这就是搜索引擎&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="github api"&gt;github api&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;list repositories being starred &lt;a href="http://developer.github.com/v3/activity/starring/#list-repositories-being-starred" rel="nofollow" target="_blank"&gt;http://developer.github.com/v3/activity/starring/#list-repositories-being-starred&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="angular js search"&gt;angular js search&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/daha/angularJS-github-contributors" rel="nofollow" target="_blank"&gt;https://github.com/daha/angularJS-github-contributors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="ruby正则匹配中文"&gt;ruby 正则匹配中文&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://www.ruby-doc.org/core-1.9.3/Regexp.html" rel="nofollow" target="_blank"&gt;http://www.ruby-doc.org/core-1.9.3/Regexp.html&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="http://ruby-china.org/topics/5680" rel="nofollow" target="_blank"&gt;http://ruby-china.org/topics/5680&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="sequel相关知识"&gt;sequel 相关知识&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://sequel.rubyforge.org/rdoc/files/doc/migration_rdoc.html" rel="nofollow" target="_blank"&gt;http://sequel.rubyforge.org/rdoc/files/doc/migration_rdoc.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://sequel.rubyforge.org/rdoc/classes/Sequel/Migrator.html" rel="nofollow" target="_blank"&gt;http://sequel.rubyforge.org/rdoc/classes/Sequel/Migrator.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://sequel.rubyforge.org/rdoc/files/doc/schema_modification_rdoc.html" rel="nofollow" target="_blank"&gt;http://sequel.rubyforge.org/rdoc/files/doc/schema_modification_rdoc.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="angular js分页"&gt;angular js 分页&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://jsfiddle.net/SAWsA/11/" rel="nofollow" target="_blank"&gt;http://jsfiddle.net/SAWsA/11/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.ivivelabs.com/blog/pagination-in-angularjs-for-typekit-api-show-more-pagination-in-angularjs/" rel="nofollow" target="_blank"&gt;http://www.ivivelabs.com/blog/pagination-in-angularjs-for-typekit-api-show-more-pagination-in-angularjs/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jsfiddle.net/2NjN5/" rel="nofollow" target="_blank"&gt;http://jsfiddle.net/2NjN5/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://groups.google.com/forum/#!topic/angular/kYg3pP-CeJU" rel="nofollow" target="_blank"&gt;https://groups.google.com/forum/#!topic/angular/kYg3pP-CeJU&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jsfiddle.net/8WUrR/" rel="nofollow" target="_blank"&gt;http://jsfiddle.net/8WUrR/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Tue, 08 Oct 2013 22:52:47 +0800</pubDate>
      <link>https://ruby-china.org/topics/14595</link>
      <guid>https://ruby-china.org/topics/14595</guid>
    </item>
    <item>
      <title>Description,一个通过分析 ActiveRecord::Migration 文件提取数据库文档的工具</title>
      <description>&lt;p&gt;花了半个多月的时间写了个小工具 &lt;a href="https://github.com/baya/description" rel="nofollow" target="_blank" title=""&gt;Description&lt;/a&gt;,
Description 能对你的 rails 项目的 ActiveRecord::Migration 文件进行分析，然后得到和项目有关的数据库文档&lt;/p&gt;
&lt;h3 id="安装"&gt;安装&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git clone https://github.com/baya/description.git&lt;/code&gt;
&lt;code&gt;cd description&lt;/code&gt;
&lt;code&gt;bundle install&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="使用"&gt;使用&lt;/h3&gt;
&lt;p&gt;在 description 目录下执行
&lt;code&gt;chmod +x bin/describle&lt;/code&gt;
&lt;code&gt;bin/describle /path/to/your/rails/app&lt;/code&gt;
然后使用浏览器访问 &lt;a href="http://localhost:9393" rel="nofollow" target="_blank"&gt;http://localhost:9393&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="主要功能"&gt;主要功能&lt;/h3&gt;
&lt;p&gt;1.提取 migration 文件中的 comment，比如你创建一个表 contests，&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;CreateContests&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
      &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:contests&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;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;    &lt;span class="ss"&gt;:itype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;  &lt;span class="c1"&gt;# 比赛形式, 0 photo, 1 video, 2 word, 3 audio, 4 组合&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;    &lt;span class="ss"&gt;:mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;   &lt;span class="c1"&gt;# 是否可加入比赛, 0 pk, 1 调查, 2 多选&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;    &lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# 比赛方式, 1 pk, 0 binary(传统比赛)&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;       &lt;span class="ss"&gt;:start_day&lt;/span&gt;                       &lt;span class="c1"&gt;# 比赛开始日期&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;    &lt;span class="ss"&gt;:length&lt;/span&gt;                          &lt;span class="c1"&gt;# 比赛时长以天为单位&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;    &lt;span class="ss"&gt;:vote_right&lt;/span&gt;                      &lt;span class="c1"&gt;# 投票权, 0 public, 1 friends only, 2 specific&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;    &lt;span class="ss"&gt;:contestant_right&lt;/span&gt;                &lt;span class="c1"&gt;# 参赛权, 0 public, 1 friedns only, 2 specific&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;     &lt;span class="ss"&gt;:logo&lt;/span&gt;                            &lt;span class="c1"&gt;# 比赛logo,图片&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&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;/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;AddStatusToContests&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
      &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:contests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:pub_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# 发布状态, 0 草稿, 1 发布&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;description 会将你的代码解析成一个 markdown 格式的 table,
&lt;img src="//l.ruby-china.com/photo/e3c9471ce338bad60f402950f6e8c044.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;2.快速检索表和字段
有些项目的表很多，表很大，在 description 中，你可以通过表名和字段名快速找出你想要查看的表和字段&lt;/p&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/0812197441526c35902a8fa7abe9445f.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/129dff61d13b22b8e7daddf4d2797dfd.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;3.提供表的 markdown 文件的下载&lt;/p&gt;

&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/afffebbe2be7243f04b87ab4de5d0669.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;结合 mou 等工具你可以很容易制作出十几页的项目文档，从此你就不需要为项目
交接时的文档发愁了&lt;/p&gt;
&lt;h3 id="后记"&gt;后记&lt;/h3&gt;
&lt;p&gt;这个小工具完全是为自己的工作需要而开发的，觉得有用 (至少对我自己来说)，就分享出来了。
我主要用 activerecord，所以没有提供其他 orm 框架的支持了。&lt;/p&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Sat, 31 Aug 2013 11:36:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/13772</link>
      <guid>https://ruby-china.org/topics/13772</guid>
    </item>
    <item>
      <title>聊聊 Ruby 中的 block, proc 和 lambda</title>
      <description>&lt;p&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;f1&lt;/span&gt;
  &lt;span class="k"&gt;yield&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;f2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&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;f3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f1&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;"f1"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;f2&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;"f2"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;f3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;proc&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;"f3"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;f3&lt;/span&gt;&lt;span class="p"&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="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"f3"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;f3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;lambda&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;"f3"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;若你用的是 ruby1.9 及以上的版本，还可以这样，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;f3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&amp;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;"f3"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面是 block, proc 和 lambda 的一些基本用法。&lt;/p&gt;
&lt;h3 id="block"&gt;block&lt;/h3&gt;
&lt;p&gt;先说说 block, ruby 中的 block 是方法的一个重要但非必要的组成部分，我们可以认为方法的完整定义类似于，&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;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&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="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意&amp;amp;p 不是参数，&amp;amp;p 类似于一种声明，当方法后面有 block 时，会将 block 捕捉起来存放到变量 p 中，如果方法后面没有 block，那么&amp;amp;p 什么也不干。&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;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&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;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;#=&amp;gt; 会抛出ArgumentError: wrong number of arguments (1 for 0)异常&lt;/span&gt;

&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;#=&amp;gt; 没有异常抛出&lt;/span&gt;

&lt;span class="n"&gt;f&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;"f"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 没有异常抛出&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从上面代码的运行结果可以知道&amp;amp;p 不是参数&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;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;#=&amp;gt; 没有异常抛出，输出1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以任何方法后面都可以挂载一个 block，如果你定义的方法想使用 block 做点事情，那么你需要使用 yield 关键字或者&amp;amp;p&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;f1&lt;/span&gt;
  &lt;span class="k"&gt;yield&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;f2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时 f1, f2 执行时后面必须挂一个 block，否则会抛出异常，f1 抛出 LocalJumpError: no block given (yield) 的异常，f2 抛出 NoMethodError: undefined method 'call' for nil:NilClass 的异常，ruby 提供了&lt;code&gt;block_given?&lt;/code&gt;方法来判断方法后面是否挂了 block，于是我们可以这样修改 f1 和 f2，&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;f1&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;f2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样的话，f1 和 f2 后面无论挂不挂 block 都不会抛异常了。
我们再来看看 f2 修改前抛出的 NoMethodError: undefined method 'call' for nil:NilClass 异常，这种说明当 f2 后面没有挂 block 的时候 p 是 nil，那么我们给 f2 挂个 block，再打印出 p，看看 p 究竟是什么，&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;f2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f2&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;# 输出Proc和类似&amp;lt;Proc:0x007fdc72829780@(irb):21&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这说明 p 是一个 Proc 实例对象，在 ruby 中，&amp;amp;还可以这么用，&lt;code&gt;[1,2] &amp;amp; [2,3]&lt;/code&gt; 或者 &lt;code&gt;puts true if 1 &amp;amp;&amp;amp; 1&lt;/code&gt; 或者在某个类中将它作为一个方法名。&lt;/p&gt;

&lt;p&gt;很多 ruby 老鸟会写类似下面的代码，&lt;/p&gt;

&lt;p&gt;&lt;code&gt;["1", "2", "3"].map(&amp;amp;:to_i)&lt;/code&gt;，其效果和&lt;code&gt;["1", "2", "3"].map {|i| i.to_i }&lt;/code&gt;一样，但简洁了许多，并且更加拉风。
这里的魔法在于符号&amp;amp;会触发:to_i 的 to_proc 方法，to_proc 执行后会返回一个 proc 实例，然后&amp;amp;会把这个 proc 实例转换成一个 block，我们需要要明白 map 方法后挂的是一个 block，而不是接收一个 proc 对象做为参数。&amp;amp;:to_i 是一个 block，block 不能独立存在，同时你也没有办法直接存储或传递它，必须把 block 挂在某个方法后面。&lt;/p&gt;

&lt;p&gt;:to_i 是 &lt;strong&gt;Symbol&lt;/strong&gt; 类的实例，&lt;strong&gt;Symbol&lt;/strong&gt; 中的 to_proc 方法的实现类似于，&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;Symbol&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_proc&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;obj&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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="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;同理我们可以给自己写的类定义 to_proc 方法，然后使用&amp;amp;耍下酷，比如，&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;AddBy&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;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;num&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;to_proc&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;obj&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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="vi"&gt;@num&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;add_by_9&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;AddBy&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="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&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;add_by_9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#输出 [10, 11, 12]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 ruby 中，block 有形，它有时候是这样&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|...|&lt;/span&gt;
  &lt;span class="o"&gt;...&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="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|...|&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者类似 &amp;amp;p, &amp;amp;:to_i, &amp;amp;add_by_9 之类，但是它无体，无体的意思就是说 block 无法单独存在，必须挂在方法后面，并且你没有办法直接把它存到变量里，也没有办法直接将它作为参数传递给方法，所以当你想存储，传递 block 时，你可以使用 proc 对象了，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt; &lt;span class="nb"&gt;p&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;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="nb"&gt;p&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;obj&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
 &lt;span class="nb"&gt;p&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="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
   &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
 &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="nb"&gt;p&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;obj&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
   &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&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;make_proc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_proc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_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;obj&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然我在开发中经常用到 block，但是我很少显式地去使用 Proc 或 proc 去实例化 block，比如我几乎没有写过这样的代码，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&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="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|...|&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nb"&gt;p&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="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;#然后在某个地方p.call(...)或者将p传递给某个方法，比如f(p)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在使用 block 时，我会忽略 proc 的存在，我将 proc 定位为一个幕后的工作者。我经常写类似下面的代码，&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;f&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="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;yield&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;def&lt;/span&gt; &lt;span class="nf"&gt;f&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&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;def&lt;/span&gt; &lt;span class="nf"&gt;f&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;instance_eval&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&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;def&lt;/span&gt; &lt;span class="nf"&gt;f&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;defime_method&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
  &lt;span class="o"&gt;...&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;instance_eval&lt;/span&gt; &lt;span class="nb"&gt;p&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;f&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="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;instance_eval&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;instance_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&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="nb"&gt;instance_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;yield&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&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="nb"&gt;instance_eval&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们经常在该挂 block 的时候，却把 proc 对象当参数传给方法了，或者不明白&amp;amp;p 就是 block 可以直接交给方法使用，我曾经也犯过这样的错误就是因为没有把 block 和 proc 正确的区分开来，&lt;strong&gt;&amp;amp;p 是 block, p 是 proc，不到万不得已的情况下不要显式地创建 proc&lt;/strong&gt;，每当我对 block 和 proc 之间的关系犯糊涂时，我就会念上几句。&lt;/p&gt;

&lt;p&gt;再来聊聊 yield 和&amp;amp;p，我们经常这样定义方法，&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;f&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;yield&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;def&lt;/span&gt; &lt;span class="nf"&gt;f&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&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="o"&gt;...&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;yield 和 call 后面都可以接参数，如果你是这样定义方法&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;f&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;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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="n"&gt;f&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;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;j&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="n"&gt;f&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;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;p.call(...) 的情况类似，也就是说 block 和 proc 都不检查参数 (其实通过 proc 方法创建的 proc 在 1.8 是严格检查参数的，但是在 1.9 及以上的版本是不检查参数的)，为什么 block 和 proc 不检查参数呢？其实这个很好理解，因为在实际应用中你可能需要在一个方法中多次调用 block 或者 proc 并且给的参数个数不一样，比如，&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;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;ff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="mi"&gt;1&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="mi"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f&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;a1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a3&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;a1&lt;/span&gt;   
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;a2&lt;/span&gt;
   &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;a3&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于方法后面只能挂一个 block，所以要实现上面的代码功能，就不能去严格检查参数了。&lt;/p&gt;

&lt;p&gt;转入正题，这两种方式效果差不多，都能很好地利用 block。使用 yield，看起来简洁，使用&amp;amp;p，看起来直观，并且你可以将&amp;amp;p 传给其他方法使用。
但是在 &lt;strong&gt;ruby1.8&lt;/strong&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;f1&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="nb"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"yield"&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;可以，&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;f2&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"p.call"&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;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;f1&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="p"&gt;{}&lt;/span&gt;  &lt;span class="c1"&gt;# 会抛异常&lt;/span&gt;
&lt;span class="n"&gt;f2&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="p"&gt;{}&lt;/span&gt;  &lt;span class="c1"&gt;# 正常运行&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然上面这种用法很少见，但是却被我碰到了，我经常写一些方法去请求外部的 api，有时这些外部的 api 不是特别稳定，时不时会遇到一些 bad respoense, timeout 错误，针对这些错误，应该立即重发报文重试，对于其他异常就直接抛异常。于是我写了一个 try 方法来满足这个需求，&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;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&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="p"&gt;},&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'#{e}'&lt;/span&gt;
    &lt;span class="n"&gt;tried_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;max_times&lt;/span&gt; &lt;span class="o"&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;:max_times&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="n"&gt;exceptions&lt;/span&gt; &lt;span class="o"&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;:on&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;
    &lt;span class="n"&gt;exceptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rescue_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
      begin
        p.call
      rescue &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&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="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; =&amp;gt; e
        Rails.logger.info("&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;发生异常&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ee&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;")
        if (tried_times += 1) &amp;lt; max_times
          Rails.logger.info("开始重试&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;--第&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tried_times&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;次重试")
          retry 
        end
        raise e
      end
&lt;/span&gt;&lt;span class="no"&gt;    EOF&lt;/span&gt;
    &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="n"&gt;rescue_text&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'某某api'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:max_times&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTPBadResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_url&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;最开始我用的是 yield，结果在 &lt;strong&gt;ree&lt;/strong&gt; 下执行 try 方法时会报错，后来改成使用&amp;amp;p 就通过了。&lt;/p&gt;

&lt;p&gt;通过试验发现在 &lt;strong&gt;ruby1.9&lt;/strong&gt; 及以上版本已经没有这种差异了。&lt;/p&gt;

&lt;p&gt;做个小结， &lt;strong&gt;block 和 proc 是两种不同的东西，block 有形无体，proc 可以将 block 实体化，可以把&amp;amp;p 看做一种运算，其中&amp;amp;触发 p 的 to_proc 方法，然后&amp;amp;会将 to_proc 方法返回的 proc 对象转换成 block&lt;/strong&gt; 。&lt;/p&gt;

&lt;p&gt;其中 proc 对象的 to_proc 方法返回自身。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal?&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_proc&lt;/span&gt;  &lt;span class="c1"&gt;# 返回true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="lambda"&gt;lambda&lt;/h3&gt;
&lt;p&gt;lambda 是匿名方法，lambda 和 proc 也是两种不同的东西，但是在 ruby 中 lambda 只能依附 proc 而存在，这点和 block 不同，block 并不依赖 proc。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;strong&gt;ruby1.8&lt;/strong&gt; 中输出的信息类似#&lt;a rel="nofollow" target="_blank"&gt;Proc:0x0000000000000000@irb):1&lt;/a&gt;(
在 &lt;strong&gt;ruby1.9&lt;/strong&gt; 及以上版本输出的信息类似#&lt;a rel="nofollow" target="_blank"&gt;Proc:0x007f85548109d0@irb):1lambda)&lt;/a&gt;(，注意 &lt;strong&gt;1.9&lt;/strong&gt; 及以上版本的输出多了 &lt;strong&gt;(lambda)&lt;/strong&gt;，从这里可以理解 ruby 的设计者们确实在有意的区分 lambda 和 proc，并不想把 lambda 和 proc 混在一起，如同 ruby 中没有叫 Block 的类，除非你自己定义一个，ruby 中也没有叫 Lambda 的类，于是将 lambda 对象化的活儿就交给了 Proc，于是令人头大的情况出现了，当你用 lambda 弄出了一个匿名方法时，发现它是一个 proc 对象，并且这个匿名方法能干的活，proc 对象都能做，于是我们这些码农不淡定了，&lt;code&gt;Proc.new {}&lt;/code&gt;这样可以，&lt;code&gt;proc {}&lt;/code&gt;这样也没有问题，&lt;code&gt;lambda {}&lt;/code&gt;这样做也不错，&lt;code&gt;-&amp;gt;{}&lt;/code&gt;这个还是能行，我平时吃个饭都为吃什么左右为难，现在一下子多出了四种差不多的方案来实现同一件事情，确实让人不好选择，特别是有些码农还有点小洁癖，如果在代码里一会儿看到&lt;code&gt;proc{}&lt;/code&gt;, 一会儿看到&lt;code&gt;lambda{}&lt;/code&gt;,这多不整洁啊，让人心情不畅快。在这里我们认定 lambda 或者 -&amp;gt; 弄出的就是一个匿名方法，记做 &lt;strong&gt;l&lt;/strong&gt;, 即使它披着 proc 的外衣，proc 或者 Proc.new 创建的就是一个 proc 对象，记做 &lt;strong&gt;p&lt;/strong&gt; 在 ruby 各个版本中，&lt;strong&gt;l&lt;/strong&gt; 和 &lt;strong&gt;p&lt;/strong&gt; 是有一些差别的。&lt;/p&gt;

&lt;p&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;f0&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nb"&gt;p&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&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;f1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&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;f2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&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;f3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;instance_eval&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
  &lt;span class="mi"&gt;1&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;A&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;f4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;p1&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="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;p2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&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;f5&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f5&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;f5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;f5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;f5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="//l.ruby-china.com/photo/891b1782bb0db9593b4efda25e2e948e.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;lambda 和 proc 之间的区别除了那个经常用做面试题目的经典的 return 之外，还有一个区别就是 lambda 不能完美的转换为 block(这点可以通过 f3 和 f4 执行的过程得证)，而 proc 可以完美的转换为 block，注意，我说的 lambda 指的是用 lambda 方法或者-&amp;gt;符号生成的 proc，当然和方法一样 lambda 是严格检查参数的，这个特点也和 proc 不一样。&lt;/p&gt;

&lt;p&gt;从上面的数据对比来看，在 1.8 版本，lambda 和 proc 方法生成的 proc 对象的行为是一致的，但在 1.9 以上和 jruby 的版本中，lambda 和 proc 的不同处增多，可以认为 ruby 的设计者们并不想把 lambda 和 proc 混同为一件事物。如我前面所讲，proc 的主要作用是对象化 block 和 lambda，并且 proc 在行为上更接近于 block。&lt;/p&gt;
&lt;h3 id="retrun的几个试验"&gt;retrun 的几个试验&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nb"&gt;p&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&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;f1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f0&lt;/span&gt; &lt;span class="c1"&gt;# 返回0&lt;/span&gt;
&lt;span class="n"&gt;f1&lt;/span&gt; &lt;span class="c1"&gt;# 返回1&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你能够理解 proc 在行为上更像 block，lambda 其实就是方法只不过是匿名的，那么你对上面的结果不会感到惊讶。&lt;/p&gt;

&lt;p&gt;如果把 f0,f1 做一些修改，就更容易理解上面的结果了。&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;f0&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="mi"&gt;1&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;f1&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;__f1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;__f1&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f0&lt;/span&gt; &lt;span class="c1"&gt;# 返回0&lt;/span&gt;
&lt;span class="n"&gt;f1&lt;/span&gt; &lt;span class="c1"&gt;# 返回1&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;return 只能在方法体里执行&lt;/strong&gt;，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="nb"&gt;p&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="c1"&gt;# 报LocalJumpError&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="c1"&gt;# 返回0&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;构造 p 的时候我没有使用 proc {return 0}，因为在 &lt;strong&gt;ruby1.8&lt;/strong&gt; 中，proc {return 0}的行为和 lambda 一样，比如在 &lt;strong&gt;ruby1.8&lt;/strong&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;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 返回1&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 返回1&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="c1"&gt;# 返回0&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="c1"&gt;# 返回0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;p&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 报LocalJumpError&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 返回1&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我感觉 proc 中的 return 能记住 proc 生成时其 block 的位置，然后无论 proc 在哪里调用，都会从 proc 生成时其 block 所在位置处开始 return，有下面的代码为证：&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;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&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;f2&lt;/span&gt;&lt;span class="p"&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&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;f3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 报LocalJumpError: unexpected return，from (irb):29:in `f2'&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 返回1&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我在 1.8, ree, jruby, 1.9, 2.0 各版本都测试过，结果一样。&lt;/p&gt;

&lt;p&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;f0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&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;f1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# LocalJumpErrorw&lt;/span&gt;
&lt;span class="n"&gt;f1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# LocalJumpErrorw&lt;/span&gt;

&lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;# LocalJumpError&lt;/span&gt;
&lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;   &lt;span class="c1"&gt;# 返回1&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;p&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;f3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f2&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f3&lt;/span&gt; &lt;span class="c1"&gt;# 返回0&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们重点看看 f3 中的 proc p，它虽然是从 f2 中生成返回的，但是 p 生成时其 block 是处在在 f3 的方法体内，这个类似于，&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;f3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nb"&gt;p&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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以执行 f3 时，没有异常抛出，返回 0。&lt;/p&gt;
&lt;h3 id="总结"&gt;总结&lt;/h3&gt;
&lt;p&gt;当你想写出类似于 f do ...; end 或者 f {...}的代码时，请直接使用 block，通过 yield, &amp;amp;p 就能达到目的，当你想使用 proc 时，其实此时绝大部分的情况是你实际想用 lambda，请直接使用 lambda{}或者-&amp;gt;{}就可以了，
尽量不要显示地使用 Proc.new{} 或者 proc{}去创建 proc。&lt;/p&gt;

&lt;p&gt;废话说了一大堆，其实我最想说的是：&lt;strong&gt;用 block，用 lambda，不要用 proc，让 proc 做好自己的幕后工作就好了&lt;/strong&gt;。&lt;/p&gt;</description>
      <author>kayakjiang</author>
      <pubDate>Mon, 22 Apr 2013 22:03:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/10414</link>
      <guid>https://ruby-china.org/topics/10414</guid>
    </item>
  </channel>
</rss>
