# 大きいファイルをスライス分割してアップロード
大きいファイルをアップロードする際に、通常のやり方だとサーバー処理のタイムアウトやサイズオーバーでアップロードできない場合が出てきます。短時間で大きいサイズのファイルをサーバーにアップロードするのに、少し工夫が必要。
基本的な考え
クライアント
- javascript slice でファイル分割
- スライスサイズ指定
- 並行処理でサーバーへアップロード
- 同時プロセス数制限
- スライスアップロード
バックエンド
- スライス結合
# javascript slice でファイル分割
File オブジェクトは特別な種類の Blob オブジェクト、Blob データを slice でファイル分割します。
let size = 1024 * 1024 * 2; // スライスサイズ設定 2MB
let fileChunks = [];
let index = 0; // index値
for (let cur = 0; cur < file.size; cur += size) {
fileChunks.push({
hash: index++,
chunk: file.slice(cur, cur + size),
});
}
slice 構文 arr.slice([start[, end]])
# 並行処理でサーバーへアップロード
非同期で並行通信しながら、await Promise.race()で最大数まで制御します。
スライスを順番で送信、送信失敗がある際にあとから再送信。
// 非同期並行処理
const uploadFileChunks = async function (list) {
if (list.length === 0) {
await axios({
method: "get",
url: "/merge",
params: {
filename: file.name,
},
});
return;
}
let pool = []; // 並行処理プール
let max = 5; // 最大並行処理数
let finish = 0; // 送信完了数
let failList = []; // 失敗リスト
for (let i = 0; i < list.length; i++) {
let item = list[i];
let formData = new FormData();
formData.append("filename", file.name);
formData.append("hash", item.hash);
formData.append("chunk", item.chunk);
// アップロードタスク
let task = axios({
method: "post",
url: "/upload",
data: formData,
});
task
.then((data) => {
let index = pool.findIndex((t) => t === task);
pool.splice(index);
})
.catch(() => {
failList.push(item);
})
.finally(() => {
finish++;
// すべてアップロード完了後処理
if (finish === list.length) {
uploadFileChunks(failList);
}
});
pool.push(task);
if (pool.length === max) {
// 並行処理最大数超えないように制御
await Promise.race(pool);
}
}
};
# クライアント全ソース
<section>
<input type="file" id="fileUpload" />
<button id="uploadBtn">upload</button>
</section>
// axios使う
axios.defaults.baseURL = "http://localhost:3000";
let file = null;
document.getElementById("fileUpload").onchange = function ({
target: { files },
}) {
file = files[0];
};
// upload
document.getElementById("uploadBtn").onclick = function () {
if (!file) return;
// スライスプール作成
let size = 1024 * 1024 * 2; // スライスサイズ設定 2MB
let fileChunks = [];
let index = 0; // index値
for (let cur = 0; cur < file.size; cur += size) {
fileChunks.push({
hash: index++,
chunk: file.slice(cur, cur + size),
});
}
// 非同期並行処理
const uploadFileChunks = async function (list) {
if (list.length === 0) {
await axios({
method: "get",
url: "/merge",
params: {
filename: file.name,
},
});
return;
}
let pool = []; // 並行処理プール
let max = 3; // 最大並行処理数
let finish = 0; // 送信完了数
let failList = []; // 失敗リスト
for (let i = 0; i < list.length; i++) {
let item = list[i];
let formData = new FormData();
formData.append("filename", file.name);
formData.append("hash", item.hash);
formData.append("chunk", item.chunk);
// アップロードタスク
let task = axios({
method: "post",
url: "/upload",
data: formData,
});
task
.then((data) => {
let index = pool.findIndex((t) => t === task);
pool.splice(index);
})
.catch(() => {
failList.push(item);
})
.finally(() => {
finish++;
// すべてアップロード完了後処理
if (finish === list.length) {
uploadFileChunks(failList);
}
});
pool.push(task);
if (pool.length === max) {
// 並行処理最大数超えないように制御
await Promise.race(pool);
}
}
};
uploadFileChunks(fileChunks);
};
# バックエンド スライス結合
// upload処理
server.post("/upload", (req, res) => {
const form = new multiparty.Form();
form.parse(req, function (err, fields, files) {
let filename = fields.filename[0];
let hash = fields.hash[0];
let chunk = files.chunk[0];
let dir = `${STATIC_TEMPORARY}/${filename}`;
// console.log(filename, hash, chunk)
try {
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
const buffer = fs.readFileSync(chunk.path);
const ws = fs.createWriteStream(`${dir}/${hash}`);
ws.write(buffer);
ws.close();
res.send(`${filename}-${hash} done`);
} catch (error) {
console.error(error);
res.status(500).send(`${filename}-${hash} failed`);
}
});
});
// 結合処理
server.get("/merge", async (req, res) => {
const { filename } = req.query;
try {
let len = 0;
const bufferList = fs
.readdirSync(`${STATIC_TEMPORARY}/${filename}`)
.map((hash, index) => {
const buffer = fs.readFileSync(
`${STATIC_TEMPORARY}/${filename}/${index}`
);
len += buffer.length;
return buffer;
});
// 結合
const buffer = Buffer.concat(bufferList, len);
const ws = fs.createWriteStream(`${STATIC_FILES}/${filename}`);
ws.write(buffer);
ws.close();
res.send(`merge success`);
} catch (error) {
console.error(error);
}
});
server.listen(3000, (_) => {
console.log("http://localhost:3000/");
});