<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>doitian (ian)</title>
    <link>https://ruby-china.org/doitian</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Rails Cookie 如何解密</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;CC-BY-SA 原文：&lt;a href="http://blog.iany.me/zh/2017/09/rails-cookie-encryption/" rel="nofollow" target="_blank"&gt;http://blog.iany.me/zh/2017/09/rails-cookie-encryption/&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如果想在已有的 Rails app 上使用其它语言加些 API，同时能直接使用 Rails 的登陆信息，最简单的就是用 Nginx 等代理将不同的服务映射到相同的域名下，其它的 App 解密 Cookie 获得登陆信息。&lt;/p&gt;

&lt;p&gt;本文以 Ruby 代码为例说明 Rails 的 Cookie 是如何加密，然后以 Go 为例说明如何解密的。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/931ecdae-5601-4395-bc2c-d1e49b2ba8e6.png!large" title="" alt="rails-cookie-encryption"&gt;&lt;/p&gt;

&lt;p&gt;Rails 的实现可以参考 &lt;a href="https://github.com/rails/rails/blob/0a6f69a5debf89748da3a43747c61d201095997e/activesupport/lib/active_support/message_encryptor.rb" rel="nofollow" target="_blank" title=""&gt;ActiveSupport::MessageEncryptor&lt;/a&gt;，&lt;a href="https://github.com/rails/rails/blob/0a6f69a5debf89748da3a43747c61d201095997e/activesupport/lib/active_support/message_verifier.rb" rel="nofollow" target="_blank" title=""&gt;ActiveSupport::MessageVerifier&lt;/a&gt; 和相应的单元测试。&lt;/p&gt;
&lt;h2 id="加密"&gt;加密&lt;/h2&gt;
&lt;p&gt;上图说明了原始的 Session 对象 &lt;em&gt;Session Data&lt;/em&gt; 是如何最终生成 Cookie 的。如果登陆用了 &lt;a href="https://github.com/plataformatec/devise" rel="nofollow" target="_blank" title=""&gt;Devise&lt;/a&gt;，那么 Session Data 中的登陆信息保存在 &lt;code&gt;warden.user.user.key&lt;/code&gt; 中。之后就用下面例子说明加密。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"warden.user.user.key"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="s2"&gt;"secret"&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;h3 id="① Cookie Serializer"&gt;① Cookie Serializer&lt;/h3&gt;
&lt;p&gt;从 Rails 4.1 开始，默认使用的 JSON，4.1 之前使用的 Ruby Marshal。为了方便其它语言中解析，推荐使用 4.1 或更新的版本并使用 JSON 做为 Cookie 的 serializer。配置在 &lt;code&gt;config/initializers/cookies_serializer.rb&lt;/code&gt; 中&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_dispatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookies_serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSON 的 serializer 就很直接了&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;'json'&lt;/span&gt;
&lt;span class="n"&gt;session_json&lt;/span&gt; &lt;span class="o"&gt;=&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;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;session_json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "{\"warden.user.user.key\":[[1],\"secret\"]}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="② Padding"&gt;② Padding&lt;/h3&gt;
&lt;p&gt;下一步的加密要求数据的字节数必须是 16 的倍数，用的算法是 &lt;a href="https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7" rel="nofollow" target="_blank" title=""&gt;PKCS7&lt;/a&gt;。简单说就是如果差 n 个字节到下个 16 的倍数就补 n 个 n。如果刚好是 16 的倍数就补 16 个 16。&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;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bytesize&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ASCII-8BIT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;padded_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_json&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;padded_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "{\"warden.user.user.key\":[[1],\"secret\"]}\t\t\t\t\t\t\t\t\t"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;末尾的 &lt;code&gt;\t&lt;/code&gt; ASCII 码是 9，表示补了 9 个字节。&lt;/p&gt;
&lt;h3 id="③ 加密 AES-CBC"&gt;③ 加密 AES-CBC&lt;/h3&gt;
&lt;p&gt;这一步是最主要的加密了，算法是 AES-CBC。加密需要配置密钥并随机生成 IV (initialization vector)。因为 Ruby 的 OpenSSL::Cipher 封装会自动 padding，所以可以跳过第 ② 步。&lt;/p&gt;

&lt;p&gt;我们知道 Rails 需要配置 secret key base，密钥就是通过 secret key base 和 salt 产生的，使用的算法 &lt;code&gt;pbkdf2&lt;/code&gt; 在 OpenSSL 里也提供了。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PKCS5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pbkdf2_hmac_sha1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keylen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pass&lt;/code&gt; 配置中的 secret key base&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;salt&lt;/code&gt; 如果使用默认 Rails 配置的话，加密是 &lt;code&gt;encrypted cookie&lt;/code&gt;，后面签名步骤是 &lt;code&gt;signed encrypted cookie&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;iter&lt;/code&gt; 默认是 1000, &lt;code&gt;keylen&lt;/code&gt; 加密是 32，签名是 64。也可以统一用 64，但是加密的 Key 只取前 32 个字节。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'openssl'&lt;/span&gt;
&lt;span class="no"&gt;SECRET_KEY_BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"development_secret"&lt;/span&gt;
&lt;span class="no"&gt;DEFAULT_SALT&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"encrypted cookie"&lt;/span&gt;
&lt;span class="no"&gt;DEFAULT_SIGN_SALT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"signed encrypted cookie"&lt;/span&gt;
&lt;span class="no"&gt;DEFAULT_ITER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="no"&gt;DEFAULT_KEYLEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret_key_base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_ITER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keylen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_KEYLEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PKCS5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pbkdf2_hmac_sha1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret_key_base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keylen&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;encrypt_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generate_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SECRET_KEY_BASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_SALT&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypt_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; vozBHj31liL/p88es/k7aywa4Po4mwMVkW/eqhFjw/4=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;IV 是随机的 16 个字节。解密的时候需要用到，所以需要保存起来下一步拼装的时候用。可以用 &lt;code&gt;SecureRandom.random_bytes&lt;/code&gt; 或者 &lt;code&gt;OpenSSL::Cipher.random_iv&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;使用 OpenSSL 实现如下，IV 应该要随机的，为了方便对照，直接用了 16 个 0&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;encrypt_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generate_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SECRET_KEY_BASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_SALT&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypt_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cipher&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="s2"&gt;"aes-256-cbc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;
&lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encrypt_key&lt;/span&gt;

&lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="c1"&gt;# iv = cipher.random_iv&lt;/span&gt;

&lt;span class="n"&gt;encrypted_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;encrypted_content&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;final&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; t7c1ncaCXhZAOPRtX0BI8eceOmx/Qg3Jrg6uwmgJuSNosKIc7M4KRfOw1q3mFWv7ZSiNO3ZRPxJMGI1cDvu+PQ==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="④ 拼装加密内容和 IV"&gt;④ 拼装加密内容和 IV&lt;/h3&gt;
&lt;p&gt;得到 &lt;code&gt;encrypted_content&lt;/code&gt; 和 &lt;code&gt;iv&lt;/code&gt; 后，分别 base64 后用 &lt;code&gt;--&lt;/code&gt; 连接，然后再做一次 base64 得到 &lt;code&gt;encrypted_data&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;encrypted_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_content&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="o"&gt;+&lt;/span&gt;
  &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_encode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iv&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="n"&gt;encrypted_data&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; dDdjMW5jYUNYaFpBT1BSdFgwQkk4ZWNlT214L1FnM0pyZzZ1d21nSnVTTm9zS0ljN000S1JmT3cxcTNtRld2Ny0tQUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="⑤ 签名 HMAC-SHA1"&gt;⑤ 签名 HMAC-SHA1&lt;/h3&gt;
&lt;p&gt;签名用的 &lt;code&gt;HMAC-SHA1&lt;/code&gt;，结果转成 16 进制字符串。Key 参考 加密步骤中的说明。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;sign_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generate_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SECRET_KEY_BASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_SIGN_SALT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HMAC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SHA1&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;sign_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encrypted_data&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;sign&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 75d8323b0f0e41cf4d5aabee1b229b1be76b83b6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="⑥ 拼装签名"&gt;⑥ 拼装签名&lt;/h3&gt;
&lt;p&gt;最后把 &lt;code&gt;encrypted_data&lt;/code&gt; 和 &lt;code&gt;sign&lt;/code&gt; 用 &lt;code&gt;--&lt;/code&gt; 连接然后做一次 URL Query Escape 就可以了&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"uri"&lt;/span&gt;
&lt;span class="n"&gt;cookie_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode_www_form_component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_data&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"--"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sign&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;cookie_content&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; dDdjMW5jYUNYaFpBT1BSdFgwQkk4ZWNlT214L1FnM0pyZzZ1d21nSnVTTm9zS0ljN000S1JmT3cxcTNtRld2Ny0tQUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09--75d8323b0f0e41cf4d5aabee1b229b1be76b83b6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完整的代码：&lt;a href="https://gist.github.com/doitian/2a89dc9e4372e55335c9111f576b47bf#file-rails-cookie-encrypt-rb" rel="nofollow" target="_blank" title=""&gt;rails-cookie-encrypt.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果用 &lt;code&gt;ActiveSupport&lt;/code&gt; 可以简化成&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"active_support/key_generator"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"active_support/message_encryptor"&lt;/span&gt;
&lt;span class="n"&gt;encrypt_key&lt;/span&gt; &lt;span class="o"&gt;=&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;KeyGenerator&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;SECRET_KEY_BASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;iterations: &lt;/span&gt;&lt;span class="no"&gt;DEFAULT_ITER&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;generate_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_SALT&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="n"&gt;sign_key&lt;/span&gt; &lt;span class="o"&gt;=&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;KeyGenerator&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;SECRET_KEY_BASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;iterations: &lt;/span&gt;&lt;span class="no"&gt;DEFAULT_ITER&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;generate_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_SIGN_SALT&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="n"&gt;encryptor&lt;/span&gt; &lt;span class="o"&gt;=&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;MessageEncryptor&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;encrypt_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sign_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;serializer: &lt;/span&gt;&lt;span class="no"&gt;JSON&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;encryptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt_and_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="解密"&gt;解密&lt;/h2&gt;
&lt;p&gt;解密就是把 6 个步骤反过来，输入就是&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;cookieContent&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"dDdjMW5jYUNYaFpBT1BSdFgwQkk4ZWNlT214L1FnM0pyZzZ1d21nSnVTTm9zS0ljN000S1JmT3cxcTNtRld2Ny0tQUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09--75d8323b0f0e41cf4d5aabee1b229b1be76b83b6"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="⑥ 分离签名"&gt;⑥ 分离签名&lt;/h3&gt;
&lt;p&gt;URL Query Unescape 然后以 &lt;code&gt;--&lt;/code&gt; 分成 &lt;code&gt;encryptedData&lt;/code&gt; 和 &lt;code&gt;sign&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;unescapedCookieContent&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unescapedCookieContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryUnescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cookieContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;encryptedDataSignVectors&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SplitN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unescapedCookieContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;encryptedData&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;encryptedDataSignVectors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;encryptedDataSignVectors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"encrypted_data = %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sign = %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// =&amp;gt; encrypted_data = dDdjMW5jYUNYaFpBT1BSdFgwQkk4ZWNlT214L1FnM0pyZzZ1d21nSnVTTm9zS0ljN000S1JmT3cxcTNtRld2Ny0tQUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09&lt;/span&gt;
&lt;span class="c"&gt;// sign = 75d8323b0f0e41cf4d5aabee1b229b1be76b83b6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="⑤ 验证签名"&gt;⑤ 验证签名&lt;/h3&gt;
&lt;p&gt;验证签名其实就是再签一次然后对比结果。为了安全，可以使用 &lt;code&gt;hmac.Equal&lt;/code&gt; 来比较签名是否一致。&lt;/p&gt;

&lt;p&gt;Key 的生成可以使用 &lt;code&gt;golang.org/x/crypto/pbkdf2&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;keyIterNum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
  &lt;span class="n"&gt;keySize&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&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;pbkdf2&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="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;keyIterNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keySize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sha1&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验证实现如下&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;secretKeyBase&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"development_secret"&lt;/span&gt;
&lt;span class="n"&gt;defaultSignSalt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"signed encrypted cookie"&lt;/span&gt;
&lt;span class="n"&gt;signKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secretKeyBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultSignSalt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;signHmac&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hmac&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;sha1&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;signKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;signHmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;verifySign&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;signHmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"verifySign = %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncodeToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verifySign&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c"&gt;// verifySign = 75d8323b0f0e41cf4d5aabee1b229b1be76b83b6&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;signDecoded&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;signDecoded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecodeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verifySign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signDecoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"verification failed"&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;h3 id="④ 分离加密内容和 IV"&gt;④ 分离加密内容和 IV&lt;/h3&gt;
&lt;p&gt;Base64 解码一次，用 &lt;code&gt;--&lt;/code&gt; 分离并分别 Base64 解码得到 &lt;code&gt;encryptedContent&lt;/code&gt;  和  &lt;code&gt;iv&lt;/code&gt; &lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;encryptedDataBase64Decoded&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;encryptedDataBase64Decoded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecodeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;encryptedContentIvVectors&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SplitN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryptedDataBase64Decoded&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;encryptedContent&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;encryptedContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecodeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryptedContentIvVectors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&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="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecodeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryptedContentIvVectors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"encrypted_content = %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncodeToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryptedContent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"iv = %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// encrypted_content = t7c1ncaCXhZAOPRtX0BI8eceOmx/Qg3Jrg6uwmgJuSNosKIc7M4KRfOw1q3mFWv7&lt;/span&gt;
&lt;span class="c"&gt;// iv = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="③ 解密"&gt;③ 解密&lt;/h3&gt;
&lt;p&gt;用 Key 和 iv 来解密&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;defaultSalt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"encrypted cookie"&lt;/span&gt;
&lt;span class="n"&gt;encryptKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secretKeyBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultSalt&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;aes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCipher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryptKey&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;cfb&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCBCDecrypter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;paddedSession&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryptedContent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;cfb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CryptBlocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encryptedContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"padded_session = %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QuoteToASCII&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedSession&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="c"&gt;// padded_session = "{\"warden.user.user.key\":[[1],\"secret\"]}\t\t\t\t\t\t\t\t\t"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="② Un-padding"&gt;② Un-padding&lt;/h3&gt;
&lt;p&gt;去除 padding 只需要看最后一个字节是多少就移除多少个字节。&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedSession&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedSession&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;sessionJSON&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedSession&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;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedSession&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"session_json = %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessionJSON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// session_json = {"warden.user.user.key":[[1],"secret"]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="① Cookie Deserializer"&gt;① Cookie Deserializer&lt;/h3&gt;
&lt;p&gt;如果是 JSON 用 go JSON 库解析就可以了。如果是 Ruby Marshal 也不用完整实现，可以用正则提取需要的信息。&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;jsonData&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&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;err&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="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionJSON&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;jsonData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%+v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// map[warden.user.user.key:[[1] secret]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完整的代码：&lt;a href="https://gist.github.com/doitian/2a89dc9e4372e55335c9111f576b47bf#file-rails-cookie-decrypt-go" rel="nofollow" target="_blank" title=""&gt;rails-cookie-decrypt.go&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果 Rails 里用的 Devise，可以在 &lt;code&gt;config/initializers/devise.rb&lt;/code&gt; 增加下面的配置来在 Cookie 中包含更多的字段，比如用户名或邮箱&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Warden&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after_authentication&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&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw_session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'warden.user.user.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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要用户重新登陆或者更换 secret key base 才会生效。&lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Sat, 23 Sep 2017 16:00:43 +0800</pubDate>
      <link>https://ruby-china.org/topics/34235</link>
      <guid>https://ruby-china.org/topics/34235</guid>
    </item>
    <item>
      <title>记录使用 typhoeus 调用 360 HTTPS API 时碰到的坑</title>
      <description>&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/0980477102c017e68a90f82d3f8b42cb.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;--- 本文原文发布在 &lt;a href="https://www.3pjgames.com/archives/229" rel="nofollow" target="_blank"&gt;https://www.3pjgames.com/archives/229&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;第一款游戏上线之后，陆续发现了一些诡异的问题。其中一个是通过 360 SDK 登录的用户在进行服务器验证的时候，偶尔会出现 HTTP 客户端没有收到任何回应就返回的情况。怪异的是当尝试手动发送请求的时候，却无法重现，即使是用脚本模拟不停的发送请求。后来陆续发现其它一些渠道，像豌豆荚也出现类似情况。&lt;/p&gt;

&lt;p&gt;经过差不多一天的研究和分析，最终发现是这些登录验证服务提供的是 HTTPS 接口，但是可能服务器版本比较老，并没有完全符合 HTTPS 协议中的一些标准。而我们使用的客户端 &lt;a href="https://github.com/typhoeus/typhoeus" rel="nofollow" target="_blank" title=""&gt;typhoeus&lt;/a&gt; 做了非常多的性能优化，其中对 HTTPS 的一项优化最终导致了 TLS 握手失败，客户端没有收到任何回应就返回了。&lt;/p&gt;

&lt;p&gt;TLDR; 问题出在 typhoeus 启用了 SSL session id cache，而这些渠道提供的 HTTPS 服务器未能正确处理该标准 (&lt;a href="http://tools.ietf.org/html/rfc5246" rel="nofollow" target="_blank" title=""&gt;RFC5246&lt;/a&gt;)。解决方案只能是牺牲性能，将连接选项中的 &lt;code&gt;:ssl_sessionid_cache&lt;/code&gt; 设置为 &lt;code&gt;false&lt;/code&gt;。Typhoeus 是对 libcurl 的封装，对应的选项是 &lt;code&gt;CURLOPT_SSL_SESSIONID_CACHE&lt;/code&gt;。如果使用了其它基于 libcurl 的 HTTP 客户端启用了这项优化，也需要找到地方关闭这个选项。&lt;/p&gt;
&lt;h2 id="变通方案"&gt;变通方案&lt;/h2&gt;
&lt;p&gt;我们通过统一的网关来集成各种第三方 SDK 的登录验证，该服务使用 Ruby 实现，使用了 HTTP 客户端框架 &lt;a href="https://github.com/lostisland/faraday" rel="nofollow" target="_blank" title=""&gt;Faraday&lt;/a&gt;，底层传输使用了 typoeus，而 typoeus 又是对 libcurl 的封装。&lt;/p&gt;

&lt;p&gt;问题发生之后，首先想到的还是网络问题，另外线上问题需要快速解决，所以首先加上了自动重试。Faraday 已经提供了 retry 中间件，只需要在出现这种问题的时候抛出特定类型的异常就可以了。因为没返回结果的时候 status code 是 0，加个中间件去检查 status code 就行了。&lt;/p&gt;

&lt;p&gt;当 status 为 0 的时候抛出 &lt;code&gt;EmptyResponseError&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;EmptyResponseError&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;HttpSdkError&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;class&lt;/span&gt; &lt;span class="nc"&gt;CheckEmptyResponse&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Middleware&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;env&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="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;EmptyResponseError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当抛出 &lt;code&gt;EmptyResponseError&lt;/code&gt; 的时候重试两次。注意 response 中间件是以相反的顺序处理，中间件 &lt;code&gt;CheckEmptyResponse&lt;/code&gt; 加到最后面让它最先处理返回结果。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Faraday&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="s1"&gt;'https://openapi.360.cn'&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;builder&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt; &lt;span class="ss"&gt;:retry&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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;interval: &lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;interval_randomness: &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;backoff_factor: &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;exceptions: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;EmptyResponseError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt; &lt;span class="ss"&gt;:json&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt; &lt;span class="ss"&gt;:json&lt;/span&gt;
  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;CheckEmptyResponse&lt;/span&gt;

  &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;adapter&lt;/span&gt; &lt;span class="ss"&gt;:typhoeus&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加了之后还是会出现连续 3 次都是空结果的情况，但是频率已经大大降低。&lt;/p&gt;
&lt;h2 id="定位问题"&gt;定位问题&lt;/h2&gt;
&lt;p&gt;加上重试问题的影响大大降低，也赢得时间去彻底解决问题。&lt;/p&gt;

&lt;p&gt;我们自架了 &lt;a href="https://pypi.python.org/pypi/sentry" rel="nofollow" target="_blank" title=""&gt;Sentry&lt;/a&gt; 来收集各种异常，问题最初就是通过 Sentry 的通知发现的。但是因为出错时，HTTP 客户端没有收到返回，所以完全无法得知原因，而手动改送请求或者通过 Sentry 的『重放』都能拿到正确的返回结果，无法重现。&lt;/p&gt;

&lt;p&gt;首先想到的是加日志，于是在向 360 发送请求时开启了 Faraday 的中间件 &lt;code&gt;logger&lt;/code&gt; 来打印更详细的日志，但是依然未能发现问题。于是只能去看 typoeus 的源代码，发现可以通过全局设置来显示更多的信息：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Typhoeus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是另外一个问题是没办法手动去重现，只能等着问题发生。虽然这个问题隔一段时间就会出现，可是有时候要等上几个小时。这会导致添加日志和尝试修改之后确认的反馈环节被拉长，可以要几天才能有点眉目，很难排除无效的修复方案。&lt;/p&gt;

&lt;p&gt;之前提到过用脚本模拟循环发也没办法重现，但是线上却一定会发生，于是想到通过解析日志，重发所有 360 的请求。这个方法果然有效，终于能比较快速的去获得反馈了，而且可以在出现问题的时候，在脚本中插入&amp;nbsp;&lt;a href="http://pryrepl.org" rel="nofollow" target="_blank" title=""&gt;pry&lt;/a&gt;&amp;nbsp;进行检查，大大加快了定位问题的速度。最终通过 Typhoeus 的日志可以确定问题是因为 TLS 握手失败了&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hostname was found in DNS cache
  Trying 220.181.132.231...
Connected to openapi.360.cn (220.181.132.231) port 443 (#2)
successfully set certificate verify locations:
  CAfile: none
  CApath: /etc/ssl/certs
SSL re-using session ID
SSLv3, TLS handshake, Client hello (1):
...

 SSLv3, TLS alert, Server hello (2):
Ferror:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number
Closing connection 2
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="解决问题"&gt;解决问题&lt;/h2&gt;
&lt;p&gt;找到问题原因但是怎么解决还是一头雾水，求助 Google 也基本说得是证书问题。然而关闭 peer verification 并没有什么用。也有提到 openssl 版本问题的，安装了 &lt;code&gt;libcurl4-openssl-dev&lt;/code&gt;，更新了 gems &lt;code&gt;faraday&lt;/code&gt; 和 &lt;code&gt;typhoeus&lt;/code&gt; 也没有任何用。&lt;/p&gt;

&lt;p&gt;不过对比成功和失败请求的 Typoeus 请求，注意到所有空结果都会包含&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SSL re-using session ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一开始以为是 keep alive 导致的问题，关闭之后也没能解决问题。最后定位到 RFC5246，而且在 Mac 下 &lt;code&gt;man curl&lt;/code&gt; 也提到&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;--no-sessionid
(SSL) Disable curl's use of SSL session-ID caching.  By default all transfers are done using the cache. Note that while nothing should ever get hurt by attempting to reuse SSL session-IDs, there seem to be broken SSL implementations in the wild that may require you to disable this in order for you to succeed. (Added in 7.16.0) Note that this is the negated option name documented. You can thus use --sessionid to enforce session-ID caching.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;简要来说就是 sessionid 缓存不会带来任何坏处，但是这个世界上还有很出有问题的 SSL 实现，这时可以通过该选项禁用。&lt;/p&gt;

&lt;p&gt;通过这个线索去 typhoeus 源码里去搜索相关选项，最终找到 &lt;code&gt;ssl_sessionid_cache&lt;/code&gt;。不过因为 faraday 没有暴露出该选项的设置，所以采用了 monkey pack 的方式强制将该选项设置为 &lt;code&gt;false&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'typhoeus/adapters/faraday'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;TyphoeusRequestPatch&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:ssl_sessionid_cache&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Faraday&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Adapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Typhoeus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt; &lt;span class="no"&gt;TyphoeusRequestPatch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试之后问题解决。&lt;/p&gt;

&lt;p&gt;这也说明了为何手动难重现，因为这个问题只会在 keep alive 的连接失效，而 session id 的缓存又还没失效的窗口之间发生。&lt;/p&gt;
&lt;h2 id="后记"&gt;后记&lt;/h2&gt;
&lt;p&gt;国内环境中使用 HTTPS 各种坑，这里提到的是服务端的问题。另外在手机客户端上也是一堆问题，比如相当一部份的国产 Android 系统手机上不信任 GoDaddy 颁发的证书，有些低端机上发送 HTTPS 请求会静默失败，没有返回结果也不报任何错。所以不管前端、后端，碰到了 HTTPS，预先考虑各种情况，记录下详细日志方便日后调试。&lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Sat, 26 Sep 2015 21:06:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/27491</link>
      <guid>https://ruby-china.org/topics/27491</guid>
    </item>
    <item>
      <title>RubyConfChina 2013 大会视频 (更新下载链接)</title>
      <description>&lt;p&gt;&lt;a href="/rails" class="user-mention" title="@rails"&gt;&lt;i&gt;@&lt;/i&gt;rails&lt;/a&gt; &lt;a href="http://ruby-china.org/topics/15747" title=""&gt;发现&lt;/a&gt; InfoQ 已经上传了大会视频，但
是没有做整理，所以手动给整理了下&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nov 28, 2013: 更新下载链接 (Thanks &lt;a href="/windix" class="user-mention" title="@windix"&gt;&lt;i&gt;@&lt;/i&gt;windix&lt;/a&gt; )&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Day 1"&gt;Day 1&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Zach Holman --
&lt;a href="http://www.infoq.com/cn/presentations/more-about-the-secrets-of-git-and-github" rel="nofollow" target="_blank" title=""&gt;"More Git and GitHub Secrets"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/25CE229B1FC90DC99C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Daniel Bovensiepen --
&lt;a href="http://www.infoq.com/cn/presentations/simplify-and-retreat-for-improvement" rel="nofollow" target="_blank" title=""&gt;"Shrink To Grow"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/1F705B6CDDAC86549C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;肖雪洁 --
&lt;a href="http://www.infoq.com/cn/presentations/write-a-ruby-web-server-for-nodejs" rel="nofollow" target="_blank" title=""&gt;"Writing Ruby web server for node.js"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/6277F1AB41BDD8879C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;ihower --
&lt;a href="http://www.infoq.com/cn/presentations/from-classes-to-objects-oop-taught-me-these-things" rel="nofollow" target="_blank" title=""&gt;"從 Classes 到 Objects：那些 OOP 教我的事"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/179A51A9D068D1269C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;吕戈 --
&lt;a href="http://www.infoq.com/cn/presentations/build-a-sync-like-async-web-server" rel="nofollow" target="_blank" title=""&gt;"How To Build A Sync-Like Async Web Server"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/AD137E6F03FFF81C9C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Jack Chen --
&lt;a href="http://www.infoq.com/cn/presentations/rails-application-testing-happy" rel="nofollow" target="_blank" title=""&gt;"Test your Rails application with pleasure"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-16/7BD527A7D27665B89C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Kevin Dewalt --
&lt;a href="http://www.infoq.com/cn/presentations/combination-ror-and-better-startup-strategy-in-business" rel="nofollow" target="_blank" title=""&gt;"How entrepreneurs can use Ruby and Rails to accelerate their companies with Lean Startup strategies."&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/15813F3D34FBA6809C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Obie Fernandez --
&lt;a href="http://www.infoq.com/cn/presentations/fanatical-sentiment-of-ruby-community-fanatics" rel="nofollow" target="_blank" title=""&gt;"Burning Ruby Lessons from Burning Man applied to theRuby Community"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/FE88003E33E93B179C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Day 2"&gt;Day 2&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;朱琅 --
&lt;a href="http://www.infoq.com/cn/presentations/the-use-of-ruby-in-jingdong-cloud-engine" rel="nofollow" target="_blank" title=""&gt;"Ruby 在京东云擎中的使用"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-14/FFD7B2A8A0BB2C9B9C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Prem --
&lt;a href="http://www.infoq.com/cn/presentations/hidden-gems-inside-those-of-Ruby-on-rails" rel="nofollow" target="_blank" title=""&gt;"Hidden gems in Ruby on Rails"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/66C36F473914148E9C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;鲁葳 --
&lt;a href="http://www.infoq.com/cn/presentations/somethings-about-rspec" rel="nofollow" target="_blank" title=""&gt;"RSpec: the good parts and the tricky parts"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/27F9E19F390383119C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Ian --
&lt;a href="http://www.infoq.com/cn/presentations/experience-of-migrated-to-jruby" rel="nofollow" target="_blank" title=""&gt;"Migration to JRuby: what we learned"&lt;/a&gt;
(&lt;a href="http://cm12.c120.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/00888B6DB15B3B5F9C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;陈金洲 --
&lt;a href="http://www.infoq.com/cn/presentations/rails-project-architecture-evolution" rel="nofollow" target="_blank" title=""&gt;"Rails 项目架构演进"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/9FCA8656DEBF9DA29C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;杨濯宇 --
&lt;a href="http://www.infoq.com/cn/presentations/with-tests-found-a-better-object-oriented-design" rel="nofollow" target="_blank" title=""&gt;"Discovering Better Object Oriented Design with Tests"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/ADBC4D0352138E059C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;李路 --
&lt;a href="http://www.infoq.com/cn/presentations/from-craftsmen-to-artists-from-programmers-to-designers" rel="nofollow" target="_blank" title=""&gt;"Artisan and Artist - From programmer to designer"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/FDBBDB91F51F4AAB9C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;XDite --
&lt;a href="http://www.infoq.com/cn/presentations/maintainable-rails-view" rel="nofollow" target="_blank" title=""&gt;"Maintainable Rails View"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/F8524678E24A1DB59C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Dave Thomas --
&lt;a href="http://www.infoq.com/cn/presentations/ruby-encoding-happy" rel="nofollow" target="_blank" title=""&gt;"红宝石，编码乐 (Code Ruby, Be Happy)"&lt;/a&gt;
(&lt;a href="http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/F96271D4AED7CACC9C33DC5901307461-10.mp4" rel="nofollow" target="_blank" title=""&gt;MP4 下载&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://ruby-china.org/topics/15151" title=""&gt;幻灯片汇集&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;p&gt;批量下载链接&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/25CE229B1FC90DC99C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/1F705B6CDDAC86549C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/6277F1AB41BDD8879C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/179A51A9D068D1269C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/AD137E6F03FFF81C9C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-16/7BD527A7D27665B89C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/15813F3D34FBA6809C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/FE88003E33E93B179C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-14/FFD7B2A8A0BB2C9B9C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/66C36F473914148E9C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/27F9E19F390383119C33DC5901307461-10.mp4
http://cm12.c120.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/00888B6DB15B3B5F9C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/9FCA8656DEBF9DA29C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/ADBC4D0352138E059C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/FDBBDB91F51F4AAB9C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/F8524678E24A1DB59C33DC5901307461-10.mp4
http://cm12.c110.play.bokecc.com/flvs/0575C033D2012A28/2013-11-13/F96271D4AED7CACC9C33DC5901307461-10.mp4
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>doitian</author>
      <pubDate>Sun, 24 Nov 2013 09:02:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/15749</link>
      <guid>https://ruby-china.org/topics/15749</guid>
    </item>
    <item>
      <title>今天才发现 ruby 的标准库有个 un</title>
      <description>&lt;p&gt;&lt;a href="http://www.ruby-doc.org/stdlib-2.0//libdoc/un/rdoc/un_rb.html" rel="nofollow" target="_blank"&gt;http://www.ruby-doc.org/stdlib-2.0//libdoc/un/rdoc/un_rb.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;提供了一些常用的 Unix 命令，库的名字可能是取自 &lt;strong&gt;UN&lt;/strong&gt;ix，也可能是为了命令行下容易记住，因为命令行下 require 的选项是 &lt;code&gt;-r&lt;/code&gt;，使用这个库就是 &lt;code&gt;-run&lt;/code&gt;。最有用的功能应该是 one line http server 了，类似 python 的 &lt;code&gt;python -m SimpleHTTPServer [port]&lt;/code&gt; 或 &lt;code&gt;python -m http.server [port]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ruby -run -e httpd . -p 5000&lt;/p&gt;

&lt;p&gt;来源：&lt;a href="https://twitter.com/n0kada/status/351556831958667264" rel="nofollow" target="_blank"&gt;https://twitter.com/n0kada/status/351556831958667264&lt;/a&gt;&lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Mon, 08 Jul 2013 14:45:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/12343</link>
      <guid>https://ruby-china.org/topics/12343</guid>
    </item>
    <item>
      <title>Rails 两种创建组合输入的方法</title>
      <description>&lt;p&gt;&lt;a href="http://iany.me/2013/01/rails-compound-input/" rel="nofollow" target="_blank"&gt;http://iany.me/2013/01/rails-compound-input/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;介绍 Rails 中两种实现组合输入方法。&lt;/p&gt;

&lt;p&gt;这源于这样一个需要，表单中需要输入时间，对应的字段是 &lt;code&gt;datetime&lt;/code&gt;，为了方便输入，
希望把日期部分做成文本框，然后用 JavaScript 的 date picker 来选择。时间部份可
以要求用户手动输入，或者也使用 JavaScript 的 time picker。把下面的例子扩展，也
可以实现时间部分使用下拉菜单来输入。&lt;/p&gt;

&lt;p&gt;两种方法分别使用了 &lt;code&gt;composed_of&lt;/code&gt; 和 &lt;code&gt;fields_for&lt;/code&gt;。 &lt;code&gt;composed_of&lt;/code&gt; 和
&lt;code&gt;datetime_select&lt;/code&gt; 类似，使用了 &lt;code&gt;assign_multiparameter_attributes&lt;/code&gt; 这个方法。而
&lt;code&gt;fields_for&lt;/code&gt; 则是伪装成 association。&lt;/p&gt;

&lt;p&gt;两种方法的示例可以在
&lt;a href="https://github.com/doitian/rails-compound-input-demo" rel="nofollow" target="_blank" title=""&gt;doitian/rails-compound-input-demo&lt;/a&gt;
中找到。&lt;/p&gt;
&lt;h2 id="composed_of"&gt;composed_of&lt;/h2&gt;
&lt;p&gt;Rails 本身就有 &lt;code&gt;date_select&lt;/code&gt;, &lt;code&gt;time_select&lt;/code&gt; and
&lt;code&gt;datetime_select&lt;/code&gt; 这些组合输入控件。它们都利用了
[assign_multiparameter_attributes][] 这个特性：如果参数名最后带有小括号，会按
照括号中的位置对应到 constructor 中。但是 datetime 对应的类是 &lt;code&gt;DateTime&lt;/code&gt;，它的
构造函数接受的是 6 个参数，年、月、日、时、分、秒。&lt;/p&gt;

&lt;p&gt;这里可以使用 [composed_of][] 来把 attribute 转换成
value object, 并且构造函数使用 日期 和 时间 两个字符串类型参数。见下面 &lt;code&gt;CompoundDatetime#initialize&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;CompoundDatetime&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;from_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tap&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;result&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

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

  &lt;span class="c1"&gt;# Accepts date and time string. The form just need to submit params&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;#   - compound_beginning_time(1s) for date&lt;/span&gt;
  &lt;span class="c1"&gt;#   - compound_beginning_time(2s) for time&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;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="vi"&gt;@datetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&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;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;presence&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;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="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;date&lt;/span&gt;
    &lt;span class="vi"&gt;@datetime.strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%Y-%m-%d'&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;@datetime&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;time&lt;/span&gt;
    &lt;span class="vi"&gt;@datetime.strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%H:%M'&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;@datetime&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;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/composed_of/app/models/compound_datetime.rb" rel="nofollow" target="_blank" title=""&gt;compound_datetime.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在 model 中使用 &lt;code&gt;composed_of&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;Event&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="n"&gt;attr_accessible&lt;/span&gt; &lt;span class="ss"&gt;:beginning_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;
  &lt;span class="n"&gt;attr_accessible&lt;/span&gt; &lt;span class="ss"&gt;:compound_beginning_time&lt;/span&gt;

  &lt;span class="n"&gt;composed_of&lt;/span&gt; &lt;span class="ss"&gt;:compound_beginning_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;:class_name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'CompoundDatetime'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;:mapping&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="sx"&gt;%w(beginning_time datetime)&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;:converter&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;datetime&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;CompoundDatetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/composed_of/app/models/event.rb" rel="nofollow" target="_blank" title=""&gt;event.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在 form view 中只需要正确设置参数名。&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"field"&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:compound_beginning_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Begining Time'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;BR&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;text_field_tag&lt;/span&gt; &lt;span class="s1"&gt;'event[compound_beginning_time(1s)]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@event.compound_beginning_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:placeholder&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'yyyy-mm-dd'&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;text_field_tag&lt;/span&gt; &lt;span class="s1"&gt;'event[compound_beginning_time(2s)]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@event.compound_beginning_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:placeholder&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'HH:MM'&lt;/span&gt; &lt;span class="cp"&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;&lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/composed_of/app/views/events/_form.html.erb" rel="nofollow" target="_blank" title=""&gt;events/_form.html.erb&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="fields_for"&gt;fields_for&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fields_for&lt;/code&gt; 一般用来在 form 中嵌入 associations。不过&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;它所需要的只是一个 getter 方法和 &lt;code&gt;&amp;lt;field&amp;gt;_attributes=&lt;/code&gt; 的 setter 方法。
&lt;a href="http://wondible.com/2011/06/11/compound-attributes-and-fields_for-in-rails/" rel="nofollow" target="_blank" title=""&gt;Compound Attributes and fields_for in Rails | Wondible&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;首先还是创建 &lt;code&gt;CompoundDatetime&lt;/code&gt;，它需要有 date 和 time 两个 attributes。
&lt;code&gt;assign_attributes&lt;/code&gt; 用来处理 form 传来的 params hash。方法
&lt;code&gt;persisted?&lt;/code&gt; 是为了消除 &lt;code&gt;NoMethodError&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;CompoundDatetime&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:datetime&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;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@datetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# accepts hash like:&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;#     {&lt;/span&gt;
  &lt;span class="c1"&gt;#       'date' =&amp;gt; '2012-12-20',&lt;/span&gt;
  &lt;span class="c1"&gt;#       'time' =&amp;gt; '20:30'&lt;/span&gt;
  &lt;span class="c1"&gt;#     }&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assign_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="vi"&gt;@datetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&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="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;presence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:time&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;presence&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;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="k"&gt;end&lt;/span&gt;
    &lt;span class="nb"&gt;self&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;date&lt;/span&gt;
    &lt;span class="vi"&gt;@datetime.strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%Y-%m-%d'&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;@datetime&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;time&lt;/span&gt;
    &lt;span class="vi"&gt;@datetime.strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%H:%M'&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;@datetime&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;persisted?&lt;/span&gt;&lt;span class="p"&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;&lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/fields_for/app/models/compound_datetime.rb" rel="nofollow" target="_blank" title=""&gt;compound_datetime.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Model 只需要代理给 &lt;code&gt;CompoundDatetime&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;Event&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="n"&gt;attr_accessible&lt;/span&gt; &lt;span class="ss"&gt;:beginning_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;

  &lt;span class="n"&gt;attr_accessible&lt;/span&gt; &lt;span class="ss"&gt;:compound_beginning_time_attributes&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compound_beginning_time&lt;/span&gt;
    &lt;span class="no"&gt;CompoundDatetime&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;beginning_time&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;compound_beginning_time_attributes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&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;beginning_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compound_beginning_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;datetime&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;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/fields_for/app/models/event.rb" rel="nofollow" target="_blank" title=""&gt;event.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Form view 中使用 &lt;code&gt;fields_for&lt;/code&gt; 来嵌入 &lt;code&gt;compound_begining_time&lt;/code&gt; 的属性&lt;/p&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"field"&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:beginning_time&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;br&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields_for&lt;/span&gt; &lt;span class="ss"&gt;:compound_beginning_time&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;fields&lt;/span&gt;&lt;span class="o"&gt;|&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;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:placeholder&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'yyyy-mm-dd'&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;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:placeholder&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'HH:MM'&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="k"&gt;end&lt;/span&gt; &lt;span class="cp"&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;&lt;a href="https://github.com/doitian/rails-compound-input-demo/blob/master/fields_for/app/views/events/_form.html.erb" rel="nofollow" target="_blank" title=""&gt;events/_form.html.erb&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.alexrothenberg.com/2009/05/21/how-to-use-dates-in-rails-when-your.html" rel="nofollow" target="_blank" title=""&gt;Alex Rothenberg - How to use dates in Rails when your database stores a string&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://wondible.com/2011/06/11/compound-attributes-and-fields_for-in-rails/" rel="nofollow" target="_blank" title=""&gt;Compound Attributes and fields_for in Rails | Wondible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://guides.rubyonrails.org/form_helpers.html" rel="nofollow" target="_blank" title=""&gt;Ruby on Rails Guides: Rails Form helpers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[assign_multiparameter_attributes][]&lt;/li&gt;
&lt;li&gt;[composed_of][]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[assign_multiparameter_attributes]: &lt;a href="http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_multiparameter_attributes" rel="nofollow" target="_blank"&gt;http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_multiparameter_attributes&lt;/a&gt; "ActiveRecord::AttributeAssignment#assign_multiparameter_attributes"
[composed_of]: &lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html#method-i-composed_of" rel="nofollow" target="_blank"&gt;http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html#method-i-composed_of&lt;/a&gt; "ActiveRecord::Aggregations.composed_of"&lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Sat, 19 Jan 2013 21:54:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/8202</link>
      <guid>https://ruby-china.org/topics/8202</guid>
    </item>
    <item>
      <title>markdown 测试</title>
      <description>&lt;h2 id="User metion"&gt;User metion&lt;/h2&gt;
&lt;p&gt;&lt;a href="/doitian" class="user-mention" title="@doitian"&gt;&lt;i&gt;@&lt;/i&gt;doitian&lt;/a&gt; &lt;a href="/__doitian__" class="user-mention" title="@__doitian__"&gt;&lt;i&gt;@&lt;/i&gt;__doitian__&lt;/a&gt; &lt;a href="/_doitian_" class="user-mention" title="@_doitian_"&gt;&lt;i&gt;@&lt;/i&gt;_doitian_&lt;/a&gt; &lt;a href="/doitian_t__" class="user-mention" title="@doitian_t__"&gt;&lt;i&gt;@&lt;/i&gt;doitian_t__&lt;/a&gt; &lt;code&gt;@doitian&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@doitian&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight objective_c"&gt;&lt;code&gt;&lt;span class="s"&gt;@"test"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="At floor"&gt;At floor&lt;/h2&gt;
&lt;p&gt;&lt;a href="#reply1" class="at_floor" data-floor="1"&gt;#1F&lt;/a&gt; &lt;code&gt;#1F&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#1F&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="emoji"&gt;emoji&lt;/h2&gt;
&lt;p&gt;&lt;img title=":apple:" alt="🍎" src="https://twemoji.ruby-china.com/2/svg/1f34e.svg" class="twemoji"&gt; &lt;code&gt;:apple:&lt;/code&gt; &lt;img title=":+1:" alt="👍" src="https://twemoji.ruby-china.com/2/svg/1f44d.svg" class="twemoji"&gt; &lt;img title=":-1:" alt="👎" src="https://twemoji.ruby-china.com/2/svg/1f44e.svg" class="twemoji"&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="ss"&gt;:apple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="encoding"&gt;encoding&lt;/h2&gt;
&lt;p&gt;中文@就乱码&lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Sat, 05 Jan 2013 20:10:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/7772</link>
      <guid>https://ruby-china.org/topics/7772</guid>
    </item>
    <item>
      <title>chef-solo-repo:  加了默认配置，集成了 vagrant 和 capistrano 的 chef repo</title>
      <description>&lt;p&gt;Github: &lt;a href="https://github.com/doitian/chef-solo-repo/" rel="nofollow" target="_blank"&gt;https://github.com/doitian/chef-solo-repo/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;用 &lt;a href="https://github.com/opscode/chef-repo" rel="nofollow" target="_blank" title=""&gt;chef-repo&lt;/a&gt; 的话，即使运行了 knife configure，还是有路径指向了 /etc, /var 这些位置，测试 cookbooks 都得需要 root 权限，很不方便，所以我加了默认的配置，把路径会都调整到 repo 中的 .chef 文件夹。&lt;/p&gt;

&lt;p&gt;另外，cookbooks 的测试最好是用 virtual machine，我添加了 Vagrantfile，直接用 vagrant 可以创建测试用的虚拟机。&lt;/p&gt;

&lt;p&gt;最后，我不太喜欢 chef-server，所以我都是使用 capistrano，一条命令就可以把整个 repo 打包上传到服务器，安装依赖 (bundler) 并运行 chef-solo。所有服务器的配置都放在 repo 下的 servers 文件夹，做了些 cap 的 hacking，可以很方便地指定哪些服务器需要运行 chef-solo&lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Wed, 19 Dec 2012 08:19:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/7681</link>
      <guid>https://ruby-china.org/topics/7681</guid>
    </item>
    <item>
      <title>使用 Active Model 封装方便生成表单</title>
      <description>&lt;p&gt;在 Rails 中需要编辑 &lt;code&gt;ActiveRecord&lt;/code&gt; 对象时，可以很方便的使用 &lt;code&gt;form_for&lt;/code&gt; (或者 &lt;code&gt;simple_form_for&lt;/code&gt;) 来生成表单。但是有时候需要编辑的对象不是 &lt;code&gt;ActiveRecord&lt;/code&gt;，比如导入导出数据，搜索的时候需要表单；再比如有时候系统设置为了不经常去改 &lt;code&gt;schema&lt;/code&gt; 会使用 &lt;code&gt;rails-settings-cached&lt;/code&gt; 这样的 gem 来存储。这种时候就可以用 &lt;code&gt;ActiveModel&lt;/code&gt; 的 modules 来生成一个 class，它满足 form builder 对 &lt;code&gt;ActiveRecord&lt;/code&gt; 的所有需求，所以可以直接用在 form builder 里。&lt;/p&gt;

&lt;p&gt;详见 gist: &lt;a href="https://gist.github.com/4307089" rel="nofollow" target="_blank"&gt;https://gist.github.com/4307089&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;其中 &lt;code&gt;type_caster&lt;/code&gt; 可以限制类型，并能进行类型转换。&lt;/p&gt;

&lt;p&gt;Ryan 在 &lt;a href="https://github.com/railscasts/396-importing-csv-and-excel/blob/master/store-with-validations/app/models/product_import.rb" rel="nofollow" target="_blank" title=""&gt;Railscasts #396&lt;/a&gt; 也做了类似的封装。&lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Sun, 16 Dec 2012 21:43:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/7615</link>
      <guid>https://ruby-china.org/topics/7615</guid>
    </item>
    <item>
      <title>pry 代替 Rails console 的 irb，不用加进 Gemfile</title>
      <description>&lt;p&gt;原理就是先不加载 Bundler，用 rubygems 把 pry awesome_print 等 gems 加进来以后来把 rails app 加进来。把 &lt;code&gt;Rails::Console::IRB&lt;/code&gt; 设置成 &lt;code&gt;Pry&lt;/code&gt;，Hack 下 &lt;code&gt;ARGV&lt;/code&gt;，就可以交给 rails/commands 处理了&lt;/p&gt;

&lt;p&gt;已经包装成 gem &lt;a href="https://github.com/doitian/rails-console-pry" rel="nofollow" target="_blank" title=""&gt;rails-console-pry&lt;/a&gt;，安装不走 bundler，如果用了 rvm gemset，需要安装到 gemset 中。顺带可以安装些 pry plugins&lt;/p&gt;

&lt;p&gt;gem install rails-console-pry pry-doc awesome_print&lt;/p&gt;

&lt;p&gt;然后在 rails app 根目录直接&lt;/p&gt;

&lt;p&gt;rails-console-pry&lt;/p&gt;

&lt;p&gt;或者进 &lt;code&gt;production&lt;/code&gt; 环境&lt;/p&gt;

&lt;p&gt;rails-console-pry production&lt;/p&gt;

&lt;p&gt;需要在 Bundler 之前加载插件（就是指不在 Gemfile 中的插件）用 &lt;code&gt;-r&lt;/code&gt; 选项&lt;/p&gt;

&lt;p&gt;rails-console-pry -r pry-doc -r awesome_print&lt;/p&gt;

&lt;p&gt;最好是加个 alias&lt;/p&gt;

&lt;p&gt;alias rpry='rails-cosnole-pry -r pry-doc -r awesome_print'&lt;/p&gt;

&lt;hr&gt;</description>
      <author>doitian</author>
      <pubDate>Mon, 09 Apr 2012 07:39:04 +0800</pubDate>
      <link>https://ruby-china.org/topics/2471</link>
      <guid>https://ruby-china.org/topics/2471</guid>
    </item>
    <item>
      <title>rbenv gemset 使用心得 for zsh</title>
      <description>&lt;p&gt;[rbenv-gemset][] 是 [rbenv][] 一个插件，支持在目录树中使用指定的 gemsets。和 [rvm][] 相比，因为文件&lt;code&gt;.rbenv-gemsets&lt;/code&gt;并不包含任意可执行代码，不需要人肉确定是否信任。&lt;/p&gt;

&lt;p&gt;安装只需要把 &lt;code&gt;rbenv-gemset&lt;/code&gt; clone 到 &lt;code&gt;.rbenv/plugins&lt;/code&gt; 里。使用方法很简单，在需要使用 &lt;code&gt;gemsets&lt;/code&gt; 的目录上建立文件，用空格分开所有 &lt;code&gt;gemsets&lt;/code&gt;。其中第一个 &lt;code&gt;gemset&lt;/code&gt; 也会作为 gems 安装目录。&lt;/p&gt;

&lt;p&gt;注意如果要指定 ruby 版本，需要使用 &lt;code&gt;rbenv local&lt;/code&gt; 方法。&lt;/p&gt;

&lt;p&gt;在 &lt;code&gt;rvm&lt;/code&gt; 中一个很方便的功能就是可以在指定的 &lt;code&gt;gemset&lt;/code&gt; 中执行一个命令，而 &lt;code&gt;rbenv-gemset&lt;/code&gt; 则必须更改 &lt;code&gt;.rbenv-gemsets&lt;/code&gt; 文件，很容易会忘记改回来。好在可以通过 &lt;code&gt;RBENV_GEMSET_FILE&lt;/code&gt; 指定文件，所以可以建立个临时文件，写入要用的 &lt;code&gt;gemsets&lt;/code&gt;。在 &lt;code&gt;zsh&lt;/code&gt; 中可以使用 &lt;code&gt;=(&amp;lt;&amp;lt;&amp;lt;arg)&lt;/code&gt; (see &lt;code&gt;zshexpn(1) Process Substitution&lt;/code&gt;)，见下面包装函数 &lt;code&gt;gemset&lt;/code&gt;。原来的 &lt;code&gt;rbenv gemset&lt;/code&gt; 二级命令仍然可以使用，比如 &lt;code&gt;gemset list&lt;/code&gt;。如果第一个参数不是有效的命令，会当成 &lt;code&gt;.rbenv-gemsets&lt;/code&gt; 文件的内容。比如在 gemset &lt;code&gt;generator&lt;/code&gt; 中执行 &lt;code&gt;rails&lt;/code&gt; 命令&lt;/p&gt;

&lt;p&gt;gemset generator rails&lt;/p&gt;

&lt;p&gt;这样可以把一些常用命令安装到单独的 gemset 中，保持环境整洁（洁癖有木有）。示例：&lt;/p&gt;

&lt;p&gt;gemset generator gem install rails
    gemset tools gem install github tmuxinator
    gemset deploy gem install capistrano capistrano-ext bundler 
    gemset debug gem install pry pry-doc awesome_print
    rbenv rehash&lt;/p&gt;

&lt;p&gt;为方便使用可以建立 alias (rvm users 也可以这么干)&lt;/p&gt;

&lt;p&gt;alias github='gemset tools github'
    alias gh=github
    alias tmuxinator="gemset tools tmuxinator"
    alias cap='gemset deploy cap'
    alias capify='gemset deploy capify'
    alias pry='gemset debug pry'
    alias irb='pry'&lt;/p&gt;

&lt;p&gt;rails 命令比较特殊，我希望是在有 &lt;code&gt;Gemfile&lt;/code&gt; 的目录树中使用 bundler 指定的版本，而在其它目录中使用 gemset generator 中的版本，见最后面的 &lt;code&gt;bundler alias&lt;/code&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="gemset"&gt;gemset&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# unset global RBENV_GEMSET_FILE to get a clean environment.&lt;/span&gt;
&lt;span class="c"&gt;# For example, tmuxinator may set this because it is a Ruby gem.&lt;/span&gt;
&lt;span class="nb"&gt;unset &lt;/span&gt;RBENV_GEMSET_FILE
&lt;span class="k"&gt;function &lt;/span&gt;gemset&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&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;then
    &lt;/span&gt;rbenv gemset active
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;shift
    &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
      &lt;/span&gt;active|create|delete|file|list|version&lt;span class="p"&gt;)&lt;/span&gt;
        rbenv gemset &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&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;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;RBENV_GEMSET_FILE&lt;/span&gt;&lt;span class="o"&gt;==(&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&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;esac&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# completion function for gemset&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;_gemset&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; _actions _gemsets
  &lt;span class="nv"&gt;_actions&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;active create delete file list version&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;_gemsets&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;rbenv gemset list | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'^ '&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  _arguments &lt;span class="s2"&gt;":action: _values actions &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_actions&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_gemsets&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'*::arguments: _sudo'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
compdef _gemset gemset


&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="bundler alias"&gt;bundler alias&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;##################################################&lt;/span&gt;
&lt;span class="c"&gt;# Bundle Alias&lt;/span&gt;
&lt;span class="c"&gt;# from oh-my-zsh bundler plugin&lt;/span&gt;

&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;be&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bundle exec"&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;bi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bundle install"&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;bl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bundle list"&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;bp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bundle package"&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;bu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bundle update"&lt;/span&gt;

&lt;span class="nv"&gt;bundled_commands&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;cucumber foreman guard nanoc3 rackup rails rainbows rake rspec shotgun spec spork thin unicorn unicorn_rails knife&lt;span class="o"&gt;)&lt;/span&gt;

_bundler-installed&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  which bundle &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;span class="o"&gt;}&lt;/span&gt;

_within-bundled-project&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;check_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;
  &lt;span class="k"&gt;while&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="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$check_dir&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&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="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$check_dir&lt;/span&gt;&lt;span class="s2"&gt;/Gemfile"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;return
    &lt;/span&gt;&lt;span class="nv"&gt;check_dir&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="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$check_dir&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

bundler-exec&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;_bundler-installed &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; _within-bundled-project&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;bundle &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
      &lt;/span&gt;nanoc3|rails&lt;span class="p"&gt;)&lt;/span&gt;
        gemset generator &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&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;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&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;esac&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

compdef _sudo bundler-exec

&lt;span class="c"&gt;## Main program&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;cmd &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$bundled_commands&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;alias&lt;/span&gt; &lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bundler-exec &lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;摘自我的 zsh 配置 &lt;a href="https://github.com/doitian/dotfiles/blob/master/_zsh_/rbenv.zsh" rel="nofollow" target="_blank" title=""&gt;rbenv.zsh&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[rbenv]: &lt;a href="https://github.com/sstephenson/rbenv" rel="nofollow" target="_blank"&gt;https://github.com/sstephenson/rbenv&lt;/a&gt;
[rbenv-gemset]: &lt;a href="https://github.com/sstephenson/rbenv-gemset" rel="nofollow" target="_blank"&gt;https://github.com/sstephenson/rbenv-gemset&lt;/a&gt;
[rvm]: &lt;a href="http://beginrescueend.com/" rel="nofollow" target="_blank"&gt;http://beginrescueend.com/&lt;/a&gt;&lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Sat, 07 Apr 2012 14:38:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/2449</link>
      <guid>https://ruby-china.org/topics/2449</guid>
    </item>
    <item>
      <title>Rails try method changes </title>
      <description>&lt;p&gt;今天把一个项目从 rails 3.1.0 升级到 3.1.4，碰到一堆 method missing exceptiosn。结果是 rails 3.1.4 里 &lt;code&gt;try&lt;/code&gt; 现在只吞掉 receive 是 nil 的 exception，而以前也是会检查&lt;code&gt;respond_to?&lt;/code&gt;的。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rails/rails/issues/3264" rel="nofollow" target="_blank"&gt;https://github.com/rails/rails/issues/3264&lt;/a&gt;  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Wed, 04 Apr 2012 12:29:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/2374</link>
      <guid>https://ruby-china.org/topics/2374</guid>
    </item>
    <item>
      <title>XMonad Starter Kit 平铺窗口管理器配置分享</title>
      <description>&lt;p&gt;把我自己用的 XMonad 配置和一些脚本单独抽了出来 &lt;a href="https://github.com/doitian/xmonad-starter-kit" rel="nofollow" target="_blank"&gt;https://github.com/doitian/xmonad-starter-kit&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  基于 dzen2 的 panel，多显示器下会在&lt;strong&gt;当前&lt;/strong&gt;显示器显示信息&lt;/li&gt;
&lt;li&gt;  基于 dmenu 的跳转或创建 workspace，有匹配的就跳转，否则创建新的 workspace&lt;/li&gt;
&lt;li&gt;  基于 dmenu 的 layout 选择&lt;/li&gt;
&lt;li&gt;  需要关注的窗口 (Urgency)，会通过 libnotify 发送通知&lt;/li&gt;
&lt;li&gt;  支持鼠标切换 workspace 和 layout&lt;/li&gt;
&lt;li&gt;  如果安装了 gpicker，可以使用&lt;code&gt;M-g&lt;/code&gt; (goto) 和&lt;code&gt;M-b&lt;/code&gt; (bring) 切换窗口，支持 fuzzy match&lt;/li&gt;
&lt;li&gt;  精心挑选的 layouts 组合&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;M-c h&lt;/code&gt;显示快捷键列表&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;p&gt;&lt;a href="http://www.yupoo.com/photos/doitian/84987916/zoom/original/" rel="nofollow" target="_blank" title=""&gt;&lt;img src="http://pic.yupoo.com/doitian/BRzHO6B6/medish.jpg" title="" alt="XMonad"&gt;&lt;/a&gt;  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Tue, 03 Apr 2012 08:51:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/2357</link>
      <guid>https://ruby-china.org/topics/2357</guid>
    </item>
    <item>
      <title>Sprockets 中集成 Jasmine 自动化测试 Javascript</title>
      <description>&lt;p&gt;介绍怎么在基于 Sprockets 的项目中测试前端 js。虽然以 Jasmine 为例，其它支持 HTML runner 的测试框架也是通用的。其实重点推荐的是用 livereload 来在文件修改后自动刷新页面执行测试&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cn.intridea.com/2011/12/integrate-jasmine-in-sprockets-to-autotest-javascript/" rel="nofollow" target="_blank"&gt;http://cn.intridea.com/2011/12/integrate-jasmine-in-sprockets-to-autotest-javascript/&lt;/a&gt;  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Sun, 01 Jan 2012 21:07:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/697</link>
      <guid>https://ruby-china.org/topics/697</guid>
    </item>
    <item>
      <title>在 Linux 下设置 Chromium Omnibar 快捷键</title>
      <description>&lt;p&gt;在 Linux 下用 Chromium 很不爽的是在 Omnibar 里，要上下选择匹配项只能用上下键，做为键盘控这是不可接受的，折腾了半天的 Google Chrome 都没搞定，换成 Chromium 最新版没有问题，应该是 gtk keybinding 的修改还没有进入 Google Chrome。&lt;/p&gt;

&lt;p&gt;换成 Chromium 后只需要用 gtkrc 来设置&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.gtkrc-2.0&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;binding "gtk-binding-text" {
    bind "&amp;lt;alt&amp;gt;n" { "move-cursor" (display-lines, 1, 0) }
    bind "&amp;lt;alt&amp;gt;p" { "move-cursor" (display-lines, -1, 0) }
    bind "&amp;lt;ctrl&amp;gt;n" { "move-cursor" (display-lines, 1, 0) }
    bind "&amp;lt;ctrl&amp;gt;p" { "move-cursor" (display-lines, -1, 0) }
    bind "&amp;lt;alt&amp;gt;BackSpace"  { "delete-from-cursor" (word-ends, -1) }
}
class "GtkEntry" binding "gtk-binding-text"
class "GtkTextView" binding "gtk-binding-text"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;详细请查看 &lt;a href="https://gist.github.com/1538492" rel="nofollow" target="_blank"&gt;https://gist.github.com/1538492&lt;/a&gt;  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Fri, 30 Dec 2011 16:04:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/654</link>
      <guid>https://ruby-china.org/topics/654</guid>
    </item>
    <item>
      <title>在 emacs 里用 doxymacs mode 写 yard doc</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/doitian/doxymacs-yard" rel="nofollow" target="_blank"&gt;https://github.com/doitian/doxymacs-yard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;基于 emacswiki 上的 doxymacs-yard.el 改了下，加入了 yard doc tag 的高亮&lt;/p&gt;

&lt;p&gt;&lt;img src="http://img806.imageshack.us/img806/1953/selection2011121401.png" title="" alt=""&gt;  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Wed, 14 Dec 2011 22:36:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/489</link>
      <guid>https://ruby-china.org/topics/489</guid>
    </item>
    <item>
      <title>原来 rebase 翻译成 变基 ?</title>
      <description>&lt;p&gt;&lt;a href="http://www.worldhello.net/gotgithub/04-work-with-others/020-shared-repo.html#id5" rel="nofollow" target="_blank"&gt;http://www.worldhello.net/gotgithub/04-work-with-others/020-shared-repo.html#id5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这翻译太有爱了&lt;/p&gt;

&lt;p&gt;P.S. 这书还是很不错的  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Wed, 14 Dec 2011 13:37:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/480</link>
      <guid>https://ruby-china.org/topics/480</guid>
    </item>
    <item>
      <title>自动根据 host 判断 root 的 nginx with passenger 配置</title>
      <description>&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
  server_name ~^(.*\.)?(?&amp;lt;app&amp;gt;[^.]+)\.dev$;
  root /opt/apps/$app/public;
  rails_env development;
  passenger_enabled on;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nginx 0.8.25+ 应该都能用，参考 &lt;a href="http://wiki.nginx.org/HttpCoreModule#server_name" rel="nofollow" target="_blank" title=""&gt;server_name&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;需要跑哪个 Rails/Rack app 就在/opt/apps 下面建立个 symbol link（link 到含 config.ru 的根目录)&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /opt/apps
ln -s /path/to/myapp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然 http 里需要设置下&lt;code&gt;passenger_root&lt;/code&gt;和&lt;code&gt;passenger_ruby&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.myapps.dev" rel="nofollow" target="_blank" title=""&gt;www.myapps.dev&lt;/a&gt; 和 myapps.dev 都会访问 /opt/apps/myapp&lt;/p&gt;

&lt;p&gt;如果你连/etc/hosts 都不想改，想像 pow 那样，在 Mac 下可以用这个&lt;a href="https://github.com/doitian/simpledns" rel="nofollow" target="_blank" title=""&gt;脚本&lt;/a&gt;来启动 pow 里的 DNS server&lt;/p&gt;

&lt;p&gt;Linux 有&lt;a href="https://github.com/pyromaniac/hoof/" rel="nofollow" target="_blank" title=""&gt;Hoof&lt;/a&gt;可以用（我还没试过），使用 nsswitch 来自动把指定的根域名解析到本机  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Fri, 09 Dec 2011 08:19:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/418</link>
      <guid>https://ruby-china.org/topics/418</guid>
    </item>
    <item>
      <title>[转] Ruby Blocks as Dynamic Callback</title>
      <description>&lt;p&gt;&lt;a href="http://www.mattsears.com/articles/2011/11/27/ruby-blocks-as-dynamic-callbacks" rel="nofollow" target="_blank"&gt;http://www.mattsears.com/articles/2011/11/27/ruby-blocks-as-dynamic-callbacks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;用几行代码实现了很酷的效果，每次 yield 一个动态创建的 Class，通过这个 Class 去选择正确的 block  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Tue, 06 Dec 2011 09:23:33 +0800</pubDate>
      <link>https://ruby-china.org/topics/370</link>
      <guid>https://ruby-china.org/topics/370</guid>
    </item>
    <item>
      <title>tmuxinator 的 shell completion</title>
      <description>&lt;p&gt;tmux 是个类似 screen 的终端会话管理工具，tmuxinator 是个自动化建立 tmux session 的工具，我一般都为每个项目建立个 tmuxinator config，然后启动服务，rails server, console, log 等等。&lt;/p&gt;

&lt;p&gt;最新版本的 tmuxinator 已带 bash completion，只需要找到 gem 目录下的 bin/tmuxinator_completion，在 basrc 里 source 一下。&lt;/p&gt;

&lt;p&gt;zsh 我刚发了个 pull request 到 oh-my-zsh，merge 后把 tmuxinator 加到 plugins 里就行了。目录可以下载&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/doitian/oh-my-zsh/blob/20b025ba2cd419a99630458052bfa9a9e8c99e91/plugins/tmuxinator/_tmuxinator" rel="nofollow" target="_blank"&gt;https://github.com/doitian/oh-my-zsh/blob/20b025ba2cd419a99630458052bfa9a9e8c99e91/plugins/tmuxinator/_tmuxinator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;到某个目录，然后把这个目录加到 fpath 里&lt;/p&gt;

&lt;p&gt;fpath=($HOME/.zsh/completions $fpath)  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Fri, 02 Dec 2011 19:06:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/315</link>
      <guid>https://ruby-china.org/topics/315</guid>
    </item>
    <item>
      <title>社区 wiki 文章 整合</title>
      <description>&lt;p&gt;这三个都差不多，可以整合下。&lt;/p&gt;

&lt;p&gt;比如 Category -&amp;gt; Posts&lt;/p&gt;

&lt;p&gt;社区，wiki, 文章就是第一层的 3 个 category&lt;/p&gt;

&lt;p&gt;每个 category 可以设置些属性和 ACL 控制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  是否默认开启 comment&lt;/li&gt;
&lt;li&gt;  是否需要审核&lt;/li&gt;
&lt;li&gt;  是否可以发贴（比如社区的根结点是没有 posts 的）&lt;/li&gt;
&lt;li&gt;  编辑权限&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;posts 可以有自己的 ACL 设置 override category 的设置&lt;/p&gt;

&lt;p&gt;这样可以弄全局的热门 posts，可以把社区的帖子直接移动到 Wiki 去。如果 category posts 是多对多，还可以在 wiki 里添加一个 page 后，同时在社区显示为一个贴子  &lt;/p&gt;</description>
      <author>doitian</author>
      <pubDate>Fri, 02 Dec 2011 15:33:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/309</link>
      <guid>https://ruby-china.org/topics/309</guid>
    </item>
  </channel>
</rss>
