大容量ファイルをネットワーク経由で転送する際、通信の安定性とデータの整合性を確保することは重要な課題です。本アプローチでは、ファイルを小さなブロックに分割して送信し、サーバー側で結合する仕組みを採用します。さらに、改ざん検知のために MD5 ハッシュ値を用いた検証プロセスを組み込みます。
効率的なハッシュ計算戦略
ファイル整合性の検証には MD5 アルゴリズムが有効ですが、数ギガバイトを超えるファイル全体を一度にハッシュ化するとメモリ不足や長時間の処理を招きます。そこで、増分ハッシュ計算(Incremental Hashing)を用います。これにより、ファイルを読み込みながら逐次的にハッシュ値を更新でき、メモリ効率と処理速度を両立できます。MD5 は单向性ハッシュ関数であるため、セキュリティ上の要件を満たしつつ、ファイル識別子として機能します。
フロントエンド実装
クライアント側では、File API の slice メソッドを用いてバイナリデータを分割します。各ブロックの読み込みと同時にハッシュ計算を行い、最終的にファイル全体のハッシュ値を算出します。以下に、ファイルの分割とハッシュ生成を行う処理の例を示します。
const BLOCK_SIZE = 2 * 1024 * 1024; // 2MB
async function computeFileHash(file) {
const hasher = new SparkMD5.ArrayBuffer();
const totalChunks = Math.ceil(file.size / BLOCK_SIZE);
for (let i = 0; i < totalChunks; i++) {
const start = i * BLOCK_SIZE;
const end = Math.min(start + BLOCK_SIZE, file.size);
const blobSlice = File.prototype.slice;
const chunk = blobSlice.call(file, start, end);
const buffer = await chunk.arrayBuffer();
hasher.append(buffer);
}
return hasher.end();
}アップロード時には、各チャンクにインデックス番号とファイルハッシュを付与してサーバーへ送信します。これにより、サーバー側で順序通りに結合し、完全性を検証することが可能になります。
バックエンド実装
サーバー側では、受信したチャンクを一時ディレクトリに保存します。すべてのチャンクが揃った時点で、それらを結合して最終ファイルを作成します。Node.js 環境ではストリーム処理を活用することで、メモリ使用量を抑えながらファイル操作を行えます。
import fs from 'fs';
import path from 'path';
import { pipeline } from 'stream/promises';
async function saveChunk(chunkData, filePath) {
const writeStream = fs.createWriteStream(filePath);
// chunkData はバイナリストリームまたはバッファと想定
await pipeline(chunkData.stream, writeStream);
}
async function mergeChunks(chunkDir, finalPath) {
const chunks = await fs.promises.readdir(chunkDir);
chunks.sort(); // インデックス順にソート
const writeStream = fs.createWriteStream(finalPath);
for (const chunkName of chunks) {
const chunkPath = path.join(chunkDir, chunkName);
const readStream = fs.createReadStream(chunkPath);
await pipeline(readStream, writeStream, { end: false });
}
writeStream.end();
}中断と再開の対応
通信障害やページ刷新によりアップロードが中断された場合、同一のファイルハッシュをキーとしてサーバーに問い合わせを行います。サーバーは既に受信済みのチャンクインデックス一覧を返却し、クライアントは未送信の部分のみを再度要求します。また、同一ハッシュ値のファイルが既にサーバーに存在する場合は、アップロード処理をスキップし、既存ファイルへのリンクを作成することで転送コストを削減できます。