ファイルを分割してアップロードする(Javascript, PHP)
どうもfujiharaです。本日はファイルを分割してアップロードする方法をご紹介します
背景
ファイルをアップロードするシステムを作るときに容量の要望にアップロードサイズ、アップロード時間の変更で対応するには 大きすぎるサイズだったので分割してアップロードして、結合するという方法にしました。
ファイル構成
以下のようになります。uploadsはアップロード先になりますので作成しておいて下さい。
├── js
│ └── upload.js
├── index.html
├── upload.php
└── uploads
HTML
まずは簡易HTMLです
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Upload</title>
</head>
<body>
<p>input size <span id="input-size"></span></p>
<p>progress <span id="progress"></span>%</p>
<form id="form">
<input id="file" type="file" name="file"/>
<button type="submit">アップロード</button>
</form>
<p>upload size <span id="upload-size"></span></p>
<script src="/js/upload.js"></script>
</body>
</html>
javascript
サンプルなので変数名などはすっ飛ばしてもらえると
(() => {
const form = document.getElementById('form');
let file = null;
let slice_size = 2 * 1024 * 1024; //切り取るサイズ 2M
const input = form.querySelector('input');
let size = 0;
let count = 0;
let postForm = null;
let splitData = null;
const inputSize = document.getElementById('input-size'); //インプットサイズ
const uploadSize = document.getElementById('upload-size'); //アップロード結合サイズ
const progress = document.getElementById('progress'); //進捗
form.addEventListener('submit', async (e) => {
e.preventDefault();
file = input.files[0];
size = file.size;
const name = `${(new Date()).getTime()}-${file.name}`; //アップロード時の名前を固定するように
inputSize.innerText = size.toString(); //インプット時に計測されるファイルサイズを表示
count = Math.ceil(size / slice_size); //分割数を計算
for (let k = 0; k < count; k++) { //分割アップロード実施
splitData = file.slice(k * slice_size, (k + 1) * slice_size); //該当箇所をスライス
postForm = new FormData(); //アップロード用フォーム
postForm.append('file', splitData); //スライスしたファイルをフォームにセット
postForm.append('name', name); //名前
progress.innerText = Math.ceil(100 * (k + 1) / count).toString(); //進捗率を更新
let result = await fetch('/upload.php', {
body: postForm,
method: 'POST',
headers: { Accept: 'application/json' }
}).then(res => res.json());
uploadSize.innerText = result.size; //アップロード結合したサイズを表示
await new Promise(res => setTimeout(() => {
res();
}, 1000)); //ゆっくり見せたいのでpost完了後、次のpostまで1000ms
}
return false;
});
})();
PHP
PHPは以下になります(簡素)
<?php
$file_place = __DIR__."/uploads/{$_POST['name']}"; //結合するファイル場所
$file = $_FILES['file'];
file_put_contents($file_place, file_get_contents($file['tmp_name']), FILE_APPEND); //FILE_APPENDで足していく
echo json_encode(['size' => filesize($file_place)]); //結果を出力
動作
以下のようになります。PHPが入っていれば、ビルトインサーバーでも動きます。 ファイルを選択してアップロードを押すと、フロント側でのファイルサイズを表示し、アップロード毎に進捗率、アップロードされたファイルのサイズを表示します。
まとめ
いかがでしたでしょうか?簡単に実装できると思いませんか?
実際にはこの他に、分割アップロード時のエラー・リトライ処理・セキュリティ設定
をしっかりする必要がありますのでご注意下さい。
ライブラリだとResumable.jsが有名ですね