ファイルを分割してアップロードする(Javascript, PHP)

ファイルを分割してアップロードする(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が入っていれば、ビルトインサーバーでも動きます。 ファイルを選択してアップロードを押すと、フロント側でのファイルサイズを表示し、アップロード毎に進捗率、アップロードされたファイルのサイズを表示します。

fujihara_20201224.gif

まとめ

いかがでしたでしょうか?簡単に実装できると思いませんか? 実際にはこの他に、分割アップロード時のエラー・リトライ処理・セキュリティ設定 をしっかりする必要がありますのでご注意下さい。
ライブラリだとResumable.jsが有名ですね

  • このエントリーをはてなブックマークに追加

この記事を読んだ人にオススメ