大容量のファイルをアップロードする
File Browserの紹介と背景
File BrowserというGoで書かれたウェブサーバーがある。 しばらくはMinIOを使ってアップロードすることもあったが、MinIO上にアップロードしたファイルを直接別のアプリケーションから利用するのはひと手間かかる。
File Browserはもともとその名の通りファイルの一覧を表示することもできるが、簡易なアップローダーとしても使えるようだ。GitHub上で26K以上ものスターがついているほどポピュラーなプロジェクトなのだが、APIのドキュメントは現在も用意されていない。
Node.jsによるアップロード方法とRubyでの実装
ブラウザのネットワークタブなどを使ってある程度は推測できるが、幸いNode.JSでの方法がissueに残っていた。
このコードを参考にHTTPartyを使って単純なファイルをアップローダーを記述した:
class Filebrowser
include HTTParty
def self.auth(username, password)
headers = { "Content-Type" => "application/json" }
body = { username:, password: }.to_json
post("/api/login", headers:, body:).then do |res|
new(res.body) if res.success?
end
end
# ログインしたトークンを格納する
def initialize(token)
@token = token
end
# `/Media`に保存する場合は`/api/resources/Media`を指定
def upload(file_path, mime_type = "text/plain")
url = "/api/resources/Media/#{File.basename(file_path)}?override=true"
headers = { "Content-Type" => mime_type, "X-Auth" => @token }
self.class.post(url, headers:, body: File.read(file_path))
end
end
大容量ファイルの問題
このアップローダーは比較的小さめのファイルであれば問題なく動作する。 もしアップロードしたいファイルが1GBを超えていた場合はどうだろうか。
まずは2.8 GBのvideo.mp4
というファイルを用意して、このファイルを実際にアップロードしてみた。
当然ではあるが、このプログラムはメモリを2.8 GB使用している。
デスクトップ環境のようにメモリに余裕があればこのプログラムを実行してもさほど問題はないが、このプログラムをサーバーなどの限られたリソースで実行する場合はアップロードするファイルの容量に左右されてしまう。
Rubyでのストリーム処理による解決策
Node.JSにはfs.createReadStream()
があるのだが、Rubyの場合はどうすればよいだろうか。
class Filebrowser
def upload(file_path)
url = "/api/resources/Media/#{File.basename(file_path)}?override=true"
headers = { "Content-Type" => "application/octet-stream",
"Transfer-Encoding" => "chunked",
"X-Auth" => @token }
self.class.post(url, headers:, body_stream: File.open(file_path, "rb"))
end
end
ヘッダにContent-Type: application-octet-stream
とTransfer-Encoding: chunked
を指定し、body
の代わりにbody_stream
を指定するようにした。
全く同じファイルをアップロードしているが、全体のメモリの使用量は23.3 MBまで抑えることに成功した。
このスクリプトであればファイルのサイズに関係なく、問題なく実行できると思う。