Fichiers
Dans un déploiement cloud on ne pourrait pas stocker des fichiers des utilisateurs localement (comme dans WordPress ou autre type de serveur) :
Pour un scaling horizontal On aimerait pouvoir dupliquer des processus de notre api plusieurs fois, sur plusieurs instances dans plusieurs pays
Il faut donc un point centralisé et partagé de stockage
Amazon a eu énormément de succès avec leur protocole S3 pour le stockage "d'objets" dans le cloud. Aujourd'hui la plupart des fournisseurs cloud offre un service de stockage qui s'appelle "Object Storage", qui respecte la norme Amazon S3.
Le principe est qu'on stocke un "objet" (un fichier) sous un chemin textuel, mais on n'a aucun détail concernant l'emplacement disque etc. Il y a un protocole HTTP qui permet d'envoyer des objets, récupérer des objets, les supprimer ou les interroger (pour de la meta-data).
Buckets chez Scaleway
Chez Scaleway, il y a ce service :

Mais on trouve aussi les Object Storage chez Google, AWS (bien sur), OVH, etc.
On commence par créer un bucket, un seau dans lequel on va stocker nos fichiers. Une fois crée, il y a des informations qu'on va utiliser dans notre API pour communiquer avec le bucket :

Identification
Il est possible d'avoir des buckets ouvert au public, ou privé :
Ouvert au public : pour les blogs etc où on va juste références les images, fichiers etc dans notre html avec
<img src="...">. Il n'y a pas de sécurité.Privé : on va exiger de la sécurité avant de récupérer les fichiers.
Pour un usage privé, il faut disposer des clés d'accès. Créer des clés d'accès change selon le fournisseur cloud, mais chez Scaleway on va dans Organisation (en haut à droite), Identifiants, Clés API. Ensuite, on clique sur "Générer une nouvelle clé API".
Attention : le code secret s'affiche qu'une seule fois donc, prenez note ! À tout moment, si on constate de l'abus sur la clé, on peut la supprimer et remplacer avec une nouvelle.
Relier notre base et le stockage
Chez nous, on va probablement devoir garder une trace de fichiers stockés dans le cloud. De la même manière que l'on stocke, par exemple, le chemin absolut (ou relative) d'un fichier sur le stockage local, on va stocker le chemin pour retrouver le fichier sur le cloud.
Heureusement avec Object Storage chaque fichier est identifié par un chemin qui est très similaire à un chemin pour un fichier :
Nous allons donc stocker cet identifiant dans notre base de données.
Par exemple, j'aimerais permettre à un utilisateur de mon API de télécharger des fichiers liées à son compte. Je vais ajouter une table suivante à mon DDL (dans src/model/schema/ddl.sql):
Pour mette à jour le schéma dans votre instance de MariaDB :
J'appelle l'ID du fichier dans le cloud storageKey
Dans notre API, nous allons préciser à Typescript l'existence de cette nouvelle table. Dans src/model/DbTable.ts :
Et dans src/model/types/IUserFile.ts :
Outil pour parler avec l'Object Storage
Amazon maintient un package NodeJS pour le protocole S3 :
Dans notre projet, on crée un outil (wrapper) qui permet d'envoyer et récupérer des fichiers de notre bucket S3, dans src/utility/storage/ObjectStorage.ts
Cette classe simplifie la donne :
On la donne un tampon mémoire avec les données d'un fichier, avec l'ID et son type, et on laisse la classe s'occuper de l'envoi vers le Bucket
On la donne une ID de stockage, et on laisse la classe récupérer l'objet, en retournant un stream qui sera rempli de données du fichier.
Notez ici, que les coordonnées de connexion au Bucket sont inclus dans la classe directement, mais modifiables par des variables d'environnement. Idéalement on utilisera un autre Bucket pour la production !!
Préciser des endpoints
On va se servir de cet outil pour uploader et downloader des fichiers pour notre utilisateur, en créant 2 endpoints :
POST /user/:userId/file: pour uploader un fichierGET /user/:userId/file: pour lister les fichiers disponiblesGET /user/:userId/file/:fileId: pour downloader un fichier
Notre contrôleur dépendra de deux librairies supplémentaires :
Je crée le controller dans src/controllers/UserFileController.ts
Pour faciliter l'exemple, on n'a pas sécurisé les routes. Dans une vraie production, normalement, ces routes doivent d'abord être sécurisées via notre jeton JWT.
Upload
Pour le upload, au lieu de recevoir un JSON, le corps du message HTTP est en multi-part, qui veut dire qu'il contient plusieurs segments, normalement identifiées par un mime-type.
Dans la route de upload, la librairie multer nous extrait le segment qui s'appelle file te nous expose un tampon de mémoire contenant les données du fichier. On n'a juste à transférer les données de ce tampon mémoire vers notre Bucket, grâce à notre outil ObjectStorage.
Ensuite, on note l'existence de ce fichier dans notre base de données.
Download
Pour le download, on commence par récupérer la clé pour notre fichier dans la base de données.
Ensuite, au lieu de charger tout le fichier directement dans la mémoire de notre API, puis le repasser au client, on va juste créer un stream de transfert. Dès qu'on reçoit un peu de données du cloud, on les transféra au demandeur. Nous conservons ainsi des ressources de notre API.
D'abord, on répond toute suite avec un code HTTP 200, et l'en-tête 'Transfer-Encoding': 'chunked'. Le demandeur sait maintenant qu'il va recevoir les résultats via plusieurs réponses, et pas une seule.
Ensuite, on utilise le stream pour recevoir et transférer progressivement les données :
dès qu'on reçoit quelques donnes, on peut réagir (
stream.on('data', (chunk) => { ... })). Dans notre cas, on réécrit ces données dans un message vers le demandeurquand il n'y a plus de données à recevoir, l'événement
endest invoqué, et on peut signaler au demandeur qu'il n'y a plus de données
Mis à jour