<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>vincenting (丁文森)</title>
    <link>https://ruby-china.org/vincenting</link>
    <description>In solitude, be a multitude to yourself.</description>
    <language>en-us</language>
    <item>
      <title>REST 风格接口中一对多 (Embedding Foreign Entities) 参数设计的疑问</title>
      <description>&lt;p&gt;最近正在进行我自己项目使用的 REST 风格的规范/实现指导，很大部分的参考了参考了 &lt;a href="http://postgrest.com/api/reading/" rel="nofollow" target="_blank" title=""&gt;PostgREST&lt;/a&gt; 的接口风格。但是遇到了 &lt;strong&gt;Embedding Foreign Entities&lt;/strong&gt; 查询参数的设计问题，示例情景如下：&lt;/p&gt;

&lt;p&gt;在用户自己买书的情景下，用户和书籍为一对多关系，现在需要在一个接口中获得满足一定条件的用户列表、同时获得每个用户最近刚买的三本书的需求。&lt;/p&gt;

&lt;p&gt;在一些实现里面，是通过 Hypermedia（超媒体）作为其解决方案，在查询 &lt;code&gt;GET:&lt;/code&gt; &lt;code&gt;https://api.example.com/v1/readers?order=created_at.desc&amp;amp;status=eq.online&lt;/code&gt; 这样的接口时，会返回如下内容：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&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;meta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;href&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;https://api.vincenting.com/v1/readers/a0f0e-ds012e-aff00f&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;id&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;a0f0e-ds012e-aff00f&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;name&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;vincent&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;created_at&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;2015-11-01T17:02:23.212Z&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;books&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;meta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;href&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;https://api.vincenting.com/v1/readers/a0f0e-ds012e-aff00f/books&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样使用起来问题不大，但是当 HTTP2 未普及的今天，带来的问题就是多次请求带来的时间问题，以及服务器需要多次进行解析、权限控制等等操作的性能损耗。&lt;/p&gt;

&lt;p&gt;于是在 &lt;a href="http://postgrest.com/api/reading/" rel="nofollow" target="_blank" title=""&gt;PostgREST&lt;/a&gt; 后面的版本里面加入了：&lt;a href="http://postgrest.com/api/reading/#embedding-foreign-entities" rel="nofollow" target="_blank" title=""&gt;Embedding Foreign Entities&lt;/a&gt; 的内容，支持这样的情况下的内容返回。基本思路是对 &lt;code&gt;select&lt;/code&gt; 参数进行拓展，显示的申明我需要返回例如 books 里面的所有字段，于是请求的地址变成了 &lt;code&gt;https://api.example.com/v1/readers?order=created_at.desc&amp;amp;status=eq.online&amp;amp;select=*,books{*}&lt;/code&gt;，从而服务器会将整个满足条件的内容返回。&lt;/p&gt;

&lt;p&gt;于是，可能是 &lt;code&gt;PostgREST&lt;/code&gt; 目前不支持或者目前对 &lt;code&gt;PostgREST&lt;/code&gt; 的理解有限，对于这种列表（虽然是内嵌的列表），至少需要满足：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;条件查询（设计的不好的话很难区分到底是给哪个资源的返回列表进行约束）；&lt;/li&gt;
&lt;li&gt;分页支持（目前分页支持是尽可能的遵循 &lt;a href="https://tools.ietf.org/html/rfc7233" rel="nofollow" target="_blank" title=""&gt;RFC 7233&lt;/a&gt; - Hypertext Transfer Protocol (HTTP/1.1): Range Requests。但是该规范规定客户端 &lt;a href="https://tools.ietf.org/html/rfc7233#section-6.1" rel="nofollow" target="_blank" title=""&gt;不能发起多个资源的 Range 请求&lt;/a&gt;，使得很难在 Range 请求头中规定多个资源的分页需求；&lt;/li&gt;
&lt;li&gt;排序需求（目前是在 url 的 查询参数中以关键字 order 来设计的，问题同一）。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;p&gt;最终目前的疑惑是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;是否有需要考虑这种情况下的查询参数的设计，或者上文中的观点部分内容自身就是错误的&lt;/li&gt;
&lt;li&gt;是否有比较好的相关设计范例指的借鉴&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注：目前参考的其他文献：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="http://www.slideshare.net/stormpath/rest-jsonapis" rel="nofollow" target="_blank" title=""&gt;Design Beautiful REST + JSON APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api" rel="nofollow" target="_blank" title=""&gt;Best Practices for Designing a Pragmatic RESTful API&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>vincenting</author>
      <pubDate>Sat, 20 Feb 2016 19:25:39 +0800</pubDate>
      <link>https://ruby-china.org/topics/29043</link>
      <guid>https://ruby-china.org/topics/29043</guid>
    </item>
    <item>
      <title>设计一个足够自动生成 RESTful 接口的 Model</title>
      <description>&lt;p&gt;最近在整理一些 rest 设计的文档，参考了以下文档：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/begriffs/postgrest/wiki/Routing" rel="nofollow" target="_blank" title=""&gt;Postgreset - Routing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.slideshare.net/stormpath/rest-jsonapis" rel="nofollow" target="_blank" title=""&gt;Design Beautiful REST + JSON APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api" rel="nofollow" target="_blank" title=""&gt;Best Practices for Designing a Pragmatic RESTful API&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;然后抱着造轮子的态度，前段时间实现了一个类似 postgreset 的简易版原型，最近准备在 Sequel 的 Model 基础上进行拓展，目前得到了如下的一个使用 demo：&lt;/p&gt;

&lt;p&gt;models/salesman.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;Salesman&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:crm_resources_salesmen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Restfy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;

 &lt;span class="c1"&gt;# sequel 中的定义 model 关系的方法&lt;/span&gt;
  &lt;span class="n"&gt;one_to_many&lt;/span&gt; &lt;span class="ss"&gt;:logs&lt;/span&gt;
  &lt;span class="n"&gt;one_to_many&lt;/span&gt; &lt;span class="ss"&gt;:tasks&lt;/span&gt;
  &lt;span class="n"&gt;one_to_many&lt;/span&gt; &lt;span class="ss"&gt;:houses&lt;/span&gt;
  &lt;span class="n"&gt;one_to_many&lt;/span&gt; &lt;span class="ss"&gt;:contacts&lt;/span&gt;
 &lt;span class="c1"&gt;# 拓展 created_at 和 updated_at&lt;/span&gt;
  &lt;span class="n"&gt;use_timestamp&lt;/span&gt; &lt;span class="ss"&gt;:createdTimestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:updatedTimestamp&lt;/span&gt;

 &lt;span class="c1"&gt;# JSON 字段支持，方便部分非重要字段拓展而不需要每次都修改数据库&lt;/span&gt;
  &lt;span class="n"&gt;dynamic_fields&lt;/span&gt; &lt;span class="ss"&gt;:details&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:sex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &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;default: &lt;/span&gt;&lt;span class="s1"&gt;'unknown'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;enums: &lt;/span&gt;&lt;span class="sx"&gt;%w(male female unknown)&lt;/span&gt;

    &lt;span class="n"&gt;validate_for_sex&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;sex&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="c1"&gt;# 每个字段的验证，这里考虑引入 helper 机制&lt;/span&gt;
  &lt;span class="n"&gt;validate_for_name&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;name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Error&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;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&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;resources/salesman.rb&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="no"&gt;Salesman&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# 允许哪些操作，包括 单个查询 :get 单个更新 :update 条件查询 :query 创建 :create 批量更新 :patch&lt;/span&gt;
  &lt;span class="n"&gt;allow_method&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;:get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;
 &lt;span class="c1"&gt;# 每个或者多个操作允许哪些字段操作、不允许哪些， 默认 create 是不允许操作 :id 字段的&lt;/span&gt;
  &lt;span class="n"&gt;exclude_fields_for&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;:query&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;:tenantId&lt;/span&gt;
  &lt;span class="n"&gt;exclude_fields_for_update&lt;/span&gt; &lt;span class="ss"&gt;:tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:groupId&lt;/span&gt;
  &lt;span class="n"&gt;include_fields_for_create&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;

  &lt;span class="c1"&gt;# 对应的请求操作的权限验证，数据库中的字段满足的条件&lt;/span&gt;
  &lt;span class="n"&gt;authenticate_for&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;:create&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;req&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_salesman_id&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;authenticate_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:query&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="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;tenantId: &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_tenant_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# 创建时从请求中默认写入哪些数据&lt;/span&gt;
  &lt;span class="n"&gt;do_before_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tenantId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_tenant_id&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;目前还在闷头 coding = =。。。为什么不直接用 postgrest：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;del&gt;&lt;code&gt;Route&lt;/code&gt; 不支持简单的联表查询，会给客户端造成过多的请求负担；&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Haskell&lt;/code&gt; 开发，遇到问题摊手，而且不太易于拓展新的 uri 参数，或者自定义返回格式。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;目前想到的就是这么多点，不知道有没有遗漏目前。&lt;/p&gt;

&lt;p&gt;updated: 在 &lt;a href="/jasl" class="user-mention" title="@jasl"&gt;&lt;i&gt;@&lt;/i&gt;jasl&lt;/a&gt; 的建议/提醒下，将表现层的代码单独提取。&lt;/p&gt;</description>
      <author>vincenting</author>
      <pubDate>Sun, 15 Nov 2015 10:47:31 +0800</pubDate>
      <link>https://ruby-china.org/topics/28063</link>
      <guid>https://ruby-china.org/topics/28063</guid>
    </item>
    <item>
      <title>使用 Nginx 优化面向侧面的架构</title>
      <description>&lt;p&gt;&lt;strong&gt;面向侧面的程序设计&lt;/strong&gt;（aspect-oriented programming，AOP），通过将解决特定领域问题的代码从业务逻辑中独立出来，从而提高代码的可维护性。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;从主关注点中分离出横切关注点是面向侧面的程序设计的&lt;strong&gt;核心概念&lt;/strong&gt;。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来，业务逻辑的代码中不再含有针对特定领域问题代码的调用，业务逻辑同特定领域问题的关系通过侧面来封装、维护，这样原本分散在在整个应用程序中的变动就可以很好的管理起来。 - &lt;a href="https://zh.wikipedia.org/wiki/%E9%9D%A2%E5%90%91%E4%BE%A7%E9%9D%A2%E7%9A%84%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1" rel="nofollow" target="_blank" title=""&gt;维基百科&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr&gt;

&lt;p&gt;示例是根据最近正在负责的 APP 后端项目简化版，需求简单说如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;APP 端会对所有请求进行加密，服务器端要对加密结果进行校验，确保正确以及未篡改；&lt;/li&gt;
&lt;li&gt;通过手机号来登录，采用基本的 token 机制验证登录；&lt;/li&gt;
&lt;li&gt;有企业、小组以及员工的层级关系，后期必须考虑根据公司来分表/集群；&lt;/li&gt;
&lt;li&gt;提供涉及到权限的 REST 风格的接口（某种程度上类似 &lt;a href="https://github.com/begriffs/postgrest" rel="nofollow" target="_blank" title=""&gt;Postgrest&lt;/a&gt;，但是进行了拓展，后面会有专门文章介绍）&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="Version 1st."&gt;Version 1st.&lt;/h2&gt;
&lt;p&gt;思路：首先整个目前项目的主关注点 (core concern) 是 REST 风格的资源服务器 —— 即通过约定俗成的风格来对应具体的数据/资源操作。在这个功能外，需要完成的其他关注点包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;所有请求加密校验&lt;/li&gt;
&lt;li&gt;登录验证&lt;/li&gt;
&lt;li&gt;资源的权限管理以及获取&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;于是，在 Sinatra 中，可以通过 &lt;a href="http://www.sinatrarb.com/extensions.html" rel="nofollow" target="_blank" title=""&gt;extensions&lt;/a&gt; 的方式将请求加密校验完成，配合 before 来进行统一处理：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sinatra/base'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Sinatra&lt;/span&gt;

  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RequestHeadersVerify&lt;/span&gt;

    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Helpers&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;headers_valid?&lt;/span&gt;
        &lt;span class="c1"&gt;# 此处省略真实业务代码&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;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;registered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;helpers&lt;/span&gt; &lt;span class="no"&gt;RequestHeadersVerify&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;

      &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;headers_valid?&lt;/span&gt;
          &lt;span class="n"&gt;halt&lt;/span&gt; &lt;span class="mi"&gt;400&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="no"&gt;ResponseErrror&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InvalidHeadersError&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;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="n"&gt;register&lt;/span&gt; &lt;span class="no"&gt;RequestHeadersVerify&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终采用"中间件"的方式，在请求的最前面一层（横切关注点 crosscutting concerns）将非法请求进行拦截。&lt;/p&gt;

&lt;p&gt;于是紧接着第二个流程，验证用户是否登录，与获取当前联系人所在的公司、小组、以及其管理的小组信息一样，这里最快速/方便的做法就是通过 helpers 来实现：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sinatra/base'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Sinatra&lt;/span&gt; 
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;UserSessionHelpers&lt;/span&gt;
    &lt;span class="no"&gt;HTTP_USER_TOKEN_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'HTTP_AUTH_TOKEN'&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;current_user&lt;/span&gt;
      &lt;span class="vi"&gt;@current_user&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="p"&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;first&lt;/span&gt; &lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;HTTP_USER_TOKEN_KEY&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;halt&lt;/span&gt; &lt;span class="mi"&gt;400&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="no"&gt;ResponseErrror&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InvalidTokenError&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;unless&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
        &lt;span class="n"&gt;user&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;module&lt;/span&gt; &lt;span class="nn"&gt;OrganizationHelpers&lt;/span&gt;
    &lt;span class="c1"&gt;# 这里省略掉相关 helpers 代码&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;helpers&lt;/span&gt; &lt;span class="no"&gt;UserSessionHelpers&lt;/span&gt;
  &lt;span class="n"&gt;helpers&lt;/span&gt; &lt;span class="no"&gt;OrganizationHelpers&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终在 REST 相关的构建代码中，就不需要去考虑用户请求加密的内容，也不需要去考虑用户是否登录（因为如果需要使用到用户信息但是用户没有登录，会直接抛出错误返回）。只需要按照约定的设计风格，把请求的内容在校验了内容和权限后，转成对应的数据库操作，最终再按照约定的内容返回。&lt;/p&gt;
&lt;h2 id="Version 2nd."&gt;Version 2nd.&lt;/h2&gt;
&lt;p&gt;第一版已经尽可能的考虑到 &lt;strong&gt;解决特定领域问题的代码从业务逻辑中独立出来&lt;/strong&gt;。但是现实开发里面经常会涉及到多人开发、跨语言合作、更快速的迭代等等的问题，最终需要把他们拆成独立的低耦合度的 Server。于是随之而来的是如何在服务间进行通讯/共享数据。&lt;/p&gt;

&lt;p&gt;这里的方案选择通常会根据实际业务以及难易程度来权衡，例如最快捷的 webServer 的方式内部通信，稍微复杂点的基于 TCP 的 RPC 通信方案（例如 thrift），或者某些特殊的情景，例如是生产者/消费者关系的话，则可能通过 MQ 来进行通信。&lt;strong&gt;最终我们采用的是通过 &lt;a href="https://github.com/openresty/lua-nginx-module" rel="nofollow" target="_blank" title=""&gt;Nginx 的 lua&lt;/a&gt; 模块来将 server 以面向侧面的思路耦合&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;首先，Nginx 的 Lua 模块可以做什么？如果可以，单纯 nginx 和 lua 就可以完成完整的 web 服务。可以连接 redis、memcache、postgresql 等等，同时可以取得请求的所有内容，可以设置返回的头部、正文。配合 lua 的对数据处理能力，基本功能都可以实现。同时 nginx 的 lua 模块整体都是异步，所以性能也相对较好。当然也可以通过 lua 脚本来控制权限，如果验证通过则继续下面的操作，例如是 proxy_pass 代理，简单的示例如下文：&lt;/p&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/foo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;access_by_lua_block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;--&lt;/span&gt; &lt;span class="s"&gt;check&lt;/span&gt; &lt;span class="s"&gt;the&lt;/span&gt; &lt;span class="s"&gt;client&lt;/span&gt; &lt;span class="s"&gt;IP&lt;/span&gt; &lt;span class="s"&gt;address&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="s"&gt;in&lt;/span&gt; &lt;span class="s"&gt;our&lt;/span&gt; &lt;span class="s"&gt;black&lt;/span&gt; &lt;span class="s"&gt;list&lt;/span&gt;
        &lt;span class="s"&gt;if&lt;/span&gt; &lt;span class="s"&gt;ngx.var.remote_addr&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"132.5.72.3"&lt;/span&gt; &lt;span class="s"&gt;then&lt;/span&gt;
            &lt;span class="s"&gt;ngx.exit(ngx.HTTP_FORBIDDEN)&lt;/span&gt;
        &lt;span class="s"&gt;end&lt;/span&gt;

        &lt;span class="s"&gt;--&lt;/span&gt; &lt;span class="s"&gt;check&lt;/span&gt; &lt;span class="s"&gt;if&lt;/span&gt; &lt;span class="s"&gt;the&lt;/span&gt; &lt;span class="s"&gt;URI&lt;/span&gt; &lt;span class="s"&gt;contains&lt;/span&gt; &lt;span class="s"&gt;bad&lt;/span&gt; &lt;span class="s"&gt;words&lt;/span&gt;
        &lt;span class="s"&gt;if&lt;/span&gt; &lt;span class="s"&gt;ngx.var.uri&lt;/span&gt; &lt;span class="s"&gt;and&lt;/span&gt;
            &lt;span class="s"&gt;string.match(ngx.var.request_body,&lt;/span&gt; &lt;span class="s"&gt;"evil")&lt;/span&gt;
        &lt;span class="s"&gt;then&lt;/span&gt;
            &lt;span class="s"&gt;return&lt;/span&gt; &lt;span class="s"&gt;ngx.redirect("/terms_of_use.html")&lt;/span&gt;
        &lt;span class="s"&gt;end&lt;/span&gt;

        &lt;span class="s"&gt;--&lt;/span&gt; &lt;span class="s"&gt;tests&lt;/span&gt; &lt;span class="s"&gt;passed&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="s"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://blah.blah.com&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;code&gt;proxy_pass&lt;/code&gt;, 和 &lt;code&gt;ngx.location.capture&lt;/code&gt;，基本代码如下：&lt;/p&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 禁止任何以下划线开始的请求地址&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;string.sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uri&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;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;cjson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"cjson"&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;custom_header_prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"V-"&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;request_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_uri_args&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="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;request_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;request_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;request_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"HTTP_"&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_method&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_headers&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;string.upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string.sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&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="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;crm_header_prefix&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&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;function&lt;/span&gt; &lt;span class="nf"&gt;res_with_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt;
  &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;request_to_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;-- 发起请求至其他地址并取得结果&lt;/span&gt;
  &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;request_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request_body&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;request_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;json_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cjson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;-- 解析返回内容&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;json_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;res_with_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;for&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;value&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;json_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
     &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_header_prefix&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="nb"&gt;string.upper&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;value&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="kc"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的代码主完成了清理用户恶意提交的请求头，以及 &lt;code&gt;request_to_server&lt;/code&gt; 的代码，实现了将原请求内容转发给另一个接口并获得请求后的内容。得到请求结果后，验证请求的参数。&lt;/p&gt;

&lt;p&gt;同时在 nginx 里面通过 stream 和 proxy_pass 的方式来配置多个内部地址：&lt;/p&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;authentication-server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;192.168.21.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6011&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;192.168.21.2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6011&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/_authentication&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_redirect&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt;      &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kn"&gt;rewrite&lt;/span&gt;  &lt;span class="s"&gt;^/_authentication/(.*)&lt;/span&gt;  &lt;span class="n"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="s"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://authentication-server&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是，将两个结合起来，就可以实现&lt;strong&gt;通过 lua 脚本把原请求的所有参数，包括头部、正文、请求地址、请求方法都带过去，请求另一个地址（和 proxy_pass 类似），并且可以得到最终返回的结果处理&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;拥有这个能力后，便是本文的重点了：在 &lt;code&gt;Sinatra&lt;/code&gt; 的第一版本中，最终都是 ruby 代码不断调用方法，来完成整个请求的流程。那如果我们把整个流程的打通交给 Nginx 的话该如何实现呢？&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;当一个请求进入后，通过 &lt;code&gt;request_to_server&lt;/code&gt; 的能力，把请求依次转发给负责 横切关注点 的服务，例如用户请求校验以及登录校验服务、用户的组织架构服务，最终再去调用主关注点，即本文中的资源服务器；&lt;/li&gt;
&lt;li&gt;每次请求完后，根据前一个流程的返回值决定是否进入下一个流程，例如示例中的 lua 脚本是通过返回的 json 里面的 next 参数来决定是否继续往下走。如果没有这个参数则直接返回当前服务的返回值不再继续请求下去；&lt;/li&gt;
&lt;li&gt;如果出现了 &lt;code&gt;next: true&lt;/code&gt; 这个关键字，则将返回值中的其他内容以请求头的形式传递给下一个服务，且每个服务都会完全信赖这些请求头（所以请求刚进来的时候需要做一些请求头和请求地址处理）。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果到这里都没有太大问题，你应该可以理解我的意图了。即 Nginx 通过 Lua 脚本来依次请求 横切关注点服务器，如果一路顺畅（每次都有 &lt;code&gt;next: true&lt;/code&gt;），最终会把携带有 横切关注点服务返回的内容的 headers 带给主关注点服务。&lt;/p&gt;

&lt;p&gt;于是，在本需求里面，为了保证可拓展性和低耦合性，最终分为了三个服务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;负责请求加密鉴权，用户登录、密码修改的用户验证服务&lt;/li&gt;
&lt;li&gt;负责管理企业结构、获取用户权限的组织架构服务&lt;/li&gt;
&lt;li&gt;负责具体的 REST 请求处理的资源服务&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;当一个用户登录的请求过来，因为密码错误或者加密鉴权失败，会在用户验证服务就直接返回错误。因为返回内容中没有 &lt;code&gt;next: true&lt;/code&gt; 字段，所以直接返回结果；&lt;/li&gt;
&lt;li&gt;当一个用户发起了一个发送短信验证码的服务，这个只是用户验证服务的职能，没有必要继续向下走，于是返回了一个没有 &lt;code&gt;next: true&lt;/code&gt; 字段的返回值，于是 Nginx 直接返回结果；&lt;/li&gt;
&lt;li&gt;当用户登录的时候，虽然通过了用户验证服务的校验，但是该服务无法获取更多的用户信息，于是把该请求继续传递到组织架构服务，组织架构服务在请求头中拿到了手机号信息，于是直接返回了该手机号所对应的详细信息；&lt;/li&gt;
&lt;li&gt;现在发起了一个资源操作的请求，因为用户验证服务无法识别，所以只返回了手机号给 nginx，nginx 继续请求组织架构服务，因为组织架构服务也不能处理，所以继续返回了详细的个人信息给 nginx，nginx 最终拿到这些信息，都通过头部请求了资源服务，然后因为这里是主关注点，也是流程里面最后一个节点，所以通过 &lt;code&gt;proxy_pass&lt;/code&gt; 给了资源服务。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;p&gt;最终，这样做的优势：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;利用 Nginx 异步的优势来弥补 ruby 服务先天性 IO 处理的不足；&lt;/li&gt;
&lt;li&gt;目前只实现了第一条线，即从用户验证 -&amp;gt; 组织信息 -&amp;gt; 资源服务器的顺序，后面如果有需要，可以随时实现其他顺序，而只需要按照在请求头里面加上相应的参数即可，减低耦合性；&lt;/li&gt;
&lt;li&gt;三个模块都有各自的业务和特点，可以针对模块去设计缓存方案，而且可以分模块去设计集群方案；&lt;/li&gt;
&lt;li&gt;对于开发者而言，更容易完成单个服务的测试用例，而不需要过多在开发过程中关注联调。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;原文：&lt;a href="https://github.com/vincenting/note/issues/5" rel="nofollow" target="_blank" title=""&gt;使用 Nginx 优化面向侧面的架构&lt;/a&gt;&lt;/p&gt;</description>
      <author>vincenting</author>
      <pubDate>Wed, 11 Nov 2015 13:26:04 +0800</pubDate>
      <link>https://ruby-china.org/topics/28017</link>
      <guid>https://ruby-china.org/topics/28017</guid>
    </item>
    <item>
      <title>用 Ruby 快速开发一个静态服务全过程</title>
      <description>&lt;p&gt;最新更新：添加最终演示的 sinatra 项目源码 &lt;a href="https://github.com/vincenting/sinatra-image-server-example" rel="nofollow" target="_blank"&gt;https://github.com/vincenting/sinatra-image-server-example&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;由于各种原因，需要给快速客户端提供一个图片上传、生成缩略图、以及获取的接口。使用到的技术关键字：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="http://www.sinatrarb.com/" rel="nofollow" target="_blank" title=""&gt;Sinatra&lt;/a&gt; - 基本的 web server 服务；&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://sidekiq.org/" rel="nofollow" target="_blank" title=""&gt;Sidekiq&lt;/a&gt; - 提供异步任务执行支持；&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/minimagick/minimagick" rel="nofollow" target="_blank" title=""&gt;MiniMagick&lt;/a&gt; - 提供图片压缩切割支持；&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://nginx.com/" rel="nofollow" target="_blank" title=""&gt;Nginx&lt;/a&gt; - 使用 rewrite 和 try_files 实现缩略图访问支持。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最终实现的目标：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;支持客户端多图片上传；&lt;/li&gt;
&lt;li&gt;URL 中支持参数 resize，后台通过该参数生成对应的图片缩略图，并且可以通过 &lt;a href="http://example.com/static/image.jpg@100x100" rel="nofollow" target="_blank"&gt;http://example.com/static/image.jpg@100x100&lt;/a&gt; 的方式来访问；&lt;/li&gt;
&lt;li&gt;如果访问是缩略图未生产，则访问原图。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;p&gt;前方高能，代码块成堆！&lt;/p&gt;
&lt;h3 id="Sinatra 简单的多图上传示例"&gt;Sinatra 简单的多图上传示例&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;begin&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;:images&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;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;filename&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extname&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="ss"&gt;:filename&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;filepath&lt;/span&gt; &lt;span class="o"&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;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'STATIC_ROOT'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cp&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="ss"&gt;:tempfile&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&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="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'STATIC_URL'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;filename&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;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="n"&gt;halt&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="ss"&gt;msg: &lt;/span&gt;&lt;span class="s1"&gt;'upload success.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;urls: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后使用 minitest 来进行测试，或者写一个简单的页面来测试。&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;&lt;/span&gt;Multi file upload&lt;span class="nt"&gt;&amp;lt;/title&amp;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="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;enctype=&lt;/span&gt;&lt;span class="s"&gt;"multipart/form-data"&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;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"images[]"&lt;/span&gt; &lt;span class="na"&gt;multiple&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;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&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;h3 id="使用 Sidekiq 和 MiniMagick 完成缩略图制作"&gt;使用 Sidekiq 和 MiniMagick 完成缩略图制作&lt;/h3&gt;
&lt;p&gt;首先需要实现一个简单的缩略图 Worker&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;ImageResizeWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image_info&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="ss"&gt;:dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:extname&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;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;image_info&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="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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;image_path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;image_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:basename&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt; &lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:extname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;sizes&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;size&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;output_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&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="n"&gt;image_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:dirname&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="n"&gt;image_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:basename&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;image_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:extname&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="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;resize_image&lt;/span&gt; &lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_path&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;resize_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MiniMagick&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt; &lt;span class="n"&gt;image_path&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt; &lt;span class="n"&gt;output_size&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="n"&gt;output_path&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;使用测试用例测试 worker，测试用例基本如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'spec_helper'&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;ImageResizeWorker&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@test_image_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&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="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'test_image_files'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wensen.jpg'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ImageResizeWorker&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;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'should resize images without any error'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@worker.perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@test_image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;%w(100x100 200x200)&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;it&lt;/span&gt; &lt;span class="s1"&gt;'should has the same extname'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@worker.perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@test_image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;%w(100x100 200x200)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exist?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&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="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@test_image_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"wensen@100x100.jpg"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exist?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&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="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@test_image_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"wensen@200x200.jpg"&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;it&lt;/span&gt; &lt;span class="s1"&gt;'should assert_equal name and size'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

    &lt;span class="vi"&gt;@worker.perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@test_image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;%w(40x50 100x80 120x120)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;File&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="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'test_image_files'&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="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;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MiniMagick&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Image&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="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&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="s1"&gt;'.*'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;
      &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
      &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;end&lt;/span&gt;

  &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;File&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="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'test_image_files'&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="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;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;f&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;当测试到大小比例和原图不等的时候突然发现报错了，发现 resize 没有自动切割（或许是有参数我不知道）。于是就要继续拓展 resize_image 方法来支持切割。此处略去一大块代码。&lt;/p&gt;

&lt;p&gt;修改 webserver 的代码，在图片拷贝到上传文件夹后，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RACK_ENV'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt;
  &lt;span class="no"&gt;ImageResizeWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;,&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;:resize&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后就是测试测试，这个时候基本上一个支持多图片上传，并且支持后台图片缩略图的 server 就完成了。&lt;/p&gt;
&lt;h3 id="Nginx 优化图片访问方式"&gt;Nginx 优化图片访问方式&lt;/h3&gt;
&lt;p&gt;在下面的讨论内容之前，先贴一个地址 &lt;a href="https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/" rel="nofollow" target="_blank" title=""&gt;IF IS EVAL&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;需求：访问 &lt;code&gt;/static/url.jpg@100x100&lt;/code&gt; 可以访问到图片 /opt/static/url@100x100.jpg 并返回，如果这个时候缩略图还没生成，就先返回原图。&lt;/p&gt;

&lt;p&gt;先不管第二个需求，写出来的基本上就是如下的配置：&lt;/p&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/opt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/static/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="kn"&gt;rewrite&lt;/span&gt;  &lt;span class="s"&gt;^/static/(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;w+)&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;w+)@(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;w+)&lt;/span&gt;  &lt;span class="n"&gt;/static/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt; &lt;span class="s"&gt;break&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认完成图片的访问地址与硬盘地址对应，如果满足 rewrite 正则则执行 rewrite，即需求中的第一点。现在需要完成第二条需求，即如果默认的访问失败的话，就去访问原图，于是这里需要使用到 &lt;a href="http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files" rel="nofollow" target="_blank" title=""&gt;try_files&lt;/a&gt;  - 按照特定顺序来尝试 file 直到第一个成功。&lt;/p&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/opt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/static/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="s"&gt;.origin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="kn"&gt;rewrite&lt;/span&gt;  &lt;span class="s"&gt;^/static/(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;w+)&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;w+)@(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;w+)&lt;/span&gt;  &lt;span class="n"&gt;/static/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt; &lt;span class="s"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.origin$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;rewrite&lt;/span&gt;  &lt;span class="s"&gt;^/static/(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;w+)&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;w+).(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;w+).origin&lt;/span&gt;$  &lt;span class="n"&gt;/static/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt; &lt;span class="s"&gt;break&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是最终就变成了如上的配置文件（虽然一路并不顺利）。&lt;strong&gt;在同时使用 try_files 和  rewrite 的时候，发现执行顺序是先 尝试第一个，即 $uri，然后执行 /static/ 中的 rewrite，此时的 $uri 其实已经变成了 rewrite 后的内容，所以当 /static/ 没有找到文件换为 $uri.origin 这里的 $uri 已经不是原来的请求路径，而是 rewrite 后的&lt;/strong&gt;。所以对于特别是新人，在写 nginx 配置的时候要记得看 error 输出，如果没有的话记得开启，里面会给出很多有价值的内容。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;吐槽完毕，最终在 sinatra 中通过 before 的方法加上基本的鉴权项目就上了测试环境。&lt;/p&gt;</description>
      <author>vincenting</author>
      <pubDate>Thu, 05 Nov 2015 10:17:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/27956</link>
      <guid>https://ruby-china.org/topics/27956</guid>
    </item>
    <item>
      <title>[简信] 智能邮件投递测试版</title>
      <description>&lt;h3 id="当前完成情况"&gt;当前完成情况&lt;/h3&gt;
&lt;p&gt;目前已经可以供发送量小中型用户使用，基本的功能已经测试通过。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;HTTP 协议的邮件投递，包括队列等待式投递、立即投递，立即投递又可选阻塞式投递（请求直接返回投递结果，默认投递后返回邮件 ID，并通过回调告知投递结果）&lt;/li&gt;
&lt;li&gt;针对收件域名的发送频率控制&lt;/li&gt;
&lt;li&gt;发送异常的根据关键字的默认处理以及可人工针对发送日志指定处理方法&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="下面要完成的功能"&gt;下面要完成的功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;SMTP 协议的邮件投递&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;h3 id="传送门："&gt;传送门：&lt;/h3&gt;
&lt;p&gt;项目主页： &lt;a href="https://github.com/jianxinio/postman" rel="nofollow" target="_blank"&gt;https://github.com/jianxinio/postman&lt;/a&gt;
测试版本安装文档： &lt;a href="https://github.com/jianxinio/postman/blob/master/docs/install.md" rel="nofollow" target="_blank"&gt;https://github.com/jianxinio/postman/blob/master/docs/install.md&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="技术关键字"&gt;技术关键字&lt;/h3&gt;
&lt;p&gt;ruby[padrino], golang, nodejs, tls, redis, mysql, semantic-ui&lt;/p&gt;
&lt;h3 id="非技术的内容"&gt;非技术的内容&lt;/h3&gt;
&lt;p&gt;这个项目由于目前由一个人开发，所以整体（性能以及安全、可靠性）很局限于个人认知水平。很希望有更多的人参与，给邮件发送一个更好的选择。
如果你了解上面的技术关键字中的一点或者几点，欢迎先了解下大概的代码，提出代码中的缺陷/提交缺陷的改进代码。目前整体的开发文档会在后面快速跟进（由于公司新项目，可能下面速度会稍慢）。
还有我蹩脚的英文，后面依旧是希望同时提供中文/英文两个版本的页面/文档，也欢迎加入。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/6dab40590a911289429fa00a06e8b406.png" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>vincenting</author>
      <pubDate>Wed, 29 Oct 2014 19:21:20 +0800</pubDate>
      <link>https://ruby-china.org/topics/22340</link>
      <guid>https://ruby-china.org/topics/22340</guid>
    </item>
  </channel>
</rss>
