Téléchargement de fichiers avec reprise
Dans cet article, nous explorerons le concept de téléchargement de fichiers avec reprise en JavaScript, en fournissant un guide complet avec des exemples pratiques. Cette technique est essentielle pour améliorer l'expérience utilisateur, en particulier lors du traitement de fichiers volumineux ou de connexions réseau peu fiables. En implémentant des téléchargements avec reprise, nous pouvons garantir que nos utilisateurs puissent continuer à télécharger des fichiers à partir de l'endroit où ils se sont arrêtés, minimisant ainsi les pertes de données et la frustration.
Introduction aux téléchargements de fichiers avec reprise
Les téléchargements de fichiers avec reprise permettent aux utilisateurs de télécharger des fichiers par morceaux, garantissant qu'en cas d'interruption du téléchargement due à un problème réseau ou à toute autre raison, il puisse reprendre à partir du dernier morceau téléchargé avec succès. Cette technique est particulièrement utile pour les fichiers volumineux et peut considérablement améliorer la fiabilité des téléchargements de fichiers.
Avantages des téléchargements de fichiers avec reprise
- Expérience utilisateur améliorée : Les utilisateurs peuvent reprendre les téléchargements sans tout recommencer.
- Efficacité : Réduit la quantité de données transférées en téléchargeant uniquement les parties manquantes.
- Gestion des erreurs : Gère les interruptions réseau de manière élégante.
Implémentation des téléchargements de fichiers avec reprise en JavaScript
Configuration de l'environnement
Avant de plonger dans l'implémentation, assurez-vous de disposer des outils et bibliothèques suivants :
- Un navigateur web moderne prenant en charge JavaScript.
- Un serveur capable de gérer les téléchargements de fichiers.
- La bibliothèque
resumable.js(ou une bibliothèque similaire) pour gérer la logique côté client.
Installez les dépendances Node.js requises :
npm install express corsConfiguration côté serveur
Tout d'abord, configurez votre serveur pour gérer les morceaux de fichiers et stocker les métadonnées des fichiers téléchargés. Voici un exemple utilisant Node.js et Express. Notez que resumable.js envoie les métadonnées des morceaux dans la chaîne de requête par défaut, nous lisons donc depuis req.query et utilisons un répertoire temporaire par fichier pour gérer en toute sécurité l'arrivée des morceaux dans le désordre.
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}`);
});Implémentation côté client
Maintenant, implémentons la logique côté client en utilisant JavaScript et la bibliothèque resumable.js. Assurez-vous d'inclure la bibliothèque resumable.js dans votre projet. Nous utilisons la version 2.1.0 pour une compatibilité moderne. Pour les environnements de production, envisagez le protocole standardisé tus ou File.slice natif avec fetch pour un meilleur contrôle et une prise en charge multiplateforme.
<!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>Alternative native : File.slice + fetch
Pour les projets qui préfèrent zéro dépendance, vous pouvez implémenter des téléchargements avec reprise nativement en utilisant l'API File.slice et fetch. Cette approche vous donne un contrôle total sur les en-têtes, les tentatives de reconnexion et l'assemblage des morceaux.
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');
}Gestion des métadonnées
Il est crucial de gérer les métadonnées concernant le fichier téléchargé et ses morceaux. Ces informations aident à reprendre le téléchargement à partir du bon morceau en cas d'interruption. La logique serveur pour le suivi et l'assemblage des morceaux est abordée dans la section précédente. Pour les environnements de production, évitez le stockage en mémoire ou uniquement sur le système de fichiers, car ils manquent de persistance et de sécurité des threads. Utilisez plutôt une base de données ou un cache (par exemple, Redis) pour suivre l'achèvement des morceaux et assembler les fichiers de manière fiable.
Exemple : Téléchargement de fichiers volumineux
La configuration client reste identique à l'exemple précédent. Pour optimiser le téléchargement de fichiers volumineux, vous pouvez augmenter la chunkSize (par exemple, à 5 Mo) et ajuster simultaneousUploads en fonction de la capacité de votre serveur et des conditions réseau.
Conseils professionnels pour les téléchargements de fichiers avec reprise
- Optimisez la taille des morceaux : Ajustez la taille des morceaux en fonction de la vitesse réseau moyenne et de la taille du fichier pour équilibrer vitesse de téléchargement et fiabilité.
- Gestion des erreurs : Implémentez des mécanismes de gestion des erreurs robustes pour faire face aux interruptions réseau et aux problèmes serveur.
- Retour utilisateur : Fournissez un retour en temps réel aux utilisateurs sur la progression du téléchargement et tout problème rencontré.
- Sécurité : Assurez-vous que le processus de téléchargement de fichiers est sécurisé en validant les types de fichiers et en mettant en place une authentification et une autorisation appropriées.
- Alternatives modernes : Pour les environnements de production, envisagez des protocoles standardisés comme
tusouFile.slicenatif avecfetchpour un meilleur contrôle, une reprise fiable et une compatibilité multiplateforme.
En suivant ces directives et exemples, nous pouvons implémenter un système de téléchargement de fichiers avec reprise robuste et efficace en JavaScript, améliorant l'expérience utilisateur et garantissant des téléchargements de fichiers fiables.
Pratique
Parmi les éléments suivants, lesquels sont des avantages de l'utilisation des téléchargements de fichiers avec reprise ?