Heim > Artikel > Web-Frontend > Optimierung großer Datei-Uploads: Sichere clientseitige mehrteilige Uploads auf AWS S3
Das Hochladen großer Dateien in die Cloud kann eine Herausforderung sein – Netzwerkunterbrechungen, Browsereinschränkungen und große Dateigrößen können den Prozess leicht stören. Amazon S3 (Simple Storage Service) ist ein skalierbarer, webbasierter Hochgeschwindigkeits-Cloud-Speicherdienst, der für die Online-Sicherung und Archivierung von Daten und Anwendungen entwickelt wurde. Das Hochladen großer Dateien auf S3 erfordert jedoch eine sorgfältige Handhabung, um Zuverlässigkeit und Leistung sicherzustellen.
Nutzen Sie den mehrteiligen Upload von AWS S3: eine leistungsstarke Lösung, die große Dateien in kleinere Teile aufteilt und so schnellere und zuverlässigere Uploads ermöglicht, indem sie jeden Teil unabhängig angeht und Teile sogar parallel hochlädt. Diese Methode überwindet nicht nur Dateigrößenbeschränkungen (S3 erfordert einen mehrteiligen Upload für Dateien, die größer als 5 GB sind), sondern minimiert auch das Fehlerrisiko und eignet sich daher perfekt für Anwendungen, die nahtlose, robuste Datei-Uploads benötigen.
In diesem Leitfaden erläutern wir die Besonderheiten clientseitiger Multipart-Uploads auf S3 und zeigen Ihnen, warum es die kluge Wahl für den Umgang mit großen Dateien ist, wie Sie es sicher zum Laufen bringen und welche Herausforderungen Sie im Auge behalten müssen raus für. Ich stelle Schritt-für-Schritt-Anleitungen, Codebeispiele und Best Practices zur Verfügung, um Sie bei der Implementierung einer zuverlässigen clientseitigen Datei-Upload-Lösung zu unterstützen.
Sind Sie bereit, Ihr Datei-Upload-Erlebnis zu verbessern? Lasst uns eintauchen!
Beim Entwerfen eines Datei-Upload-Systems haben Sie zwei Hauptoptionen: Hochladen von Dateien über Ihren Server (serverseitig) oder Hochladen von Dateien direkt vom Client auf S3 (clientseitig). Jeder Ansatz hat seine Vor- und Nachteile.
Erhöhte Sicherheit: Alle Uploads werden vom Server verwaltet, sodass die AWS-Anmeldeinformationen sicher bleiben.
Bessere Fehlerbehandlung: Server können Wiederholungsversuche, Protokollierung und Fehlerbehandlung robuster verwalten.
Zentralisierte Verarbeitung: Dateien können auf dem Server validiert, verarbeitet oder konvertiert werden, bevor sie in S3 gespeichert werden.
Höhere Serverlast: Große Uploads verbrauchen Serverressourcen (CPU, Speicher, Bandbreite), was sich auf die Leistung auswirken und die Betriebskosten erhöhen kann.
Potenzielle Engpässe: Der Server kann bei hohem Upload-Verkehr zu einem Single Point of Failure oder einem Leistungsengpass werden, was zu langsamen Uploads oder Ausfallzeiten führt.
Erhöhte Kosten: Die serverseitige Bearbeitung von Uploads erfordert möglicherweise eine Skalierung Ihrer Infrastruktur, um Spitzenlasten zu bewältigen, was zu höheren Betriebskosten führt.
Reduzierte Serverlast: Dateien werden direkt vom Gerät des Benutzers an S3 gesendet, wodurch Serverressourcen freigegeben werden.
Verbesserte Geschwindigkeit: Benutzer erleben schnellere Uploads, da sie den Anwendungsserver umgehen.
Kosteneffizienz: Macht eine Serverinfrastruktur zur Verarbeitung großer Uploads überflüssig und senkt möglicherweise die Kosten.
Skalierbarkeit: Ideal zum Skalieren von Datei-Uploads, ohne Backend-Server zu belasten.
Sicherheitsrisiken: Erfordert einen sorgfältigen Umgang mit AWS-Anmeldeinformationen und -Berechtigungen. Vorsignierte URLs müssen sicher generiert werden, um unbefugten Zugriff zu verhindern.
Eingeschränkte Kontrolle: Weniger serverseitige Kontrolle über Uploads; Fehlerbehandlung und Wiederholungsversuche werden oft auf dem Client verwaltet.
Browsereinschränkungen: Browser haben Speicher- und API-Einschränkungen, die die Verarbeitung sehr großer Dateien behindern oder die Leistung auf Geräten der unteren Preisklasse beeinträchtigen können.
Die sichere Implementierung clientseitiger Uploads erfordert die Koordination zwischen Ihrer Frontend-Anwendung und einem sicheren Backend-Dienst. Die Hauptaufgabe des Backend-Dienstes besteht darin, vorsignierte URLs zu generieren, die es dem Client ermöglichen, Dateien direkt auf S3 hochzuladen, ohne vertrauliche AWS-Anmeldeinformationen preiszugeben.
Um clientseitige Uploads effektiv zu implementieren, benötigen Sie:
Diese Architektur stellt sicher, dass sensible Vorgänge sicher im Backend abgewickelt werden, während das Frontend den Upload-Prozess verwaltet.
Vordefinierte URLs ermöglichen es Clients, direkt mit S3 zu interagieren und Vorgänge wie das Hochladen von Dateien auszuführen, ohne dass AWS-Anmeldeinformationen auf der Clientseite erforderlich sind. Sie sind sicher, weil:
Erstellen Sie auf Ihrem Server eine Serviceklasse, die verantwortlich ist für:
a. Definieren des S3-Buckets und der Region
B. AWS-Anmeldeinformationen sicher einrichten.
C. Bereitstellung von Methoden zum Generieren vorsignierter URLs und zum Verwalten mehrteiliger Uploads.
// 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); } }
Hinweis: Stellen Sie sicher, dass Ihre AWS-Anmeldeinformationen sicher verwaltet werden. In der Produktion wird empfohlen, IAM-Rollen zu verwenden, die Ihren EC2-Instanzen oder ECS-Aufgaben zugeordnet sind, anstatt Anmeldeinformationen fest zu codieren oder Umgebungsvariablen zu verwenden.
Erstellen Sie API-Endpunkte in Ihrem Backend, um Anfragen vom Frontend zu verarbeiten. Diese Endpunkte nutzen den S3UploadService, um Aktionen auszuführen.
// 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); } };
Richten Sie die Routen für diese Endpunkte in Ihrer Express-App oder dem von Ihnen verwendeten Framework ein.
Das Frontend übernimmt die Auswahl der Dateien, entscheidet anhand der Dateigröße, ob ein einteiliger oder mehrteiliger Upload durchgeführt werden soll, und verwaltet den Upload-Prozess.
Im Allgemeinen empfiehlt AWS: „Wenn Ihre Objektgröße 100 MB erreicht, sollten Sie mehrteilige Uploads in Betracht ziehen, anstatt das Objekt in einem einzigen Vorgang hochzuladen.“ Quelle
// 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); } };
Während AWS S3 Objekte mit einer Größe von bis zu 5 TiB (Terabyte) unterstützt, ist das Hochladen solch großer Dateien direkt aus einem Browser aufgrund von Browserbeschränkungen und clientseitigen Ressourcenbeschränkungen unpraktisch und oft unmöglich. Browser können abstürzen oder nicht mehr reagieren, wenn sie extrem große Dateien verarbeiten, insbesondere wenn sie im Speicher verarbeitet werden müssen.
Das Hochladen großer Dateien erhöht das Risiko von Netzwerkunterbrechungen oder Ausfällen während des Upload-Vorgangs. Die Implementierung einer robusten Wiederholungsstrategie ist entscheidend, um das Benutzererlebnis zu verbessern und erfolgreiche Uploads sicherzustellen.
Unvollständige mehrteilige Uploads können sich in Ihrem S3-Bucket ansammeln, Speicherplatz verbrauchen und möglicherweise Kosten verursachen.
Beispiel für die Konfiguration einer Lebenszyklusregel:
// 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); } }
Das Hochladen großer Dateien kann ressourcenintensiv sein und dazu führen, dass der Hauptthread des Browsers nicht mehr reagiert, was zu einer schlechten Benutzererfahrung führt.
Bei der Implementierung clientseitiger mehrteiliger Uploads ist die Browserkompatibilität tatsächlich ein Problem. Verschiedene Browser verfügen möglicherweise über unterschiedliche Unterstützungsstufen für die APIs und Funktionen, die für die Verarbeitung großer Datei-Uploads erforderlich sind, wie z. B. die *Datei-API, Blob-Slicing, Web Worker und Netzwerkanfrageverarbeitung* . Die erfolgreiche Bewältigung dieser Unterschiede ist entscheidend, um eine konsistente und zuverlässige Benutzererfahrung in allen unterstützten Browsern sicherzustellen.
Durch die Implementierung clientseitiger Uploads mit vorsignierten URLs und mehrteiligem Upload können Sie Datei-Uploads jeder Größe direkt in S3 effizient verarbeiten, wodurch die Serverlast reduziert und die Leistung verbessert wird. Denken Sie daran, die Sicherheit im Vordergrund zu halten, indem Sie AWS-Anmeldeinformationen sicher verwalten und die Berechtigungen und die Lebensdauer vorsignierter URLs begrenzen.
Diese Anleitung bietet einen schrittweisen Ansatz zum Einrichten eines sicheren und skalierbaren Datei-Upload-Systems mit AWS S3, dem AWS SDK für JavaScript und vorsignierten URLs. Mit den bereitgestellten Codebeispielen und Best Practices sind Sie auf dem besten Weg, die Datei-Upload-Funktionen Ihrer Anwendung zu verbessern.
Das obige ist der detaillierte Inhalt vonOptimierung großer Datei-Uploads: Sichere clientseitige mehrteilige Uploads auf AWS S3. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!