Come gestire file in modo sicuro con Angular e Amazon S3, senza dover scrivere API

Amazon Web Services offre un ottimo e conosciutissimo servizio per lo storage di file chiamato S3 (Simple Storage Service) che in Innoteam apprezziamo particolarmente per la sua semplicità, robustezza e per il suo pricing particolarmente conveniente.

In questo articolo vedremo come interfacciare una Web App con il servizio S3 di Amazon per effettuare upload e download di file in maniera sicura ed efficiente.

Il punto di forza dell’approccio descritto è che upload e download vengono effettuati direttamente verso il bucket S3 senza necessità di dover appoggiare i file su server intermediari.

Supponiamo di dover sviluppare un’app gestionale che ci permetta di caricare e scaricare documenti.

Bucket S3

Per questo tipo di applicazioni non possiamo utilizzare un bucket pubblico e accessibile da chiunque ma, anzi, vogliamo creare un bucket privato a cui possa accedere solo e soltanto il nostro applicativo.

Creiamo quindi un bucket privato dal pannello di amministrazione di Amazon S3 come indicato nella guida ufficiale di AWS.

Pre-Signed URL

Come possiamo gestire upload e download di file verso un bucket privato?
Utilizziamo una feature di Amazon S3 chiamata Pre-Signed URL.

Una Pre-Signed URL è una particolare URL che è stata generata e “firmata” dall’SDK di Amazon e che ci permette di accedere ad un ben definito oggetto di un bucket S3 in lettura oppure in scrittura in base al tipo di Pre-Signed URL.

Chi è in possesso di una di queste URL è quindi in grado fare il download oppure l’upload di uno specifico file, effettuando verso questa URL rispettivamente una chiamata di tipo HTTP GET per il download e HTTP PUT per l’upload.
Una Pre-Signed URL ha una validità temporale prefissata, terminata la quale non è più utilizzabile.

Per poter utilizzare le Pre-Signed URL è necessario modificare le configurazioni CORS del bucket S3 che abbiamo creato, in modo che vengano accettate chiamate HTTP di tipo GET e PUT verso il bucket.

E’ possibile modificare la configurazione CORS del bucket dal pannello di amministrazione di Amazon S3:
Cliccare sul nome del bucket -> Permissions -> CORS configuration

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

Architettura

In questo articolo faremo riferimento ad un’architettura che prevede un’applicazione Angular ed un’API REST JSON con un DB relazionale sottostante.

Modello Dati

Nella nostra applicazione terremo traccia di tutti i file presenti sul bucket S3, creando un record nel nostro DB associato ad ogni file.

Avremo quindi una tabella `attachments` strutturata in questo modo:

Campo Descrizione
filename: string Nome del file (key nel bucket)
uploaded: boolean Flag che indica se l’upload del file sul bucket è terminato con successo

E avremo i seguenti endpoint di una classica API REST:

Endpoint Descrizione
POST /attachments Creazione di un nuovo record di tipo Attachment
PUT /attachments/:id Modifica di un record esistente di tipo Attachment
GET /attachments/:id Recupero di un record esistente di tipo Attachment

Download di un file

Quando il frontend Angular effettua una richiesta GET all’endpoint `/attachments/:id` per il recupero di un record di tipo Attachment, la nostra API, nel caso il record abbia flag `uploaded` impostato a `true`, provvederà a generare una Pre-Signed URL di lettura e ad aggiungerla alle informazioni prelevate da DB.

Generazione di una Pre-Signed URL di lettura in PHP:

// Creating a presigned URL
$cmd = $s3Client->getCommand('GetObject', [ 'Bucket' => 'my-bucket', 'Key' => 'testKey' ]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');

// Get the actual presigned-url
$presignedUrl = (string) $request->getUri();

Body della risposta:

{
  filename: "notes.txt",
  uploaded: true,
  presignedUrl: "https://..."
}

Upload di un file

L’upload di un file verrà invece effettuato in tre passaggi.

1. Creazione di un record Attachment in DB e conseguente recupero della Pre-Signed URL di scrittura

Il frontend Angular effettua una richiesta POST all’endpoint `/attachments` per la creazione di un nuovo record di tipo Attachment.

Body della richiesta:

{ filename: "notes.txt" }

L’API REST crea un record nel DB, imposta automaticamente il flag `uploaded` di questo record a `false` e utilizza l’SDK di AWS per generare una Pre-Signed URL di scrittura.
La Pre-Signed URL viene restituita ad Angular.

Generazione di una Pre-Signed URL di scrittura in PHP:

// Creating a presigned URL
$cmd = $s3Client->getCommand('PutObject', [ 'Bucket' => 'my-bucket', 'Key' => 'testKey' ]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');

// Get the actual presigned-url
$presignedUrl = (string) $request->getUri();

Body della risposta:

{
  id: 44,
  filename: "notes.txt",
  presignedUrl: "https://...",
  uploaded: false
}

2. Upload vero e proprio direttamente verso il bucket di Amazon S3

Angular farà semplicemente una richiesta HTTP PUT verso la Pre-Signed URL ricevuta dalle API con il contenuto del file.

Upload di file utilizzando Pre-Signed URL in service Angular:

@Injectable()
export class AttachmentService {
  ...
  constructor(private http: HttpClient) {}
  ...
  upload(file: File, presignedUrl: string): Promise<any> {
    const options = {
      headers: { 'Content-Type': file.type },
      responseType: 'text'
    };
    return this.http
      .put(presignedUrl, file, options)
      .toPromise();
  }
  ...
}

3. Aggiornamento del record precedentemente creato con flag `uploaded` impostato a `true`

Una volta terminato l’upload verso il bucket S3, Angular chiamerà le API per aggiornare il record Attachment nel DB.

Body della richiesta:

{ uploaded: true }

I 3 step possono essere riassunti dal seguente diagramma:

Conclusioni

Uno dei più grandi vantaggi di questo approccio è sicuramente il fatto di poter effettuare upload e download comunicando direttamente con il bucket S3 senza dover passare dal server che ospita le API, sgravandolo dal lavoro di intermediario verso S3 e non occupandone inutilmente banda e risorse.

Ci teniamo inoltre a sottolineare che per gli esempi è stato utilizzato codice PHP e Angular ma l’approccio descritto è del tutto generale e può essere applicato con qualsiasi combinazione di tecnologie Backend/Frontend.

Digital Executives Club: dove si riuniscono i più brillanti Manager IT e Digitali

Tempo fa ho creato una community che ho chiamato Digital Executives Club, di cui fanno parte i miei clienti, vari esperti di settore e altri manager e persone che gravitano attorno al digitale.

La community è diventata talmente seguita che ho creato anche una newsletter con contenuti esclusivi e un apposito gruppo su Facebook con alcuni membri selezionati.

Decine delle menti più brillanti del digitale si confrontano, pongono questioni, condividono idee e ottengono fonti di ispirazione – e voglio che anche tu faccia parte di questo vero e proprio MasterMind.

Il Digital Executives Club non è come un normale gruppo Facebook.

Continua a leggere

Perché ci sono sempre bug e li devo pagare (quasi sempre) io?

Dietro le quinte, il lavoro di un team di sviluppo e di gestione dell’operatività di un prodotto digitale è caratterizzato da molte soddisfazioni così come da molti problemi frustranti.

Dal punto di vista delle soddisfazioni, una di quelle che personalmente mi stimolano di più è quella di poter creare, spesso da zero, qualcosa che genera un risultato anche molto rilevante per un cliente.

Tra le frustrazioni ci sono invece quelle relative al fatto che è impossibile produrre software senza difetti o riuscire a servire sempre correttamente una richiesta operativa di un utente, anche con le migliori intenzioni, così come riuscire a far sì che un’infrastruttura abbia un uptime del 100% in un anno è pressoché infattibile e, in ultima istanza, anti economico.

In generale molti di questi problemi rimangono poco visibili agli utenti finali e ai committenti, se il team è ben organizzato e ci sono dei processi di controllo adeguati.

Tuttavia, proprio per la natura del software e dei sistemi che lo fanno girare su Internet, e in generale della complessità di una piattaforma digitale, è inevitabile imbattersi in bug, incidenti e problemi di comunicazione.

Continua a leggere

Share The Pain, ovvero come far funzionare un team digitale

In questo articolo tratto un argomento che mi sta molto a cuore che è anche uno di quelli più contro intuitivi, soprattutto nell’ambito del digitale e nella gestione appunto di piattaforme e prodotti digitali.

Un concetto che infatti ripeto spesso è che la tecnologia è tutto sommato facile, se la raffrontiamo al vero problema che è invece quello delle persone.

Inteso come: gestire gli aspetti tecnologici di una piattaforma digitale è il problema minore rispetto a quello di gestire le persone che lo fanno funzionare.

A dispetto di quello che pensano molti non addetti ai lavori, un prodotto digitale non sta in piedi da solo ed è necessario farlo evolvere e correggerlo continuamente.

Continua a leggere

Perché il tuo team tecnologico non rispetta le scadenze e le attività si accumulano?

Hai mai la sensazione che gestire lo sviluppo e la manutenzione del tuo prodotto digitale svolte dal tuo team (interno o esterno) sia come cercare di spegnere un palazzo in fiamme?

Mi riferisco al fatto che definire le priorità degli sviluppi, delle attività di supporto, degli incidenti e di tutte le altre operazioni digitali è un vero e proprio inferno dove si risolve una cosa e se ne rompe un’altra, si litiga continuamente nel definire delle scadenze che molto frequentemente non vengono rispettate e lo spazio per migliorare la situazione si fa sempre più ristretto.

E questo avviene in modo paradossale: più si cerca di pianificare le attività in modo esatto, tentando di mettere a punto anche il più piccolo dei dettagli, facendosi dare stime più precise possibili dagli sviluppatori e dagli altri specialisti, allineandosi il più frequentemente possibile con il team, più la situazione si aggrava invece di migliorare.

Continua a leggere