Passer au contenu principal

Upload

Envoyer des fichiers à l'API et les enregistrer dans un dossier ou dans la base de données.

Introduction​

Lorsque nous devons envoyer des fichiers aux services de l'API REST, nous avons plusieurs façons de réaliser ce développement dans Netuno.

Normalement, les fichiers sont envoyés dans le corps de la requête HTTP structurés au format multipart, c'est la procédure la plus courante dans le frontend, par exemple lorsque nous remplissons un formulaire et qu'il y a ce champ pour choisir un fichier.

Alternativement, nous pouvons envoyer les octets des fichiers encodés en Base64 comme valeur dans un objet JSON, lorsque nous voulons une API REST 100% JSON.

Fichiers​

Grâce à _req.file, nous pouvons passer le nom du champ et ainsi obtenir l'objet du fichier qui a été envoyé, voyez comment :

const monFichier = _req.file("fichier");

C'est-à-dire que _req contient toutes les données que nous recevons dans le corps de la requête HTTP.

Et grâce à la méthode file, il permet d'obtenir l'objet qui représente le fichier qui a été envoyé.

La valeur obtenue peut être null s'il n'y a aucun fichier.

Images​

Lorsque nous recevons des images, il est courant d'avoir besoin de les redimensionner, pour garantir l'uniformité et pour réduire l'espace occupé par les images.

const monFichier = _req.getFile("image");
if (monFichier != null
&& (monFichier.isExtension("jpeg")
|| monFichier.isExtension("jpg")
|| monFichier.isExtension("png")
)) {
const monImage = _image.init(monFichier)
.resize(200, 200)
.file(monFichier.baseName() +".jpg", "jpeg");
_header.contentTypeJPG();
_out.copy(monImage);
}

Avec la ressource de programmation low-code polyglotte Image, nous pouvons manipuler des images, les recadrer, les redimensionner, en créer de nouvelles, etc.

Comment faire un Base64 dans le JSON​

Pour effectuer l'upload d'un fichier dans le JSON envoyé au service, nous pouvons définir une propriété avec la valeur :

data:image/png;base64,...

Où dans les ... suit la séquence des octets du fichier encodés en Base64.

Le type du fichier va après le data, par exemple :

  • PNG : image/png.
  • JPG : image/jpeg
  • TXT : text/plain
  • PDF : application/pdf
  • XLSX : application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

Voyez l'exemple d'un fichier d'une petite image PNG envoyée dans le JSON à un service :

{
"fichier": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAFIGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpDb2xvclNwYWNlPSIxIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iMyIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjMiCiAgIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiCiAgIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMyIKICAgdGlmZjpJbWFnZVdpZHRoPSIzIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI3Mi8xIgogICB0aWZmOllSZXNvbHV0aW9uPSI3Mi8xIgogICB4bXA6TWV0YWRhdGFEYXRlPSIyMDI1LTEyLTI5VDE5OjEyOjI3WiIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjUtMTItMjlUMTk6MTI6MjdaIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHhtcE1NOmFjdGlvbj0icHJvZHVjZWQiCiAgICAgIHhtcE1NOnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IERlc2lnbmVyIDEuMTAuNSIKICAgICAgeG1wTU06d2hlbj0iMjAyNS0wMi0yM1QyMjoxNDozM1oiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0icHJvZHVjZWQiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IDMuMC4xIgogICAgICBzdEV2dDp3aGVuPSIyMDI1LTEyLTI5VDE5OjEyOjI3WiIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+UGhcxwAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZG7SwNBEIe/JEqCRhS0sFAIEq3iG4I2ggmigkiIEXw1yZmHkMdxlyDBVrANKIg2vgr9C7QVrAVBUQSx1lbRRuWc84QEMbPMzre/3Rl2Z8EeSSsZvaYPMtm8Fh4PeObmFzzOZ9y004yL3qiiq6Oh0BRV7f0Wmxmvu81a1c/9a/XLcV0Bm0t4RFG1vPCE8NRqXjV5S7hFSUWXhU+EfZpcUPjG1GMWP5mctPjTZC0SDoK9SdiTrOBYBSspLSMsL8ebSReU3/uYL3HHs7MzEjvE29AJM04AD5OMEcRPP8My++lmgB5ZUSW/7yd/mpzkKjKrFNFYIUmKPD5RC1I9LjEhelxGmqLZ/7991RODA1Z1dwBqHw3jtROcm/BVMoyPA8P4OgTHA5xny/m5fRh6E71U1rx70LgOpxdlLbYNZxvQeq9GteiP5BC3JxLwcgwN89B8BXWLVs9+9zm6g8iafNUl7OxCl5xvXPoGe8Rn71E5kqUAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAySURBVAiZAScA2P8BJYrEogr//l3h5+qlBP4HCF3n/AAA/wP7WwLu7PKk+eHWAPzWyKSmfRbbIj23ZQAAAABJRU5ErkJggg=="
}

Nous pouvons obtenir le fichier envoyé de la manière suivante :

const monFichier = _req.getFile("fichier");
if (monFichier != null) {
_out.json(
_val.map()
.set("result", true)
.set("name", monFichier.name())
.set("sizeBytes", monFichier.size())
.set("sizeKB", monFichier.sizeKB())
.set("sizeMB", monFichier.sizeMB())
.set("sizeGB", monFichier.sizeGB())
);
} else {
_out.json(
_val.map()
.set("result", false)
);
}

Exemple de la sortie générée par le service ci-dessus :

{
"name": "file.png",
"result": true,
"size": 1849,
"sizeKB": 1
}

Objet​

Si le fichier se trouve dans une structure interne du JSON à l'intérieur du sous-objet :

{
"personne": {
"fichier": "data:image/png;base64,..."
}
}

Il peut être obtenu de la manière suivante :

const monFichier = _req.getValues("personne").getFile("avatar");

Tableau​

Si le fichier se trouve dans une structure interne du JSON à l'intérieur du tableau :

{
"fichiers": [
"data:image/png;base64,..."
]
}

Il peut être obtenu de la manière suivante :

const monFichier = _req.getValues("fichiers").getFile(0);

OpenAPI​

Netuno prend en charge l'intégration avec OpenAPI et utilise la définition de l'OpenAPI pour valider la requête HTTP.

Plus d'informations sur OpenAPI dans Netuno.

Avec le type file, Netuno valide si la valeur commence par le format de fichier dans le JSON, par exemple :

  • data:image/png;base64,...

Pour valider si un fichier est reçu dans une propriété de l'objet JSON :

post.in.json
{
"summary": "Upload de fichier",
"description": "Service qui reçoit un fichier dans l'objet JSON.",
"type": "object",
"properties": {
"fichier": {
"type": "file"
}
},
"required": [
"fichier"
]
}

Pour valider si une liste de fichiers est reçue dans un tableau JSON :

post.in.json
{
"summary": "Upload de fichiers",
"description": "Service qui reçoit une liste de fichiers dans un tableau JSON.",
"properties": {
"fichiers": {
"type": "array",
"items": {
"type": "file"
}
}
},
"required": [
"fichiers"
]
}

Enregistrer le fichier​

Normalement, lorsque nous recevons un fichier dans le service, nous devons l'enregistrer, par exemple :

  • Base de données : Fichier lié à un enregistrement, comme celui d'un utilisateur.
  • Storage : Dossier interne de l'application qui sert à gérer des fichiers en général.
  • Dossier : Placer le fichier dans un dossier quelconque du système.

Base de données​

Dans la gestion des champs des formulaires, Netuno prend en charge le type image et file, à savoir :

  • Image : Fichiers d'image en général, JPG et PNG.
  • File : Tout type de fichier.

Imaginez que vous avez un champ Image dans le formulaire Produit, nous pouvons enregistrer le fichier envoyé via upload de la manière suivante :

Exemple utilisant le DB Form :​

Remarquez l'utilisation de _db.form :

services/produit/image/post.js
const produitUid = _req.getString("uid");
const produitImage = _req.getFile("image");

if (produitImage == null) {
_header.status(400);
_out.json(
_val.map()
.set("result", false)
.set("error", "image-obligatoire")
);
_exec.stop();
}

const dbResultat = _db.form("produit")
.set("image", produitImage)
.where(_db.where("uid").equals(produitUid))
.update();

if (dbResultat.getInt("produit") == 1) {
_out.json(
_val.map()
.set("result", true)
);
} else {
_header.status(404);
_out.json(
_val.map()
.set("result", false)
.set("error", "uid-produit-n-existe-pas")
);
}

Exemple utilisant l'update classique :​

Remarquez l'utilisation de _db.update :

services/produit/image/post.js
const produitUid = _req.getString("uid");
const produitImage = _req.getFile("image");

if (produitImage == null) {
_header.status(400);
_out.json(
_val.map()
.set("result", false)
.set("error", "image-obligatoire")
);
_exec.stop();
}

const dbResultat = _db.update(
"produit",
produitUid,
_val.map()
.set("image", produitImage)
);

if (dbResultat == 1) {
_out.json(
_val.map()
.set("result", true)
);
} else {
_header.status(404);
_out.json(
_val.map()
.set("result", false)
.set("error", "uid-produit-n-existe-pas")
);
}

Téléchargement du fichier depuis la base de données​

Pour présenter le fichier stocké, nous devons créer un service qui fournit les octets du fichier.

Étant donné que les fichiers enregistrés dans la base de données, comme dans l'exemple ci-dessus, le fichier lui-même est placé dans le storage de l'application et dans la base de données seul le nom final du fichier est stocké.

Le storage de l'application est un dossier destiné à stocker et organiser des structures de fichiers génériques.

Donc, dans l'exemple ci-dessus, les fichiers de base de données du formulaire Produit et du champ Image sont stockés dans :

  • storage/database/produit/image/*

Netuno gère automatiquement le nom du fichier stocké pour éviter les doublons de noms de fichiers.

Pour obtenir le fichier, un service peut être créé qui présente le fichier image du produit, par exemple :

services/produit/image/get.js
const produitUid = _req.getString("uid");

// DB Form:
const dbProduit = _db.form("produit")
.where(_db.where("uid").equals(produitUid))
.first();
// Alternative avec DB Classique :
// const dbProduit = _db.get("produit", produitUid)

if (dbProduit == null) {
_header.status(404);
_out.json(
_val.map()
.set("result", false)
.set("error", "produit-non-trouve")
);
_exec.stop();
}

const dbProduitImageNom = dbProduit.getString("image");

const storageProduitImageFichier = _storage.database(
"produit",
"image",
dbProduitImageNom
);

if (storageProduitImageFichier.extension() == "jpg" || storageProduitImageFichier.extension() == "jpeg") {
_header.contentTypeJPG();
} else {
_header.contentTypePNG();
}

_header.noCache();

_out.copy(storageProduitImageFichier.inputStream());

Remarquez que la ressource _storage (Storage) est utilisée pour obtenir le fichier :

_storage.database(
"produit", // Nom du formulaire/table.
"image", // Nom du champ/colonne.
dbProduitImageNom // Nom du fichier.
);

Dans la base de données, seul le nom du fichier est stocké, et grâce à _storage.database, nous pouvons obtenir le fichier lui-même.

Storage​

Nous pouvons enregistrer un fichier directement dans le storage de l'application.

Pour ne pas mélanger avec les fichiers de base de données, nous devons utiliser le dossier filesystem, qui a pour finalité de stocker des fichiers génériques qui ne sont pas associés dans la base de données.

Pour recevoir le fichier, dans cet exemple il est démontré comment un fichier envoyé dans le service POST est stocké directement dans le storage :

services/fichier/post.js
const fichier = _req.getFile("fichier");
_storage.filesystem("serveur", "upload").ensurePath();
fichier.save(_storage.filesystem("serveur", "upload", fichier.name()));

Le fichier est stocké dans le dossier storage de l'application, dans :

  • storage/filesystem/serveur/upload/*

La ressource Storage est utilisée pour obtenir le chemin du dossier et du fichier :

_storage.filesystem(
"serveur", // Nom du sous-dossier dans le filesystem.
"upload", // Nom ou chemin du sous-dossier interne.
fichier.name() // Nom final du fichier.
);

Voir plus sur la documentation de Storage.

Pour télécharger le fichier, dans cet exemple il est démontré comment les octets du fichier peuvent être obtenus via le service GET :

services/fichier/get.js
const nomFichier = _req.getString("fichier");

const fichier = _storage.filesystem(
"serveur",
"upload",
nomFichier
).file();

if (fichier.exists() == false) {
_header.status(404);
_out.json(
_val.map()
.set("result", false)
.set("error", "fichier-inexistant")
);
_exec.stop();
}

_header.downloadFile(fichier.name());
_out.copy(fichier.input());

Dossier​

Nous pouvons enregistrer un fichier directement dans n'importe quel dossier de l'ordinateur.

Il y a deux ressources qui sont très utiles dans ces cas :

  • _app - Nous pouvons récupérer n'importe quel dossier ou fichier dans l'application.
  • _os - Nous pouvons récupérer n'importe quel dossier ou fichier sur l'ordinateur.

Voir la documentation des ressources respectives App et OS, les deux ressources ont les méthodes folder et file, qui permettent d'indiquer le chemin du dossier ou du fichier respectivement.

Pour recevoir le fichier, dans cet exemple il est démontré comment un fichier envoyé dans le service POST est stocké directement dans un dossier de l'application :

services/fichier/post.js
const fichier = _req.getFile("fichier");
const dossier = _app.folder("temporaire");
if (dossier.exists() == false) {
dossier.mkdir();
}
fichier.save(_app.file(`temporaire/${fichier.name()}`));

Le code ci-dessus va créer le dossier temporaire dans l'application et enregistrer le fichier reçu dans ce dossier.

Pour télécharger le fichier, dans cet exemple il est démontré comment les octets du fichier peuvent être obtenus via le service GET :

services/fichier/get.js
const nomFichier = _req.getString("fichier");
const fichier = _app.file(`temporaire/${nomFichier}`);

if (fichier.exists() == false) {
_header.status(404);
_out.json(
_val.map()
.set("result", false)
.set("error", "fichier-inexistant")
);
_exec.stop();
}

_header.downloadFile(fichier.name());
_out.copy(fichier.input());

Conclusion​

Comme on peut le voir, nous pouvons intégrer l'upload de fichiers dans les services de l'API REST, y compris intégré avec OpenAPI.

Nous pouvons enregistrer le fichier dans la base de données, dans le dossier storage de l'application ou dans n'importe quel dossier de l'ordinateur.

Voir la documentation des ressources utilisées dans ce tutoriel qui permettent de manipuler les fichiers :

En base de données, nous pouvons enregistrer des images dans des champs de type image, et nous pouvons enregistrer tout type de fichier dans des champs de type file, comment nous enregistrons ou obtenons des fichiers est de la même manière dans tous les cas, qu'il s'agisse d'une image ou d'un fichier générique.