Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

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

GRGSIBERIA
なんでもやる人.元未踏クリエータ.三次元幾何学と音響工学を少々.
http://www.grgsiberia.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした