LoginSignup
3
1

More than 5 years have passed since last update.

TEMPファイルを作らずにフォームで受け取ったZIPをメモリ上で展開・解凍する手順

Last updated at Posted at 2018-07-01

はじめに

ZIPファイルを受け取ったあと,なんとなくTEMPファイルを作ったりしてディレクトリやサーバの容量を無駄に食うことがあります.TEMPファイルを作らずフォームから受け取ったZIPをメモリ上で展開・解凍する手順について説明します.

開発環境

  • Ruby 2.3.3
  • Sinatra
Gemfile
gem "sinatra"
gem "rubyzip"

手順

フォームからの受け取り

ここはそれほど難しくはなく,form enctype="multipart/formdata"を指定するだけです.

upload.slim
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に格納します.

config.rb
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のコードを以下のように修正します.

config.rb
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も固まらないので,ファイルは分割して埋め込む,もしくはレンダリングに関わらないように埋め込むのがベストだと思います.

3
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
3
1