独自のProcessを追加する方法が知りたかったので調べてみました。
ポイント
ポイントは、
-
/tmp
フォルダにアップロードされたファイル(current_path
)を加工して上書きする - ファイル名は
full_filename
メソッドを上書きして変更する - Processでは
/tmp
内への保存まででよく、モデルがsaveされるとstore!
(/uploads
やS3
へ転送)される
といった感じでした。
注意点
store
される時にバージョンのファイル名が特定できないと転送されないので、Processの中でファイル名を変えたりとかはNGな感じです。かっこ悪いけど都度full_filename
に定義した方がよさそうです。
まあ、Processでファイル名を変えると他のProcessと共存しにくくなるので、これでいいのかもです。
サンプルソース
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
を出しておければいいかなと思います。
そこで、ミドルウェアというものを作ってヘッダーを差し込んでみます。
やってみる
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
をヘッダーに追加する」ことが出来ました。