<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>dayudodo (OldBoy)</title>
    <link>https://ruby-china.org/dayudodo</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>2021 Rails-carrierwave 通过 api 上传音频实例</title>
      <description>&lt;p&gt;carrierwave 是 rails 中可以上传文件的工具库，虽然 rails 在 5.2 版本之后已经提供了 active storage，更安全、功能强大，但 carrierwave 用起来比较简单，适合小的项目。&lt;/p&gt;

&lt;p&gt;本文要讲的是使用 api 来上传音频文件到 rails 服务器的办法。&lt;/p&gt;
&lt;h2 id="carrierwave官网地址"&gt;carrierwave 官网地址&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/carrierwaveuploader/carrierwave" rel="nofollow" target="_blank"&gt;https://github.com/carrierwaveuploader/carrierwave&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;star: 8.6k&lt;/p&gt;

&lt;p&gt;根据 ruby toolbox 统计，其在文件上传中排名第二，第一的 paperclip 已经停更了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dw.gsenglish.cn:5543/uploads/2021/08/image-20210821170730387.png" title="" alt="image-20210821170730387"&gt;&lt;/p&gt;

&lt;p&gt;首先，在 Gemfile 中添加&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'carrierwave'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装好&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成一个音频上传类&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails generate uploader Soundfile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会保存在 app/uploaders/soundfile_uploader.rb 中，其内容如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SoundfileUploader&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;CarrierWave&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Uploader&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="c1"&gt;# Include RMagick or MiniMagick support:&lt;/span&gt;
  &lt;span class="c1"&gt;# include CarrierWave::RMagick&lt;/span&gt;
  &lt;span class="c1"&gt;# include CarrierWave::MiniMagick&lt;/span&gt;

  &lt;span class="c1"&gt;# Choose what kind of storage to use for this uploader:&lt;/span&gt;
  &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="ss"&gt;:file&lt;/span&gt;
  &lt;span class="c1"&gt;# storage :fog&lt;/span&gt;

  &lt;span class="c1"&gt;# Override the directory where uploaded files will be stored.&lt;/span&gt;
  &lt;span class="c1"&gt;# This is a sensible default for uploaders that are meant to be mounted:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;store_dir&lt;/span&gt;
     &lt;span class="s2"&gt;"storage/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;underscore&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mounted_as&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Provide a default URL as a default if there hasn't been a file uploaded:&lt;/span&gt;
  &lt;span class="c1"&gt;# def default_url(*args)&lt;/span&gt;
  &lt;span class="c1"&gt;#   # For Rails 3.1+ asset pipeline compatibility:&lt;/span&gt;
  &lt;span class="c1"&gt;#   # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;#   "/images/fallback/" + [version_name, "default.png"].compact.join('_')&lt;/span&gt;
  &lt;span class="c1"&gt;# end&lt;/span&gt;

  &lt;span class="c1"&gt;# Process files as they are uploaded:&lt;/span&gt;
  &lt;span class="c1"&gt;# process scale: [200, 300]&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# def scale(width, height)&lt;/span&gt;
  &lt;span class="c1"&gt;#   # do something&lt;/span&gt;
  &lt;span class="c1"&gt;# end&lt;/span&gt;

  &lt;span class="c1"&gt;# Create different versions of your uploaded files:&lt;/span&gt;
  &lt;span class="c1"&gt;# version :thumb do&lt;/span&gt;
  &lt;span class="c1"&gt;#   process resize_to_fit: [50, 50]&lt;/span&gt;
  &lt;span class="c1"&gt;# end&lt;/span&gt;

  &lt;span class="c1"&gt;# Add an allowlist of extensions which are allowed to be uploaded.&lt;/span&gt;
  &lt;span class="c1"&gt;# For images you might use something like this:&lt;/span&gt;
  &lt;span class="c1"&gt;# def extension_allowlist&lt;/span&gt;
  &lt;span class="c1"&gt;#   %w(jpg jpeg gif png)&lt;/span&gt;
  &lt;span class="c1"&gt;# end&lt;/span&gt;

  &lt;span class="c1"&gt;# Override the filename of the uploaded files:&lt;/span&gt;
  &lt;span class="c1"&gt;# Avoid using model.id or version_name here, see uploader/store.rb for details.&lt;/span&gt;
  &lt;span class="c1"&gt;# def filename&lt;/span&gt;
  &lt;span class="c1"&gt;#   if original_filename&lt;/span&gt;
  &lt;span class="c1"&gt;#     original_filename.force_encoding 'UTF-8'&lt;/span&gt;
  &lt;span class="c1"&gt;#   end&lt;/span&gt;
  &lt;span class="c1"&gt;# end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="storage"&gt;storage&lt;/h2&gt;
&lt;p&gt;指定了你要保存文档的位置，我是想把文件上传后保存到服务器中，所以 storage :file 并不需要改动。如果想保存到云存储中，请参考官方文档。&lt;/p&gt;
&lt;h2 id="store_dir"&gt;store_dir&lt;/h2&gt;
&lt;p&gt;里面你可以设置成自己想要保存的路径，&lt;strong&gt;其默认是保存在 public 目录下&lt;/strong&gt;，类似于这样：public/storage/practice/文件名，这样上传之后是可以通过 url 来访问的，可以根据你自己的需求调整。也可以使用绝对路径，比如/home/user/storage，请确认你有对应的权限。&lt;/p&gt;
&lt;h2 id="建立关联"&gt;建立关联&lt;/h2&gt;
&lt;p&gt;我想给练习添加一个音频&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g migration add_soundfile_to_practices soundfile:string
rails db:migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;db/migrate/20210818034921_add_soundfile_to_practices.rb内容如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddSoundfileToPractices&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:practices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:soundfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 practice.rb 中建立关联&lt;/p&gt;

&lt;p&gt;记得要使用 mount_uploader, 如果是多个文件，需要使用 mount_uploaders, 后面多个 S！&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;Practice&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
    &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
    &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:pth_sentence&lt;/span&gt;
    &lt;span class="n"&gt;mount_uploader&lt;/span&gt; &lt;span class="ss"&gt;:soundfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SoundfileUploader&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Controller"&gt;Controller&lt;/h2&gt;
&lt;p&gt;app/controllers/practices_controller.rb中主要代码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="vi"&gt;@practice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Practice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@practice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="no"&gt;Practice&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;practice_params&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;@practice.save&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;msg: &lt;/span&gt;&lt;span class="s1"&gt;'创建成功'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;practice_params&lt;/span&gt;
  &lt;span class="n"&gt;prms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:practice&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:pth_sentence_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:soundfile&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# prms[:soundfiles].first.headers.force_encoding 'utf-8'&lt;/span&gt;
  &lt;span class="n"&gt;prms&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="routes"&gt;routes&lt;/h2&gt;
&lt;p&gt;routes.rb 中要有路由&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources :practices
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="测试web上传"&gt;测试 web 上传&lt;/h2&gt;
&lt;p&gt;如果你使用 web 上传，那么可以在 views 中这样来写（app/views/practices/_form.html.erb）：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= form_for(@practice) do |f| %&amp;gt;
  &amp;lt;%= f.text_field 'user_id' %&amp;gt;
  &amp;lt;%= f.text_field 'pth_sentence_id' %&amp;gt;
  &amp;lt;%= f.file_field :soundfile %&amp;gt;
  &amp;lt;%= f.submit %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="测试API上传"&gt;测试 API 上传&lt;/h2&gt;
&lt;p&gt;参数要组装成 rails 需要的格式，类名 [属性]，客户端我用的是 uniapp, 所以用 uniapp 来举例。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;uni&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uploadFile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`http://192.168.1.8:3000/practices`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;practice[soundfile]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;voicePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;practice[user_id]&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;practice[pth_sentence_id]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadFileRes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadFileRes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;uniapp 提供了 uploadFile 的 API：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;filePath 是要上传的文件名称&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;name 是参数名称，注意其写法为 practice[soundfile]，要与 model 中对应&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;其它参数写在 formData 中。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;测试成功，文件已经上传到指定的目录中&lt;/p&gt;

&lt;p&gt;&lt;img src="https://dw.gsenglish.cn:5543/uploads/2021/08/image-20210821185153643.png" title="" alt="image-20210821185153643"&gt;&lt;/p&gt;

&lt;p&gt;观察后台，可以看到 soundfile 已经保存好了。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&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="n"&gt;pry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
  &lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="sb"&gt;`users`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="sb"&gt;`users`&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="sb"&gt;`users`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;` = 6 LIMIT 1
=&amp;gt; #&amp;lt;User id: 6, email: "c6@c6.com", created_at: "2021-08-18 03:25:59", updated_at: "2021-08-18 03:25:59", phone: "13317555983", name: "king1", title: nil&amp;gt;
[2] pry(main)&amp;gt; u.practices.last
  Practice Load (0.2ms)  SELECT `&lt;/span&gt;&lt;span class="n"&gt;practices&lt;/span&gt;&lt;span class="sb"&gt;`.* FROM `&lt;/span&gt;&lt;span class="n"&gt;practices&lt;/span&gt;&lt;span class="sb"&gt;` WHERE `&lt;/span&gt;&lt;span class="n"&gt;practices&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="sb"&gt;` = 6 ORDER BY `&lt;/span&gt;&lt;span class="n"&gt;practices&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;` DESC LIMIT 1
=&amp;gt; #&amp;lt;Practice:0x00007f9c9c83dca0
 id: 8,
 user_id: 6,
 pth_sentence_id: 44,
 local_sound: nil,
 comment: nil,
 created_at: Thu, 19 Aug 2021 09:58:42 CST +08:00,
 updated_at: Thu, 19 Aug 2021 09:58:42 CST +08:00,
 soundfile: "07-最后他选择了后者_.mp3"&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="常见用法"&gt;常见用法&lt;/h2&gt;&lt;h3 id="可用url"&gt;可用 url&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;pry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;practices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;soundfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;
  &lt;span class="no"&gt;Practice&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="sb"&gt;`practices`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="sb"&gt;`practices`&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="sb"&gt;`practices`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;`&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="sb"&gt;` = 6 ORDER BY `&lt;/span&gt;&lt;span class="n"&gt;practices&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;` DESC LIMIT 1
=&amp;gt; "/storage/practice/soundfile/07-%E6%9C%80%E5%90%8E%E4%BB%96%E9%80%89%E6%8B%A9%E4%BA%86%E5%90%8E%E8%80%85_.mp3"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="服务器路径"&gt;服务器路径&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;pry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;practices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;soundfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_path&lt;/span&gt;
  &lt;span class="no"&gt;Practice&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="sb"&gt;`practices`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="sb"&gt;`practices`&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="sb"&gt;`practices`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;`&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="sb"&gt;` = 6 ORDER BY `&lt;/span&gt;&lt;span class="n"&gt;practices&lt;/span&gt;&lt;span class="sb"&gt;`.`&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="sb"&gt;` DESC LIMIT 1
=&amp;gt; "/home/study/funny_words/public/storage/practice/soundfile/07-最后他选择了后者_.mp3"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="可能出现的错误"&gt;可能出现的错误&lt;/h2&gt;&lt;h3 id="1、no implicit conversion of nil into String"&gt;1, no implicit conversion of nil into String&lt;/h3&gt;
&lt;p&gt;使用 mount_uploaders, sssss, 复数！我恨复数，我恨复数，我恨复数。&lt;/p&gt;
&lt;h3 id="2、Encoding::UndefinedConversionError  "&gt;2, Encoding::UndefinedConversionError  "\xE6" from ASCII-8BIT to UTF-8&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://dw.gsenglish.cn:5543/uploads/2021/08/image-20210821175802042.png" title="" alt="image-20210821175802042"&gt;&lt;/p&gt;

&lt;p&gt;编码问题导致的，经分析，是 headers 中的 filename 编码有问题，见上图 (吐槽：外国人开发的工具总是容易产生国际化问题)，经搜索官网 issues，可能的解决办法如下。&lt;/p&gt;

&lt;p&gt;1、application.rb 中添加 config.encoding = "utf-8"&lt;/p&gt;

&lt;p&gt;2、在 practices_controller.rb 的 practice_params 中添加 prms[:soundfile].first.headers.force_encoding 'utf-8'&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;practice_params&lt;/span&gt;
  &lt;span class="n"&gt;prms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:practice&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:pth_sentence_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:soundfile&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;prms&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:soundfile&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;force_encoding&lt;/span&gt; &lt;span class="s1"&gt;'utf-8'&lt;/span&gt;
  &lt;span class="n"&gt;prms&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就会把 headers 强制转成 utf-8 编码。&lt;/p&gt;
&lt;h3 id="3、Can't verify CSRF token authenticity"&gt;3, Can't verify CSRF token authenticity&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://dw.gsenglish.cn:5543/uploads/2021/08/image-20210821184643969.png" title="" alt="image-20210821184643969"&gt;&lt;/p&gt;

&lt;p&gt;使用 API 上传的时候，可能会遇到 CSRF 问题，这是为了防止一些网络攻击，可以通过在 application.rb 中添加&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_protect_from_forgery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来关闭此项检查，这样开发时就方便喽。注意！在生产环境中要使用其它安全令牌比如 jwt 来进行合法用户校验。&lt;/p&gt;
&lt;h2 id="运行环境"&gt;运行环境&lt;/h2&gt;
&lt;p&gt;ruby 2.6.5, rails 6.0.4, mysql 5.7(utf8mb4)&lt;/p&gt;</description>
      <author>dayudodo</author>
      <pubDate>Sat, 21 Aug 2021 20:02:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/41602</link>
      <guid>https://ruby-china.org/topics/41602</guid>
    </item>
    <item>
      <title>快速搭建自定义域名的 Rails 5 HTTPS 开发服务器</title>
      <description>&lt;p&gt;https 已经是大势所趋，越来越多的 Web API 调用依赖 HTTPS 环境，苹果公司也要求其 ios App 强制使用 https。本人想写点个微信小程序玩下，发现也得用 https 才好使。&lt;a href="https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-network.html" rel="nofollow" target="_blank" title=""&gt;微信小程序服务器只支持 https&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;研究了一下如何在 rails 开发的时候开启 https，最终发现使用 nginx 开启反向代理是最简单可行的办法，曾经试过用 rails s -b 'ssl://localhost:3000?key=path/to/file/localhost.key&amp;amp;cert=path/to/file/localhost.crt'来开启 https, 用倒是能用，就是后台错误一大堆。&lt;/p&gt;
&lt;h2 id="本人开发环境"&gt;本人开发环境&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Rails 5.1.4&lt;/li&gt;
&lt;li&gt;Mac OS X 10.12.5 (El Capitan)&lt;/li&gt;
&lt;li&gt;OpenSSL 1.0.2l(brew install openssl)&lt;/li&gt;
&lt;li&gt;nginx 1.12.2&lt;/li&gt;
&lt;li&gt;Google Chrome 64.0.3282.140 (64-bit)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="生成根证书"&gt;生成根证书&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="nt"&gt;-keyout&lt;/span&gt; server.key &lt;span class="nt"&gt;-out&lt;/span&gt; server.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;填写有关项，想省事儿的话只需要填写 Common Name, 比如使用星号开头的*.blog.me。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Generating a 2048 bit RSA private key
..............+++
.........................................................................................+++
writing new private key to &lt;span class="s1"&gt;'server.key'&lt;/span&gt;
&lt;span class="nt"&gt;-----&lt;/span&gt;
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter &lt;span class="s1"&gt;'.'&lt;/span&gt;, the field will be left blank.
&lt;span class="nt"&gt;-----&lt;/span&gt;
Country Name &lt;span class="o"&gt;(&lt;/span&gt;2 letter code&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;AU]:
State or Province Name &lt;span class="o"&gt;(&lt;/span&gt;full name&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;Some-State]:
Locality Name &lt;span class="o"&gt;(&lt;/span&gt;eg, city&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt;:
Organization Name &lt;span class="o"&gt;(&lt;/span&gt;eg, company&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;Internet Widgits Pty Ltd]:
Organizational Unit Name &lt;span class="o"&gt;(&lt;/span&gt;eg, section&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt;:
Common Name &lt;span class="o"&gt;(&lt;/span&gt;e.g. server FQDN or YOUR name&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt;:&lt;span class="k"&gt;*&lt;/span&gt;.blog.me
Email Address &lt;span class="o"&gt;[]&lt;/span&gt;:example@blog.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会生成两个文件：证书文件 server.crt 和密钥文件 server.key，下面的 nginx 中会用到&lt;/p&gt;
&lt;h2 id="设置hosts添加自定义域名"&gt;设置 hosts 添加自定义域名&lt;/h2&gt;
&lt;p&gt;打开/etc/hosts 文件，添加此句：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;127.0.0.1       1.blog.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建议使用免费的 hosts 管理工具：SwitchHosts: &lt;a href="https://github.com/oldj/SwitchHosts/releases" rel="nofollow" target="_blank" title=""&gt;点击下载&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="启动Rails开发服务器"&gt;启动 Rails 开发服务器&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;rails server &lt;span class="nt"&gt;-b&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;-p&lt;/span&gt; 3000
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="设置nginx"&gt;设置 nginx&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;安装 nginx: 
&lt;code&gt;bash
brew install nginx
&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;修改 nginx 配置，配置文件位于：/usr/local/etc/nginx/nginx.conf&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;worker_processes&lt;/span&gt;  &lt;span class="m"&gt;1&lt;/span&gt;;

&lt;span class="n"&gt;events&lt;/span&gt; {
    &lt;span class="n"&gt;worker_connections&lt;/span&gt;  &lt;span class="m"&gt;1024&lt;/span&gt;;
}

&lt;span class="n"&gt;http&lt;/span&gt; {
    &lt;span class="n"&gt;include&lt;/span&gt;       &lt;span class="n"&gt;mime&lt;/span&gt;.&lt;span class="n"&gt;types&lt;/span&gt;;
    &lt;span class="n"&gt;default_type&lt;/span&gt;  &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;octet&lt;/span&gt;-&lt;span class="n"&gt;stream&lt;/span&gt;;

    &lt;span class="c"&gt;#log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
&lt;/span&gt;    &lt;span class="c"&gt;#                  '$status $body_bytes_sent "$http_referer" '
&lt;/span&gt;    &lt;span class="c"&gt;#                  '"$http_user_agent" "$http_x_forwarded_for"';
&lt;/span&gt;
    &lt;span class="c"&gt;#access_log  logs/access.log  main;
&lt;/span&gt;
    &lt;span class="n"&gt;sendfile&lt;/span&gt;        &lt;span class="n"&gt;on&lt;/span&gt;;
    &lt;span class="c"&gt;#tcp_nopush     on;
&lt;/span&gt;
    &lt;span class="c"&gt;#keepalive_timeout  0;
&lt;/span&gt;    &lt;span class="n"&gt;keepalive_timeout&lt;/span&gt;  &lt;span class="m"&gt;65&lt;/span&gt;;

    &lt;span class="c"&gt;#gzip  on;
&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; {
        &lt;span class="n"&gt;listen&lt;/span&gt;       &lt;span class="m"&gt;8080&lt;/span&gt;;
        &lt;span class="n"&gt;server_name&lt;/span&gt;  &lt;span class="n"&gt;localhost&lt;/span&gt;;

        &lt;span class="c"&gt;#charset koi8-r;
&lt;/span&gt;
        &lt;span class="c"&gt;#access_log  logs/host.access.log  main;
&lt;/span&gt;
        &lt;span class="n"&gt;location&lt;/span&gt; / {
            &lt;span class="n"&gt;root&lt;/span&gt;   &lt;span class="n"&gt;html&lt;/span&gt;;
            &lt;span class="n"&gt;index&lt;/span&gt;  &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;htm&lt;/span&gt;;
        }

        &lt;span class="c"&gt;#error_page  404              /404.html;
&lt;/span&gt;
        &lt;span class="c"&gt;# redirect server error pages to the static page /50x.html
&lt;/span&gt;        &lt;span class="c"&gt;#
&lt;/span&gt;        &lt;span class="n"&gt;error_page&lt;/span&gt;   &lt;span class="m"&gt;500&lt;/span&gt; &lt;span class="m"&gt;502&lt;/span&gt; &lt;span class="m"&gt;503&lt;/span&gt; &lt;span class="m"&gt;504&lt;/span&gt;  /&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;
        &lt;span class="n"&gt;location&lt;/span&gt; = /&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; {
            &lt;span class="n"&gt;root&lt;/span&gt;   &lt;span class="n"&gt;html&lt;/span&gt;;
        }

    }
    &lt;span class="c"&gt;# HTTPS server
&lt;/span&gt;    &lt;span class="n"&gt;server&lt;/span&gt; {
        &lt;span class="n"&gt;listen&lt;/span&gt;       &lt;span class="m"&gt;4433&lt;/span&gt;;
        &lt;span class="n"&gt;server_name&lt;/span&gt;  &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="n"&gt;blog&lt;/span&gt;.&lt;span class="n"&gt;me&lt;/span&gt;;

        &lt;span class="n"&gt;ssl&lt;/span&gt;                  &lt;span class="n"&gt;on&lt;/span&gt;;
        &lt;span class="n"&gt;ssl_certificate&lt;/span&gt;      &lt;span class="n"&gt;server&lt;/span&gt;.&lt;span class="n"&gt;crt&lt;/span&gt;;
        &lt;span class="n"&gt;ssl_certificate_key&lt;/span&gt;  &lt;span class="n"&gt;server&lt;/span&gt;.&lt;span class="n"&gt;key&lt;/span&gt;;
        &lt;span class="n"&gt;ssl_session_timeout&lt;/span&gt;  &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;;

        &lt;span class="n"&gt;ssl_protocols&lt;/span&gt; &lt;span class="n"&gt;TLSv1&lt;/span&gt; &lt;span class="n"&gt;TLSv1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;TLSv1&lt;/span&gt;.&lt;span class="m"&gt;2&lt;/span&gt;;
        &lt;span class="n"&gt;ssl_ciphers&lt;/span&gt;  &lt;span class="n"&gt;HIGH&lt;/span&gt;:!&lt;span class="n"&gt;aNULL&lt;/span&gt;:!&lt;span class="n"&gt;MD5&lt;/span&gt;;
        &lt;span class="n"&gt;ssl_prefer_server_ciphers&lt;/span&gt;   &lt;span class="n"&gt;on&lt;/span&gt;;

        &lt;span class="n"&gt;location&lt;/span&gt; / {
          &lt;span class="n"&gt;proxy_pass&lt;/span&gt;          &lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;:&lt;span class="m"&gt;3000&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt;    &lt;span class="n"&gt;Host&lt;/span&gt;             $&lt;span class="n"&gt;host&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt;    &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Real&lt;/span&gt;-&lt;span class="n"&gt;IP&lt;/span&gt;        $&lt;span class="n"&gt;remote_addr&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt;    &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;For&lt;/span&gt;  $&lt;span class="n"&gt;proxy_add_x_forwarded_for&lt;/span&gt;;
          &lt;span class="n"&gt;proxy_set_header&lt;/span&gt;    &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;Proto&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;;
        }
    }
    &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="n"&gt;servers&lt;/span&gt;/*;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中的 HTTPS server 就是相关的 https 配置，端口号设置为 4433，因为默认的 443 是被 mac 系统屏蔽的。server_name 就填咱们的自定义域名：1.blog.me。ssl_certificate 对应的是证书文件，ssl_certificate_key 则是你的密钥文件。在 location 中的 proxy_pass 设置的是反向代理地址，也就是本机的 rails 开发服务器地址，其它照搬即可。配置好后建议使用 sudo nginx -t 测试下避免有写错的情况。&lt;/p&gt;
&lt;h3 id="使用brew services restart nginx来重启nginx！"&gt;使用 brew services restart nginx 来重启 nginx！&lt;/h3&gt;&lt;h2 id="信任证书"&gt;信任证书&lt;/h2&gt;
&lt;p&gt;使用浏览器打开&lt;a href="https://1.blog.me:4433" rel="nofollow" target="_blank"&gt;https://1.blog.me:4433&lt;/a&gt; 后会告诉你 authority invalid
&lt;img src="https://l.ruby-china.com/photo/2018/109fe5a2-e854-462f-a302-942cf16cb9ba.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;此时打开 developer tools&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/d156c804-be15-444b-8158-7179c6ddfaf6.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;点击 View certificate&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/7a9d208d-d84b-49c7-995a-519ff2da3305.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;拖动&lt;strong&gt;证书图标&lt;/strong&gt;到桌面生成证书文件，并双击打开&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/35a849db-d66e-4270-8e18-da981768d1b5.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;设置始终信任，输入超级用户密码后确认&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/90cbb47d-13db-4d25-b187-fbc9bd746d0d.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;一切正常的话应该是这样：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/c3a04fb5-3aaf-4e97-9e9b-f227fc9ffa34.jpg!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="小成功"&gt;小成功&lt;/h2&gt;
&lt;p&gt;通过 safari 来访问是 OK 的&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/0a743ad1-305e-46c4-9446-937f8b08a671.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;chrome 会报错（老版本的 chrome 反倒不会）：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/a44dc481-adfc-43ed-939a-e59e8219bf7b.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;可以点击高级中的继续前往来访问网站，只是左边会显示不安全：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/66ba6d85-e4c6-4761-bcc8-6d5a3a88b47e.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;chrome 出错是因为其已经不再支持证书中的 commonName 匹配，实际上，自 2017 年 1 月起需要 subjectAltName 这个规则了。&lt;/p&gt;
&lt;h2 id="解决办法"&gt;解决办法&lt;/h2&gt;
&lt;p&gt;用根 SSL 证书来为本地自定义域名 1.blog.me 专门发行证书。
创建 1.blog.me.csr.cnf 配置文件&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[dn]
C=US
ST=RandomState
L=RandomCity
O=RandomOrganization
OU=RandomOrganizationUnit
emailAddress=hello@example.com
CN = 1.blog.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用此配置 1.blog.me.csr.cnf 来创建证书密钥。该密钥存储在 1.blog.me.key&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl req -new -sha256 -nodes -out 1.blog.me.csr -newkey rsa:2048 -keyout 1.blog.me.key -config &amp;lt;(cat 1.blog.me.csr.cnf)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Generating a 2048 bit RSA private key
.....................................................+++
....................................................................................................+++
writing new private key to '1.blog.me.key'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;创建有 subjectAltName 的配置文件 v3.ext&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = 1.blog.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建域名证书：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl x509 -req -in 1.blog.me.csr -CA server.crt -CAkey server.key -CAcreateserial -out 1.blog.me.crt -days 500 -sha256 -extfile v3.ext
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Signature ok
subject=/C=US/ST=RandomState/L=RandomCity/O=RandomOrganization/OU=RandomOrganizationUnit/emailAddress=hello@example.com/CN=1.blog.me
Getting CA Private Key&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;修改 nginx.conf 中的证书和密钥项：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
        ssl_certificate      1.blog.me.crt;
        ssl_certificate_key  1.blog.me.key;
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启 nginx: &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew services restart nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chrome 中再次打开：&lt;a href="https://1.blog.me:4433" rel="nofollow" target="_blank"&gt;https://1.blog.me:4433&lt;/a&gt;  PERFECT！&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/ffec055c-e954-4000-ab0c-81901ce664c5.jpg!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="优点"&gt;优点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;通过&lt;a href="http://localhost:3000" rel="nofollow" target="_blank"&gt;http://localhost:3000&lt;/a&gt; 也能访问本地开发服务器&lt;/li&gt;
&lt;li&gt;puma 在 http 模式下更少出错&lt;/li&gt;
&lt;li&gt;测试方便&lt;/li&gt;
&lt;li&gt;真实的产品服务器也可以这样开启 https&lt;/li&gt;
&lt;li&gt;开发服务器可以与产品服务器使用相同的域名（别忘了用 SwitchHost 切换），申请到的 SSL 证书也能用于开发 (好处是不需要添加信任)&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>dayudodo</author>
      <pubDate>Thu, 08 Feb 2018 16:38:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/35013</link>
      <guid>https://ruby-china.org/topics/35013</guid>
    </item>
    <item>
      <title>Windows 下 Bundler::Fetcher::CertificateFailureError 出错的解决办法</title>
      <description>&lt;p&gt;安装新版本的 railsInstaller5.1.4 一切正常，等到 bundle install 的时候出现了这样的错误：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retrying fetcher due to error (2/4): Bundler::Fetcher::CertificateFailureError Could not verify the SSL certificate for https://gems.ruby-china.org/.
There is a chance you are experiencing a man-in-the-middle attack, but most likely your system doesn't have the CA certificates needed for verification. For information about OpenSSL certificates, see http://bit.ly/ruby-ssl. To connect without using SSL, edit your Gemfile sources and change 'https' to 'http'.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;google 后发现解决办法，需要个 carcert.pem 的文件，可以用下面的脚本自动下载此文件并加载到当前环境中，请先创建一个目录名称为：C:\RailsInstaller&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;'net/http'&lt;/span&gt;

&lt;span class="c1"&gt;# create a path to the file "C:\RailsInstaller\cacert.pem"&lt;/span&gt;
&lt;span class="n"&gt;cacert_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;%w{c: RailsInstaller cacert.pem}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"curl.haxx.se"&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;http&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/ca/cacert.pem"&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;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt;
    &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacert_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"wb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;A bundle of certificate authorities has been installed to"&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;RailsInstaller&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;cacert.pem&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"* Please set SSL_CERT_FILE in your current command prompt session with:"&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"     set SSL_CERT_FILE=C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;RailsInstaller&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;cacert.pem"&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"* To make this a permanent setting, add it to Environment Variables"&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"  under Control Panel -&amp;gt; Advanced -&amp;gt; Environment Variables"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="nb"&gt;abort&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; A cacert.pem bundle could not be downloaded."&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;永久的解决办法是把变量 SSL_CERT_FILE 添加到环境变量中，如图：
&lt;img src="https://l.ruby-china.com/photo/2017/936aa90e-39ff-4bff-a224-7397a1b5ca70.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;或者你自己手动下载 carcert.pem 文件并设置好环境变量 (&lt;a href="http://curl.haxx.se/ca/cacert.pem" rel="nofollow" target="_blank"&gt;http://curl.haxx.se/ca/cacert.pem&lt;/a&gt;)，
本文代码及方法参考自：(&lt;a href="https://gist.github.com/fnichol/867550" rel="nofollow" target="_blank"&gt;https://gist.github.com/fnichol/867550&lt;/a&gt;)&lt;/p&gt;</description>
      <author>dayudodo</author>
      <pubDate>Mon, 13 Nov 2017 11:49:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/34564</link>
      <guid>https://ruby-china.org/topics/34564</guid>
    </item>
  </channel>
</rss>
