Pular para o conteúdo

Rails + carrierwave + unicorn + nginx + progress module

Postado em

No último post eu expliquei como instalar e configurar o nginx com upload e upload progress module. Agora vamos fazer nossa aplicação funcionar com o Carrierwave e Unicorn.

Configurando o nginx

Primeiro vamos alterar o arquivo de configuração do nginx da nossa aplicação, abra o arquivo /opt/nginx/conf/sites-available/app.com no seu editor de textos favorito e adicione o seguinte conteúdo:

upstream app_unicorn {
  server unix:/tmp/unicorn.app.com.sock fail_timeout=0;
}

upload_progress proxied 1m;

server {
  listen 80;
  server_name arquivos.app.com www.arquivos.app.com;

  if ($host = 'www.arquivos.app.com' ) {
    rewrite  ^/(.*)$  http://arquivos.arquivos.app.com/$1  permanent;
  }

  root /srv/rails/app.com/current/public;

  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_unicorn;
  }

  # Rota para uploads da aplicação
  location /arquivos {
    # Pra onde o nginx envia a requisição quando o upload terminar
    upload_pass @unicorn;

    # Local em que o nginx vai subir o arquivo temporario
    upload_store /srv/rails/app.com/current/public/uploads/tmp 1;

    # Permissão nos arquivos enviados
    upload_store_access user:rw group:rw all:r;

    # Campos passados no request body para o Rails
    upload_set_form_field $upload_field_name[original_filename] "$upload_file_name";
    upload_set_form_field $upload_field_name[content_type] "$upload_content_type";
    upload_set_form_field $upload_field_name[path] "$upload_tmp_path";

    upload_pass_form_field "^X-Progress-ID$|^authenticity_token$";

    upload_cleanup 400 404 499 500-505;

    track_uploads proxied 30s;
  }

  location /uploads {
    # Força download dos arquivos que estiverem dentro de uploads
    if ($request_filename !~* ^.*?\.(jpg)|(png)|(gif)) {
      add_header Content-Type "application/octet-stream";
      add_header Content-Disposition "attachment; filename=$1";
    }
  }

  location ^~ /progress {
    upload_progress_json_output;
    report_uploads proxied;
  }

  client_max_body_size 4G;
  keepalive_timeout 10;
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /srv/rails/app.com/current/public;
  }
}

O arquivo está bem comentado e explicado, tem a configuração do Unicorn e a parte que nos interessa do upload.

A aplicação responde com o progresso do arquivo enviado na url /progress, e eu uso a url /uploads pra forçar o download dos arquivos que foram enviados.

Mas ainda é necessário criar os diretórios temporários que o nginx usará:

$ cd /srv/rails/app.com/current/public/uploads/tmp
$ mkdir 0 1 2 3 4 5 6 7 8 9
$ sudo chown -R nginx:nginx /srv/rails/app.com/current/public/uploads/tmp

Configurando a aplicação Rails

Agora é a hora de configurar algumas partes da nossa aplicação, primeiro vamos alterar o Model que receberá o upload:

# app.com/app/models/arquivo.rb
class Arquivo
  include Mongoid::Document
  # ...

  attr_accessible :arquivo, :original_name, :content_type, :path

  mount_uploader :arquivo, ArquivoUploader

  # ...
end

Agora criamos o ArquivoUploader.rb:

# app.com/app/uploaders/arquivo_uploader.rb
# encoding: utf-8
require 'carrierwave/processing/mime_types'
class ArquivoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MimeTypes

  process :set_content_type

  def move_to_store
    true
  end

  # Choose what kind of storage to use for this uploader:
  storage :file

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "#{Rails.root}/public/uploads"
  end
end

Criamos então uma rota para receber o formulário de arquivos:

# app.com/config/routes.rb
resources :arquivos, :only => [:new, :create]

E a ação correspondente no Controller para salvar o arquivo enviado:

# app.com/app/controllers/arquivos_controller.rb
  def create
    @arquivo = Arquivo.new(params[:arquivo])

    # Para receber o arquivo do nginx em produção
    if Rails.env != "development"
      @arquivo.arquivo = ActionDispatch::Http::UploadedFile.new(
        filename: params['arquivo']['arquivo']['original_filename'],
        tempfile: File.open(params['arquivo']['arquivo']['path'])
      )
    end

    if @arquivo.save
      respond_to do |format|
        format.html {
          render :json => @arquivo,
          :content_type => 'text/html',
          :layout => false
        }
        format.json {
          render :json => @arquivo
        }
      end
    else
      render :json => @arquivo.errors, :status => :unprocessable_entity
    end
  end

A parte mágica aqui é simular um ActionDispatch::Http::UploadedFile sendo passado para o Carrierwave. Dessa forma, a aplicação recebe o arquivo do nginx e o Carrierwave move o arquivo do diretório temporário para o store_dir configurado anteriormente.