Maison > Article > interface Web > Optimisation des téléchargements de fichiers volumineux : téléchargements partitionnés sécurisés côté client vers AWS S3
Le téléchargement de fichiers volumineux vers le cloud peut s'avérer difficile : les interruptions du réseau, les limitations du navigateur et les fichiers volumineux peuvent facilement perturber le processus. Amazon S3 (Simple Storage Service) est un service de stockage cloud évolutif et haut débit basé sur le Web, conçu pour la sauvegarde et l'archivage en ligne des données et des applications. Cependant, le téléchargement de fichiers volumineux vers S3 nécessite une manipulation minutieuse pour garantir la fiabilité et les performances.
Entrez dans le téléchargement multipart d'AWS S3 : une solution puissante qui divise les gros fichiers en morceaux plus petits, permettant des téléchargements plus rapides et plus fiables en traitant chaque partie indépendamment et même en téléchargeant des parties en parallèle. Cette méthode dépasse non seulement les limites de taille de fichier (S3 nécessite un téléchargement en plusieurs parties pour les fichiers de plus de 5 Go), mais minimise également le risque d'échec, ce qui en fait une solution idéale pour les applications nécessitant des téléchargements de fichiers fluides et robustes.
Dans ce guide, nous détaillerons les tenants et les aboutissants des téléchargements partitionnés côté client vers S3, vous montrant pourquoi il s'agit du choix judicieux pour gérer des fichiers volumineux, comment le rendre opérationnel en toute sécurité et quels sont les défis à relever. dehors pour. Je fournirai des instructions étape par étape, des exemples de code et des bonnes pratiques pour vous aider à mettre en œuvre une solution fiable de téléchargement de fichiers côté client.
Prêt à améliorer votre expérience de téléchargement de fichiers ? Allons-y !
Lors de la conception d'un système de téléchargement de fichiers, vous disposez de deux options principales : télécharger des fichiers via votre serveur (côté serveur) ou télécharger des fichiers directement du client vers S3 (côté client). Chaque approche a ses avantages et ses inconvénients.
Sécurité améliorée : tous les téléchargements sont gérés par le serveur, garantissant ainsi la sécurité des informations d'identification AWS.
Meilleure gestion des erreurs : les serveurs peuvent gérer les tentatives, la journalisation et la gestion des erreurs de manière plus robuste.
Traitement centralisé : les fichiers peuvent être validés, traités ou convertis sur le serveur avant d'être stockés dans S3.
Charge de serveur plus élevée : les téléchargements volumineux consomment des ressources du serveur (CPU, mémoire, bande passante), ce qui peut avoir un impact sur les performances et augmenter les coûts opérationnels.
Glots d'étranglement potentiels : le serveur peut devenir un point de défaillance unique ou un goulot d'étranglement des performances lors d'un trafic de téléchargement élevé, entraînant des téléchargements lents ou des temps d'arrêt.
Augmentation des coûts : la gestion des téléchargements côté serveur peut nécessiter une mise à l'échelle de votre infrastructure pour gérer les pics de charge, ce qui augmente les dépenses opérationnelles.
Charge réduite du serveur : les fichiers sont envoyés directement de l'appareil de l'utilisateur vers S3, libérant ainsi les ressources du serveur.
Vitesse améliorée : les utilisateurs bénéficient de téléchargements plus rapides puisqu'ils contournent le serveur d'applications.
Efficacité des coûts : élimine le besoin d'une infrastructure de serveur pour gérer les téléchargements volumineux, ce qui réduit potentiellement les coûts.
Évolutivité : idéal pour mettre à l'échelle les téléchargements de fichiers sans stresser les serveurs backend.
Risques de sécurité : nécessite une gestion minutieuse des informations d'identification et des autorisations AWS. Les URL présignées doivent être générées de manière sécurisée pour empêcher tout accès non autorisé.
Contrôle limité : moins de surveillance côté serveur sur les téléchargements ; la gestion des erreurs et les tentatives sont souvent gérées sur le client.
Contraintes du navigateur : les navigateurs ont des limitations de mémoire et d'API, qui peuvent entraver la gestion de fichiers très volumineux ou affecter les performances sur les appareils bas de gamme.
La mise en œuvre sécurisée des téléchargements côté client implique une coordination entre votre application frontend et un service backend sécurisé. Le rôle principal du service backend est de générer des URL présignées, permettant au client de télécharger des fichiers directement sur S3 sans exposer les informations d'identification AWS sensibles.
Pour mettre en œuvre efficacement les téléchargements côté client, vous avez besoin :
Cette architecture garantit que les opérations sensibles sont traitées en toute sécurité sur le backend, tandis que le frontend gère le processus de téléchargement.
Les URL prédéfinies permettent aux clients d'interagir directement avec S3, en effectuant des opérations telles que le téléchargement de fichiers sans nécessiter d'informations d'identification AWS du côté client. Ils sont sécurisés car :
Créez une classe de service sur votre serveur responsable de :
a. Définir le compartiment et la région S3
b. Établir les informations d'identification AWS en toute sécurité.
c. Fournir des méthodes pour générer des URL présignées et gérer les téléchargements en plusieurs parties.
// services/S3UploadService.js import { S3Client, CreateMultipartUploadCommand, CompleteMultipartUploadCommand, UploadPartCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; // Import credential providers import { fromIni, fromInstanceMetadata, fromEnv, fromProcess, } from '@aws-sdk/credential-providers'; export class S3UploadService { constructor() { this.s3BucketName = process.env.S3_BUCKET_NAME; this.s3Region = process.env.S3_REGION; this.s3Client = new S3Client({ region: this.s3Region, credentials: this.getS3ClientCredentials(), }); } // Method to generate AWS credentials securely getS3ClientCredentials() { if (process.env.NODE_ENV === 'development') { // In development, use credentials from environment variables return fromEnv(); } else { // In production, use credentials from EC2 instance metadata or another secure method return fromInstanceMetadata(); } } // Generate a presigned URL for single-part upload (PUT), download (GET), or deletion (DELETE) async generatePresignedUrl(key, operation) { let command; switch (operation) { case 'PUT': command = new PutObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'GET': command = new GetObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'DELETE': command = new DeleteObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; default: throw new Error(`Invalid operation "${operation}"`); } // Generate presigned URL return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); // Expires in 1 hour } // Methods for multipart upload async createMultipartUpload(key) { const command = new CreateMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, }); const response = await this.s3Client.send(command); return response.UploadId; } async generateUploadPartUrl(key, uploadId, partNumber) { const command = new UploadPartCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, PartNumber: partNumber, }); return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); } async completeMultipartUpload(key, uploadId, parts) { const command = new CompleteMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts }, }); return await this.s3Client.send(command); } async abortMultipartUpload(key, uploadId) { const command = new AbortMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, }); return await this.s3Client.send(command); } }
Remarque : Assurez-vous que vos informations d'identification AWS sont gérées de manière sécurisée. En production, il est recommandé d'utiliser des rôles IAM attachés à vos instances EC2 ou tâches ECS, plutôt que de coder en dur des informations d'identification ou d'utiliser des variables d'environnement.
Créez des points de terminaison d'API dans votre backend pour gérer les requêtes du frontend. Ces points de terminaison utiliseront S3UploadService pour effectuer des actions.
// controllers/S3UploadController.js import { S3UploadService } from '../services/S3UploadService'; const s3UploadService = new S3UploadService(); export const generatePresignedUrl = async (req, res, next) => { try { const { key, operation } = req.body; // key is the S3 object key (file identifier) const url = await s3UploadService.generatePresignedUrl(key, operation); res.status(200).json({ url }); } catch (error) { next(error); } }; export const initializeMultipartUpload = async (req, res, next) => { try { const { key } = req.body; const uploadId = await s3UploadService.createMultipartUpload(key); res.status(200).json({ uploadId }); } catch (error) { next(error); } }; export const generateUploadPartUrls = async (req, res, next) => { try { const { key, uploadId, parts } = req.body; // parts is the number of parts const urls = await Promise.all( [...Array(parts).keys()].map(async (index) => { const partNumber = index + 1; const url = await s3UploadService.generateUploadPartUrl(key, uploadId, partNumber); return { partNumber, url }; }) ); res.status(200).json({ urls }); } catch (error) { next(error); } }; export const completeMultipartUpload = async (req, res, next) => { try { const { key, uploadId, parts } = req.body; // parts is an array of { ETag, PartNumber } const result = await s3UploadService.completeMultipartUpload(key, uploadId, parts); res.status(200).json({ result }); } catch (error) { next(error); } }; export const abortMultipartUpload = async (req, res, next) => { try { const { key, uploadId } = req.body; await s3UploadService.abortMultipartUpload(key, uploadId); res.status(200).json({ message: 'Upload aborted' }); } catch (error) { next(error); } };
Configurez les itinéraires pour ces points de terminaison dans votre application Express ou dans le framework que vous utilisez.
L'interface gérera la sélection des fichiers, décidera d'effectuer un téléchargement en une seule partie ou en plusieurs parties en fonction de la taille du fichier et gérera le processus de téléchargement.
En général, AWS recommande « lorsque la taille de votre objet atteint 100 Mo, vous devriez envisager d'utiliser des téléchargements en plusieurs parties au lieu de télécharger l'objet en une seule opération. » Source
// services/S3UploadService.js import { S3Client, CreateMultipartUploadCommand, CompleteMultipartUploadCommand, UploadPartCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; // Import credential providers import { fromIni, fromInstanceMetadata, fromEnv, fromProcess, } from '@aws-sdk/credential-providers'; export class S3UploadService { constructor() { this.s3BucketName = process.env.S3_BUCKET_NAME; this.s3Region = process.env.S3_REGION; this.s3Client = new S3Client({ region: this.s3Region, credentials: this.getS3ClientCredentials(), }); } // Method to generate AWS credentials securely getS3ClientCredentials() { if (process.env.NODE_ENV === 'development') { // In development, use credentials from environment variables return fromEnv(); } else { // In production, use credentials from EC2 instance metadata or another secure method return fromInstanceMetadata(); } } // Generate a presigned URL for single-part upload (PUT), download (GET), or deletion (DELETE) async generatePresignedUrl(key, operation) { let command; switch (operation) { case 'PUT': command = new PutObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'GET': command = new GetObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'DELETE': command = new DeleteObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; default: throw new Error(`Invalid operation "${operation}"`); } // Generate presigned URL return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); // Expires in 1 hour } // Methods for multipart upload async createMultipartUpload(key) { const command = new CreateMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, }); const response = await this.s3Client.send(command); return response.UploadId; } async generateUploadPartUrl(key, uploadId, partNumber) { const command = new UploadPartCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, PartNumber: partNumber, }); return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); } async completeMultipartUpload(key, uploadId, parts) { const command = new CompleteMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts }, }); return await this.s3Client.send(command); } async abortMultipartUpload(key, uploadId) { const command = new AbortMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, }); return await this.s3Client.send(command); } }
// controllers/S3UploadController.js import { S3UploadService } from '../services/S3UploadService'; const s3UploadService = new S3UploadService(); export const generatePresignedUrl = async (req, res, next) => { try { const { key, operation } = req.body; // key is the S3 object key (file identifier) const url = await s3UploadService.generatePresignedUrl(key, operation); res.status(200).json({ url }); } catch (error) { next(error); } }; export const initializeMultipartUpload = async (req, res, next) => { try { const { key } = req.body; const uploadId = await s3UploadService.createMultipartUpload(key); res.status(200).json({ uploadId }); } catch (error) { next(error); } }; export const generateUploadPartUrls = async (req, res, next) => { try { const { key, uploadId, parts } = req.body; // parts is the number of parts const urls = await Promise.all( [...Array(parts).keys()].map(async (index) => { const partNumber = index + 1; const url = await s3UploadService.generateUploadPartUrl(key, uploadId, partNumber); return { partNumber, url }; }) ); res.status(200).json({ urls }); } catch (error) { next(error); } }; export const completeMultipartUpload = async (req, res, next) => { try { const { key, uploadId, parts } = req.body; // parts is an array of { ETag, PartNumber } const result = await s3UploadService.completeMultipartUpload(key, uploadId, parts); res.status(200).json({ result }); } catch (error) { next(error); } }; export const abortMultipartUpload = async (req, res, next) => { try { const { key, uploadId } = req.body; await s3UploadService.abortMultipartUpload(key, uploadId); res.status(200).json({ message: 'Upload aborted' }); } catch (error) { next(error); } };
Bien qu'AWS S3 prenne en charge des objets d'une taille allant jusqu'à 5 Tio (téraoctets), le téléchargement de fichiers aussi volumineux directement à partir d'un navigateur n'est pas pratique et souvent impossible en raison des limitations du navigateur et des contraintes de ressources côté client. Les navigateurs peuvent planter ou ne plus répondre lors de la manipulation de fichiers extrêmement volumineux, surtout s'ils doivent être traités en mémoire.
Le téléchargement de fichiers volumineux augmente le risque d'interruptions ou de pannes du réseau pendant le processus de téléchargement. La mise en œuvre d'une stratégie de nouvelle tentative robuste est cruciale pour améliorer l'expérience utilisateur et garantir la réussite des téléchargements.
Les téléchargements partitionnés incomplets peuvent s'accumuler dans votre compartiment S3, consommant de l'espace de stockage et potentiellement engendrant des coûts.
Exemple de configuration de règle de cycle de vie :
// services/S3UploadService.js import { S3Client, CreateMultipartUploadCommand, CompleteMultipartUploadCommand, UploadPartCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; // Import credential providers import { fromIni, fromInstanceMetadata, fromEnv, fromProcess, } from '@aws-sdk/credential-providers'; export class S3UploadService { constructor() { this.s3BucketName = process.env.S3_BUCKET_NAME; this.s3Region = process.env.S3_REGION; this.s3Client = new S3Client({ region: this.s3Region, credentials: this.getS3ClientCredentials(), }); } // Method to generate AWS credentials securely getS3ClientCredentials() { if (process.env.NODE_ENV === 'development') { // In development, use credentials from environment variables return fromEnv(); } else { // In production, use credentials from EC2 instance metadata or another secure method return fromInstanceMetadata(); } } // Generate a presigned URL for single-part upload (PUT), download (GET), or deletion (DELETE) async generatePresignedUrl(key, operation) { let command; switch (operation) { case 'PUT': command = new PutObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'GET': command = new GetObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; case 'DELETE': command = new DeleteObjectCommand({ Bucket: this.s3BucketName, Key: key, }); break; default: throw new Error(`Invalid operation "${operation}"`); } // Generate presigned URL return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); // Expires in 1 hour } // Methods for multipart upload async createMultipartUpload(key) { const command = new CreateMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, }); const response = await this.s3Client.send(command); return response.UploadId; } async generateUploadPartUrl(key, uploadId, partNumber) { const command = new UploadPartCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, PartNumber: partNumber, }); return await getSignedUrl(this.s3Client, command, { expiresIn: 3600 }); } async completeMultipartUpload(key, uploadId, parts) { const command = new CompleteMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts }, }); return await this.s3Client.send(command); } async abortMultipartUpload(key, uploadId) { const command = new AbortMultipartUploadCommand({ Bucket: this.s3BucketName, Key: key, UploadId: uploadId, }); return await this.s3Client.send(command); } }
Le téléchargement de fichiers volumineux peut nécessiter beaucoup de ressources et empêcher le thread principal du navigateur de répondre, ce qui entraîne une mauvaise expérience utilisateur.
Lors de la mise en œuvre de téléchargements partitionnés côté client, la compatibilité du navigateur est en effet un problème. Différents navigateurs peuvent avoir différents niveaux de prise en charge des API et des fonctionnalités requises pour gérer les téléchargements de fichiers volumineux, telles que l'*API de fichiers, le découpage Blob, les Web Workers et la gestion des requêtes réseau* . Il est essentiel de surmonter ces différences avec succès pour garantir une expérience utilisateur cohérente et fiable sur tous les navigateurs pris en charge.
En implémentant des téléchargements côté client avec des URL prédéfinies et un téléchargement en plusieurs parties, vous pouvez gérer efficacement les téléchargements de fichiers de toute taille directement vers S3, réduisant ainsi la charge du serveur et améliorant les performances. N'oubliez pas de garder la sécurité au premier plan en gérant en toute sécurité les informations d'identification AWS et en limitant les autorisations et la durée de vie des URL présignées.
Ce guide fournit une approche étape par étape pour configurer un système de téléchargement de fichiers sécurisé et évolutif à l'aide d'AWS S3, du kit AWS SDK pour JavaScript et d'URL présignées. Grâce aux exemples de code et aux bonnes pratiques fournis, vous êtes sur la bonne voie pour améliorer les capacités de téléchargement de fichiers de votre application.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!