Wiederaufnehmbarer Datei-Upload
In diesem Artikel werden wir das Konzept von wiederaufnehmbaren Datei-Uploads in JavaScript erkunden und einen umfassenden Leitfaden mit praktischen Beispielen bereitstellen. Diese Technik ist entscheidend für die Verbesserung der Benutzererfahrung, insbesondere beim Umgang mit großen Dateien oder instabilen Netzwerkverbindungen. Durch die Implementierung von wiederaufnehmbaren Uploads können wir sicherstellen, dass unsere Benutzer Dateien dort fortsetzen können, wo sie aufgehört haben, wodurch Datenverlust und Frustration minimiert werden.
Einführung in wiederaufnehmbare Datei-Uploads
Wiederaufnehmbare Datei-Uploads ermöglichen es Benutzern, Dateien in Chunks (Blöcken) hochzuladen. Dadurch wird sichergestellt, dass ein Upload bei Unterbrechungen aufgrund von Netzwerkproblemen oder anderen Gründen an dem letzten erfolgreich hochgeladenen Chunk fortgesetzt werden kann. Diese Technik ist besonders für große Dateien nützlich und kann die Zuverlässigkeit von Datei-Uploads erheblich verbessern.
Vorteile von wiederaufnehmbaren Datei-Uploads
- Verbesserte Benutzererfahrung: Benutzer können Uploads fortsetzen, ohne von vorne zu beginnen.
- Effizienz: Reduziert die übertragene Datenmenge, indem nur fehlende Teile hochgeladen werden.
- Fehlerbehandlung: Verarbeitet Netzwerkunterbrechungen auf elegante Weise.
Implementierung von wiederaufnehmbaren Datei-Uploads in JavaScript
Einrichten der Umgebung
Bevor wir in die Implementierung einsteigen, stellen Sie sicher, dass Sie über die folgenden Tools und Bibliotheken verfügen:
- Ein moderner Webbrowser mit JavaScript-Unterstützung.
- Ein Server, der Datei-Uploads verarbeiten kann.
- Die
resumable.js-Bibliothek (oder eine ähnliche Bibliothek) zur Verwaltung der Client-seitigen Logik.
Installieren Sie die erforderlichen Node.js-Abhängigkeiten:
npm install express corsServerseitige Konfiguration
Konfigurieren Sie zunächst Ihren Server, um Datei-Chunks zu verarbeiten und Metadaten über die hochgeladenen Dateien zu speichern. Hier ist ein Beispiel mit Node.js und Express. Beachten Sie, dass resumable.js standardmäßig Chunk-Metadaten in der Query-String sendet, daher lesen wir aus req.query und verwenden ein temporäres Verzeichnis pro Datei, um die Ankunft von Chunks in falscher Reihenfolge sicher zu verarbeiten.
const express = require('express');
const cors = require('cors');
const fs = require('fs');
const path = require('path');
const app = express();
const port = 3000;
app.use(cors());
// Handle chunk verification for testChunks: true
app.head('/upload', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
const chunkNumber = parseInt(req.query.resumableChunkNumber);
const identifier = req.query.resumableIdentifier;
const chunkPath = path.join('uploads', identifier, `chunk-${chunkNumber}.bin`);
fs.promises.access(chunkPath)
.then(() => res.status(200).end())
.catch(() => res.status(404).end());
});
app.post('/upload', async (req, res) => {
try {
const chunkNumber = parseInt(req.query.resumableChunkNumber);
const totalChunks = parseInt(req.query.resumableTotalChunks);
const identifier = req.query.resumableIdentifier;
const fileName = req.query.resumableFilename;
const chunkDir = path.join('uploads', identifier);
await fs.promises.mkdir(chunkDir, { recursive: true });
// Read raw body (resumable.js sends chunks as application/octet-stream)
const buffer = await new Promise((resolve, reject) => {
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', () => resolve(Buffer.concat(chunks)));
req.on('error', reject);
});
const chunkPath = path.join(chunkDir, `chunk-${chunkNumber}.bin`);
await fs.promises.writeFile(chunkPath, buffer);
const receivedChunks = (await fs.promises.readdir(chunkDir)).length;
if (receivedChunks === totalChunks) {
const finalPath = path.join('uploads', fileName);
const writeStream = fs.createWriteStream(finalPath);
const finishPromise = new Promise((resolve, reject) => {
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
for (let i = 1; i <= totalChunks; i++) {
const chunkPath = path.join(chunkDir, `chunk-${i}.bin`);
fs.createReadStream(chunkPath).pipe(writeStream);
}
await finishPromise;
await fs.promises.rmdir(chunkDir);
res.status(200).send('File uploaded successfully');
} else {
// resumable.js expects a 200 OK for successful chunk uploads
res.status(200).send('Chunk uploaded successfully');
}
} catch (error) {
console.error('Upload error:', error);
res.status(500).send('Server error during upload');
}
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});Clientseitige Implementierung
Nun implementieren wir die Client-seitige Logik mit JavaScript und der resumable.js-Bibliothek. Stellen Sie sicher, dass Sie die resumable.js-Bibliothek in Ihr Projekt einbinden. Wir verwenden v2.1.0 für moderne Kompatibilität. Für Produktionsumgebungen sollten Sie das standardisierte tus-Protokoll oder natives File.slice in Kombination mit fetch für bessere Kontrolle und plattformübergreifende Unterstützung in Betracht ziehen.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Resumable File Upload</title>
</head>
<body>
<input type="file" id="fileInput" />
<button id="uploadButton">Upload</button>
<p id="progress">Ready</p>
<script src="https://unpkg.com/[email protected]/resumable.min.js"></script>
<script>
const fileInput = document.getElementById('fileInput');
const uploadButton = document.getElementById('uploadButton');
const progressEl = document.getElementById('progress');
const r = new Resumable({
target: '/upload',
chunkSize: 1 * 1024 * 1024, // 1MB chunks
simultaneousUploads: 1,
testChunks: true,
throttleProgressCallbacks: 1,
});
r.assignBrowse(fileInput);
uploadButton.addEventListener('click', () => {
if (r.files.length > 0) {
r.upload();
} else {
alert('Please select a file to upload.');
}
});
r.on('progress', (file, loaded, total) => {
const percent = Math.round((loaded / total) * 100);
progressEl.textContent = `Uploading ${file.fileName}: ${percent}%`;
});
r.on('fileSuccess', (file, message) => {
console.log(`File ${file.fileName} uploaded successfully.`);
progressEl.textContent = 'Upload complete!';
});
r.on('fileError', (file, message) => {
console.error(`Error uploading file ${file.fileName}: ${message}`);
progressEl.textContent = 'Upload failed.';
});
</script>
</body>
</html>Native Alternative: File.slice + fetch
Für Projekte, die keine Abhängigkeiten bevorzugen, können Sie wiederaufnehmbare Uploads nativ mit der File.slice-API und fetch implementieren. Dieser Ansatz gibt Ihnen die volle Kontrolle über Header, Wiederholungen und die Zusammenführung von Chunks.
async function uploadFileNative(file) {
const chunkSize = 1 * 1024 * 1024; // 1MB
const totalChunks = Math.ceil(file.size / chunkSize);
const identifier = `${file.name}-${Date.now()}`;
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const url = new URL('/upload', window.location.origin);
url.searchParams.set('resumableChunkNumber', i + 1);
url.searchParams.set('resumableTotalChunks', totalChunks);
url.searchParams.set('resumableIdentifier', identifier);
url.searchParams.set('resumableFilename', file.name);
await fetch(url, { method: 'POST', body: chunk });
}
console.log('Native upload complete');
}Verwaltung von Metadaten
Es ist entscheidend, Metadaten über die hochgeladene Datei und ihre Chunks zu verwalten. Diese Informationen helfen dabei, den Upload im Falle von Unterbrechungen vom richtigen Chunk fortzusetzen. Die Serverlogik zum Verfolgen und Zusammenfügen von Chunks wurde im vorherigen Abschnitt behandelt. Vermeiden Sie für Produktionsumgebungen reinen Arbeitsspeicher- oder Dateisystem-Speicher, da diese an Persistenz und Thread-Sicherheit mangeln. Verwenden Sie stattdessen eine Datenbank oder einen Cache (z. B. Redis), um den Abschluss von Chunks zu verfolgen und Dateien zuverlässig zusammenzuführen.
Beispiel: Hochladen großer Dateien
Die Client-Konfiguration bleibt identisch mit dem vorherigen Beispiel. Um für große Dateien zu optimieren, können Sie die chunkSize erhöhen (z. B. auf 5 MB) und simultaneousUploads basierend auf der Kapazität Ihres Servers und den Netzwerkbedingungen anpassen.
Professionelle Tipps für wiederaufnehmbare Datei-Uploads
- Chunk-Größe optimieren: Passen Sie die Chunk-Größe basierend auf der durchschnittlichen Netzwerkgeschwindigkeit und Dateigröße an, um ein Gleichgewicht zwischen Upload-Geschwindigkeit und Zuverlässigkeit zu finden.
- Fehlerbehandlung: Implementieren Sie robuste Fehlerbehandlungsmechanismen, um mit Netzwerkunterbrechungen und Serverproblemen umzugehen.
- Benutzerfeedback: Geben Sie Benutzern Echtzeit-Feedback zum Upload-Fortschritt und auftretenden Problemen.
- Sicherheit: Stellen Sie sicher, dass der Datei-Upload-Prozess sicher ist, indem Sie Dateitypen validieren und eine ordnungsgemäße Authentifizierung und Autorisierung implementieren.
- Moderne Alternativen: Für Produktionsumgebungen sollten Sie standardisierte Protokolle wie
tusoder nativesFile.slicein Kombination mitfetchfür bessere Kontrolle, Wiederaufnehmbarkeit und plattformübergreifende Kompatibilität in Betracht ziehen.
Durch die Befolgung dieser Richtlinien und Beispiele können wir ein robustes und effizientes System für wiederaufnehmbare Datei-Uploads in JavaScript implementieren, das die Benutzererfahrung verbessert und zuverlässige Datei-Uploads gewährleistet.
Practice
Welche der folgenden sind Vorteile der Verwendung von wiederaufnehmbaren Datei-Uploads?