Quando un'applicazione Delphi deve archiviare file su uno storage cloud — backup, documenti, allegati — Amazon S3 è una delle destinazioni più comuni. L'AWS SDK for Delphi espone un client dedicato che evita di dover costruire a mano le richieste firmate richieste dal protocollo AWS (la parte più delicata e facile da sbagliare se fatta manualmente via HTTP).
Incapsulare il client S3
Anche qui vale il principio già visto per altre integrazioni: una classe di servizio nasconde la creazione del client e le credenziali, esponendo solo i due metodi che servono davvero al resto dell'applicazione.
unit S3StorageService;
interface
uses
System.SysUtils, System.Classes, AWS.S3, Vcl.ComCtrls;
type
TS3StorageService = class
private
FS3Client: IAmazonSimpleStorageService;
public
constructor Create(const AccessKey, SecretKey, Region: string);
destructor Destroy; override;
function UploadFile(const BucketName, LocalFilePath, S3ObjectKey: string;
ProgressBar: TProgressBar = nil): Boolean;
function DownloadFile(const BucketName, S3ObjectKey, LocalFilePath: string;
ProgressBar: TProgressBar = nil): Boolean;
end;
implementation
constructor TS3StorageService.Create(const AccessKey, SecretKey, Region: string);
begin
inherited Create;
FS3Client := TAmazonSimpleStorageServiceClient.Create(AccessKey, SecretKey, Region);
end;
destructor TS3StorageService.Destroy;
begin
FS3Client := nil;
inherited;
end;
end.Upload con avanzamento
La richiesta di upload accetta uno stream di input e un handler opzionale per l'avanzamento, comodo per collegare direttamente una TProgressBar senza logica aggiuntiva nel form chiamante:
function TS3StorageService.UploadFile(const BucketName, LocalFilePath, S3ObjectKey: string;
ProgressBar: TProgressBar): Boolean;
var
Request: TPutObjectRequest;
Stream: TFileStream;
begin
Result := False;
Stream := TFileStream.Create(LocalFilePath, fmOpenRead or fmShareDenyWrite);
try
Request := TPutObjectRequest.Create;
try
Request.BucketName := BucketName;
Request.Key := S3ObjectKey;
Request.InputStream := Stream;
if Assigned(ProgressBar) then
Request.OnUploadProgress := procedure(const Sender: TObject; BytesSent, TotalBytes: Int64)
begin
ProgressBar.Position := Round((BytesSent / TotalBytes) * 100);
end;
FS3Client.PutObject(Request);
Result := True;
finally
Request.Free;
end;
finally
Stream.Free;
end;
end;Download, stesso principio
Il download è speculare: uno stream di destinazione al posto di quello sorgente, e lo stesso tipo di callback per l'avanzamento, questa volta sui byte ricevuti.
function TS3StorageService.DownloadFile(const BucketName, S3ObjectKey, LocalFilePath: string;
ProgressBar: TProgressBar): Boolean;
var
Request: TGetObjectRequest;
Stream: TFileStream;
begin
Result := False;
Stream := TFileStream.Create(LocalFilePath, fmCreate);
try
Request := TGetObjectRequest.Create;
try
Request.BucketName := BucketName;
Request.Key := S3ObjectKey;
if Assigned(ProgressBar) then
Request.OnDownloadProgress := procedure(const Sender: TObject; BytesRead, TotalBytes: Int64)
begin
ProgressBar.Position := Round((BytesRead / TotalBytes) * 100);
end;
FS3Client.GetObject(Request, Stream);
Result := True;
finally
Request.Free;
end;
finally
Stream.Free;
end;
end;Dove tenere le credenziali
Access key e secret key non vanno mai scritte nel codice sorgente: si leggono da un file di configurazione (idealmente offuscato, come nell'articolo sulla protezione dei file INI) o, meglio ancora quando l'infrastruttura lo consente, da un ruolo IAM temporaneo invece di una coppia di chiavi statiche a lunga durata. Per applicazioni desktop distribuite a più postazioni, conviene inoltre limitare i permessi della policy IAM associata alle chiavi al solo bucket e alle sole operazioni realmente necessarie (upload e download), non ad accesso amministrativo completo su S3.