LoginSignup
1
1

More than 5 years have passed since last update.

Rails+CarrierwaveにGzip圧縮するProcessを追加する

Last updated at Posted at 2016-08-26

独自のProcessを追加する方法が知りたかったので調べてみました。

ポイント

ポイントは、

  1. /tmp フォルダにアップロードされたファイル(current_path)を加工して上書きする
  2. ファイル名はfull_filenameメソッドを上書きして変更する
  3. Processでは/tmp内への保存まででよく、モデルがsaveされるとstore!/uploadsS3へ転送)される

といった感じでした。

注意点

storeされる時にバージョンのファイル名が特定できないと転送されないので、Processの中でファイル名を変えたりとかはNGな感じです。かっこ悪いけど都度full_filenameに定義した方がよさそうです。
まあ、Processでファイル名を変えると他のProcessと共存しにくくなるので、これでいいのかもです。

サンプルソース

ダウンロード / Gist

config/initializers/extensions/carrierwave/processings/gzip.rb
module CarrierWave
  module Gzip
    extend ActiveSupport::Concern

    module ClassMethods
      def gzip_compress
        process :gzip
      end
    end

    def gzip_compress
      # move upload to local cache
      cache_stored_file! if !cached?

      directory = File.dirname( current_path )

      # move upload to tmp file - encoding result will be saved to
      # original file name
      tmp_path   = File.join( directory, "tmpfile" )
      File.rename current_path, tmp_path

      # compress
      content = File.read(tmp_path)
      gzip_content = ActiveSupport::Gzip.compress(content)
      File.open(current_path, "wb") do |f|
        f.write(gzip_content)
      end

      # delete tmp file
      File.delete tmp_path
    end

    private
      def prepare!
        cache_stored_file! if !cached?
      end
  end
end

使い方

app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base

  include CarrierWave::RMagick
  include CarrierWave::Gzip

  storage :file

  # Gzip version
  version :gz do
    process :gzip_compress
    def full_filename(for_file)
      for_file + ".gz"
    end
  end

  # 既存のprocessとの組み合わせも可能
  # Thumbnail & Gzip version
  version :thumb do
    process :resize_to_fit => [100, 100]
    process :gzip_compress
    def full_filename(for_file)
      super(for_file) + ".gz"
    end
  end

  # gz版があればgzのURLを、なければoriginalのURLを返す
  def gzurl
    if self.gz.present?
      self.gz.url
    else
      self.url
    end
  end
end

この設定でファイルをアップロードすると、アップロードフォルダはこんな感じになります。

original.png
original.png.gz
thumb_original.png.gz

あとはWEBサーバーで、
Content-Encoding: gzip出してあげたり、gzなしのリクエストでもAccept-Encoding:gzipがあればgzありのファイルを返してあげたりしてあればよさげです。

ついでに

本番ではなかなかRailsから静的ファイルを配信したりしないですが、開発環境だとrails sしてすぐ動作確認したい方もいると思うので、RailsからもちゃんとContent-Encoding: gzipを出しておければいいかなと思います。
そこで、ミドルウェアというものを作ってヘッダーを差し込んでみます。

やってみる

ダウンロード / Gist

config/initializers/middlewares/gzip_header.rb
module Rack
  class GzipHeader

    def initialize(app, options = {})
      @app = app
      @start_with = options[:start_with] || '/'
      @end_with   = options[:end_with]   || '.gz'
    end

    def call(env)
      status, headers, body = result = @app.call(env)

      case status
      when 200  # OK
        path = URI.unescape(env['PATH_INFO'])
        if path.start_with?(@start_with) && path.end_with?(@end_with)
          enc = env['HTTP_ACCEPT_ENCODING']
          if enc && enc.include?('gzip')
            result[1]['Content-Encoding'] = 'gzip'
          end
        end
      end
      result
    end

  end
end

使ってみる

config/application.rb
module App
  class Application < Rails::Application
    config.middleware.insert_before ActionDispatch::Static, 
      "Rack::GzipHeader", {
        start_with: "/uploads/", 
        end_with:   ".gz"
      }

これで、「/uploads/以下の.gzファイルがAccept-Encoding:gzipでリクエストされていたらContent-Encoding: gzipをヘッダーに追加する」ことが出来ました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1