はじめに
ZIPファイルを受け取ったあと,なんとなくTEMPファイルを作ったりしてディレクトリやサーバの容量を無駄に食うことがあります.TEMPファイルを作らず,フォームから受け取ったZIPをメモリ上で展開・解凍する手順について説明します.
開発環境
- Ruby 2.3.3
- Sinatra
gem "sinatra"
gem "rubyzip"
手順
フォームからの受け取り
ここはそれほど難しくはなく,form enctype="multipart/formdata"
を指定するだけです.
form method="post" action="/convert" enctype="multipart/form-data"
p
label for="uploadFile"
| ZIPファイル:
input type="file" name="uploadFile" id="uploadFile"
p
input type="submit" value="送信"
Sinatraでの受け取り
Sinatraではformから受け取ったpostのパラメータをparamsに格納します.
require "zip"
require "base64"
# 省略
post '/convert' do
data = params["uploadFile"]["tempfile"]
end
zipの展開
展開したzipからファイル名とデータ本体を受け取り,それらの対応を返す関数を作ります.
def extract(data)
retval = {contents: [], streams: []}
Zip::InputStream.open(data) do |ios|
while (entry = ios.get_next_entry)
retval[:contents] << entry.name
retval[:streams] << entry.get_input_stream.sysread
end
end
retval
end
zipの圧縮
受け取ったファイル名とデータ本体の対応させてファイルを圧縮します.
下の例では, zos.close_buffer
でStringIOを返す関数を作っています.
def archive(contents, streams)
Zip::OutputStream.write_buffer do |zos|
for i in 0...contents.size
zos.put_next_entry(contents[i])
zos.write streams[i]
end
zos.close_buffer
end
end
Zip::OutputStream.write_buffer
は内部でyieldしているので, ブロックを与えないと余裕でLocalJumpErrorを吐く ので注意してください.昔からのくせでf = File.open(path)
みたいな処理を書くと怒られます.
Base64へ変換
Base64.strict_encode64
で改行なしのBase64を作ります.
Sinatraの修正
以上の関数を使ってSinatraのコードを以下のように修正します.
require "zip"
require "base64"
# 省略
post '/convert' do
data = params["uploadFile"]["tempfile"]
pair = extract(data)
out = archive(pair[:contents], pair[:streams])
Base64.strict_encode64(out.rewind.read)
end
StringIO.rewind
は現在位置をファイルの先頭に戻す関数です.
これで,TEMPファイルを全く作らずに,メモリ上だけで完結するような処理を実現できます.
終わりに
以上のようなコードを応用すると,
- 受け取ったZIPファイルに何かを差し込みたい
- ZIPファイルの検証や確認
- ダウンロード予定のZIPファイルをシングルページに埋め込みたい
などなど,ファイルIOやリクエストを極力減らせます.シングルページだと30%ぐらいレンダリングが速くなるらしいですよ.
余談
どうでもいいですけど,なんでcontentsとstreamsをわざわざ分けたんでしょうね.例えば,contents.size != streams.size
が成立する可能性を考えてないです.
コードを書いているときにスペースを連打しすぎて頭がおかしくなったのでしょうか? Qiitaのエディタは自動インデントが効かないのでカロリーを消費しすぎます.
コードが非常にキモいので書き方を真似しないように.エラったらごめんなさい.
追記(2018/07/03)
試しに10MB程度のZIPを差し込むとChromeでは問題なく動作しましたが,Firefoxは固まりました.たぶん,ブラウザ本体がBase64を解釈する処理が重いのかと.Base64を埋め込むなら画像とかにしましょう(戒め)
ちなみに,CADソフトのFusion360でシミュレーション・レポートを書き出すと,画像がBase64で埋め込まれています.HTML単体で10MBぐらいありますが開くのにそれほど時間がかからないです.Firefoxも固まらないので,ファイルは分割して埋め込む,もしくはレンダリングに関わらないように埋め込むのがベストだと思います.