Passa al contenuto principale

Upload

Inviare file all'API e salvarli in una cartella o nel database.

Introduzione​

Quando dobbiamo inviare file ai servizi dell'API REST, abbiamo diversi modi per realizzare questo sviluppo in Netuno.

Normalmente i file vengono inviati nel corpo della richiesta HTTP strutturati nel formato multipart; è la procedura più comune nel frontend, ad esempio quando compiliamo un modulo e c'è quel campo per scegliere un file.

In alternativa possiamo inviare i byte dei file codificati in Base64 come valore in un oggetto JSON, quando vogliamo un'API REST 100% JSON.

File​

Attraverso _req.file possiamo passare il nome del campo e così ottenere l'oggetto del file che è stato inviato, vedi come:

const mioFile = _req.file("file");

Ovvero, _req contiene tutti i dati che riceviamo nel corpo della richiesta HTTP.

E tramite il metodo file permette di ottenere l'oggetto che rappresenta il file che è stato inviato.

Il valore ottenuto potrà essere null se non c'è nessun file.

Immagini​

Quando riceviamo immagini è comune la necessità di ridimensionarle, per garantire uniformità e per ridurre lo spazio occupato dalle immagini.

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

Con la risorsa di programmazione low-code poliglotta Image, possiamo manipolare immagini, ritagliare, ridimensionare, crearne di nuove, ecc.

Come Base64 nel JSON​

Per eseguire l'upload di un file nel JSON inviato al servizio, possiamo definire una proprietà con il valore:

data:image/png;base64,...

Dove nei ... segue la sequenza dei byte del file codificati in Base64.

Il tipo del file segue data, ad esempio:

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

Vedi l'esempio di un file di un'immagine PNG piccola inviata nel JSON a un servizio:

{
"file": "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=="
}

Possiamo ottenere il file inviato nel modo seguente:

const mioFile = _req.getFile("file");
if (mioFile != null) {
_out.json(
_val.map()
.set("risultato", true)
.set("nome", mioFile.name())
.set("dimensioneByte", mioFile.size())
.set("dimensioneKB", mioFile.sizeKB())
.set("dimensioneMB", mioFile.sizeMB())
.set("dimensioneGB", mioFile.sizeGB())
);
} else {
_out.json(
_val.map()
.set("risultato", false)
);
}

Esempio dell'output generato dal servizio sopra:

{
"nome": "file.png",
"risultato": true,
"dimensione": 1849,
"dimensioneKB": 1
}

Oggetto​

Se il file si trova in una struttura interna del JSON dentro il sottooggetto:

{
"persona": {
"file": "data:image/png;base64,..."
}
}

Può essere ottenuto nel modo seguente:

const mioFile = _req.getValues("persona").getFile("avatar");

Array​

Se il file si trova in una struttura interna del JSON dentro l'array:

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

Può essere ottenuto nel modo seguente:

const mioFile = _req.getValues("file").getFile(0);

OpenAPI​

Netuno supporta l'integrazione con OpenAPI e utilizza la definizione OpenAPI per validare la richiesta HTTP.

Maggiori informazioni su OpenAPI in Netuno.

Con il tipo file, Netuno valida se il valore inizia con il formato file nel JSON, ad esempio:

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

Per validare se si sta ricevendo un file in una proprietà dell'oggetto JSON:

post.in.json
{
"summary": "Upload di File",
"description": "Servizio che riceve un file nell'oggetto JSON.",
"type": "object",
"properties": {
"file": {
"type": "file"
}
},
"required": [
"file"
]
}

Per validare se si sta ricevendo una lista di file in array nel JSON:

post.in.json
{
"summary": "Upload di File",
"description": "Servizio che riceve una lista di file in array nel JSON.",
"properties": {
"file": {
"type": "array",
"items": {
"type": "file"
}
}
},
"required": [
"file"
]
}

Salvare il File​

Normalmente quando riceviamo un file nel servizio dobbiamo salvarlo, come ad esempio:

  • Database: File relativo a un record, come quello di un utente.
  • Storage: Cartella interna dell'applicazione che serve per gestire file in generale.
  • Cartella: Inserire il file in una cartella qualsiasi del sistema.

Base di Dati​

Nella gestione dei campi dei moduli, Netuno supporta il tipo image e file, ovvero:

  • Image: File di immagine in generale, JPG e PNG.
  • File: Qualsiasi tipo di file.

Immagina di avere un campo Immagine nel modulo Prodotto; possiamo salvare il file inviato via upload nel modo seguente:

Esempio utilizzando il DB Form:​

Nota l'utilizzo di _db.form:

services/prodotto/immagine/post.js
const prodottoUid = _req.getString("uid");
const prodottoImmagine = _req.getFile("immagine");

if (prodottoImmagine == null) {
_header.status(400);
_out.json(
_val.map()
.set("risultato", false)
.set("errore", "immagine-obbligatoria")
);
_exec.stop();
}

const dbRisultato = _db.form("prodotto")
.set("immagine", prodottoImmagine)
.where(_db.where("uid").equals(prodottoUid))
.update();

if (dbRisultato.getInt("prodotto") == 1) {
_out.json(
_val.map()
.set("risultato", true)
);
} else {
_header.status(404);
_out.json(
_val.map()
.set("risultato", false)
.set("errore", "uid-prodotto-non-esiste")
);
}

Esempio utilizzando l'update classico:​

Nota l'utilizzo di _db.update:

services/prodotto/immagine/post.js
const prodottoUid = _req.getString("uid");
const prodottoImmagine = _req.getFile("immagine");

if (prodottoImmagine == null) {
_header.status(400);
_out.json(
_val.map()
.set("risultato", false)
.set("errore", "immagine-obbligatoria")
);
_exec.stop();
}

const dbRisultato = _db.update(
"prodotto",
prodottoUid,
_val.map()
.set("immagine", prodottoImmagine)
);

if (dbRisultato == 1) {
_out.json(
_val.map()
.set("risultato", true)
);
} else {
_header.status(404);
_out.json(
_val.map()
.set("risultato", false)
.set("errore", "uid-prodotto-non-esiste")
);
}

Download del File dalla Base di Dati​

Per presentare il file memorizzato dobbiamo creare un servizio che fornisce i byte del file.

Nei file salvati nel database, come nell'esempio sopra, il file stesso viene inserito nello storage dell'applicazione e nel database viene memorizzato solo il nome finale del file.

Lo storage dell'applicazione è una cartella destinata a memorizzare e organizzare strutture di file generici.

Pertanto, nell'esempio sopra i file del database del modulo Prodotto e del campo Immagine vengono memorizzati in:

  • storage/database/prodotto/immagine/*

Netuno gestisce automaticamente il nome del file memorizzato per evitare nomi di file ripetuti.

Per ottenere il file, può essere creato un servizio che presenta il file immagine del prodotto, ad esempio:

services/prodotto/immagine/get.js
const prodottoUid = _req.getString("uid");

// DB Form:
const dbProdotto = _db.form("prodotto")
.where(_db.where("uid").equals(prodottoUid))
.first();
// Alternativa con DB Classico:
// const dbProdotto = _db.get("prodotto", prodottoUid)

if (dbProdotto == null) {
_header.status(404);
_out.json(
_val.map()
.set("risultato", false)
.set("errore", "prodotto-non-trovato")
);
_exec.stop();
}

const dbProdottoImmagineNome = dbProdotto.getString("immagine");

const storageProdottoImmagineFile = _storage.database(
"prodotto",
"immagine",
dbProdottoImmagineNome
);

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

_header.noCache();

_out.copy(storageProdottoImmagineFile.inputStream());

Nota che la risorsa _storage (Storage) viene utilizzata per ottenere il file:

_storage.database(
"prodotto", // Nome del modulo/tabella.
"immagine", // Nome del campo/colonna.
dbProdottoImmagineNome // Nome del file.
);

Nel database viene memorizzato solo il nome del file, mentre tramite _storage.database possiamo ottenere il file stesso.

Storage​

Possiamo salvare un file direttamente nello storage dell'applicazione.

Per non mescolare con i file del database, dobbiamo utilizzare la cartella filesystem, che ha lo scopo di memorizzare file generici non associati al database.

Per ricevere il file, in questo esempio viene mostrato come un file inviato nel servizio POST viene memorizzato direttamente nello storage:

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

Il file viene memorizzato nella cartella storage dell'applicazione, in:

  • storage/filesystem/server/upload/*

La risorsa Storage viene utilizzata per ottenere il percorso della cartella e del file:

_storage.filesystem(
"server", // Nome della sottocartella nel filesystem.
"upload", // Nome o percorso della sottocartella interna.
file.name() // Nome finale del file.
);

Vedi maggiori informazioni nella documentazione di Storage.

Per scaricare il file, in questo esempio viene mostrato come i byte del file possono essere ottenuti tramite il servizio GET:

services/file/get.js
const nomeFile = _req.getString("file");

const file = _storage.filesystem(
"server",
"upload",
nomeFile
).file();

if (file.exists() == false) {
_header.status(404);
_out.json(
_val.map()
.set("risultato", false)
.set("errore", "file-non-esiste")
);
_exec.stop();
}

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

Cartella​

Possiamo salvare un file direttamente in qualsiasi cartella del computer.

Ci sono due risorse molto utili in questi casi:

  • _app - Possiamo prendere qualsiasi cartella o file nell'applicazione.
  • _os - Possiamo prendere qualsiasi cartella o file nel computer.

Vedi la documentazione delle rispettive risorse App e OS; entrambe le risorse hanno i metodi folder e file, che permettono di indicare rispettivamente il percorso della cartella o del file.

Per ricevere il file, in questo esempio viene mostrato come un file inviato nel servizio POST viene memorizzato direttamente in una cartella nell'applicazione:

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

Il codice sopra creerà la cartella temp all'interno dell'applicazione e salverà il file ricevuto in questa cartella.

Per scaricare il file, in questo esempio viene mostrato come i byte del file possono essere ottenuti tramite il servizio GET:

services/file/get.js
const nomeFile = _req.getString("file");
const file = _app.file(`temp/${nomeFile}`);

if (file.exists() == false) {
_header.status(404);
_out.json(
_val.map()
.set("risultato", false)
.set("errore", "file-non-esiste")
);
_exec.stop();
}

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

Conclusione​

Come si può vedere, possiamo integrare l'upload di file nei servizi dell'API REST, anche integrato con OpenAPI.

Possiamo salvare il file nel database, nella cartella storage dell'applicazione o in qualsiasi cartella del computer.

Vedi la documentazione delle risorse utilizzate in questo tutorial che permettono di manipolare i file:

Nel database possiamo salvare immagini in campi di tipo image e possiamo salvare qualsiasi tipo di file in campi di tipo file; il modo in cui salviamo o otteniamo i file è lo stesso in ogni caso, sia che si tratti di un'immagine sia di un file generico.