1. Introduzione
In questo breve tutorial, impariamo come caricare un file utilizzando Spring Boot.
Vediamo come fare tramite un form submission.
2. Perché gli Upload di File Sono Importanti?
Caricare file è un caso d’uso molto comune per le moderne applicazioni web, ad esempio, allegare documenti nei form o caricare foto profilo. Anche se è facile da capire, gestire i file correttamente presenta alcune sfide. Ad esempio, esporre un’applicazione senza vincoli sulla dimensione dei file che gli utenti possono caricare può sovraccaricare la nostra applicazione o consumare tutto lo spazio di archiviazione disponibile. Pertanto, prima di accettare qualsiasi file, dobbiamo convalidare il suo tipo e contenuto e gestire gli errori in modo elegante.
Quindi, nella nostra applicazione, utilizzeremo le opzioni offerte da Spring Boot per renderla robusta, imponendo un limite alla dimensione del file e fornendo feedback affinché l’utente comprenda cosa sta accadendo.
3. Flusso di Caricamento
Il seguente diagramma di sequenza mostra visivamente le interazioni tra l’Utente, il Browser e la nostra applicazione Spring Boot:
Mi sembra che non ci sia alcun contenuto fornito da tradurre. Potresti per favore fornire il testo che desideri venga tradotto in italiano?
4. Configurazione di Maven
Iniziamo includendo le nostre dipendenze. Assicuriamoci di ottenere le ultime versioni di spring-boot-starter-web e spring-boot-starter-thymeleaf.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
Per un nuovo progetto, è meglio partire da Spring Initializr e includere queste dipendenze:
Spring Web
: framework MVC & Tomcat incorporatoThymeleaf
: Un moderno motore di template Java lato server per ambienti web e standalone.
5. Implementazione del File Upload
Iniziamo a implementare la nostra applicazione.
5.1 Upload Form in Thymeleaf
Innanzitutto, creeremo un template Thymeleaf che include un form di base con un file input e un pulsante per inviare il form al nostro endpoint.
Dobbiamo creare un file file-upload.html
all’interno di una cartella chiamata templates
nella cartella delle risorse dell’applicazione:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mini.css/3.0.1/mini-default.min.css">
</head>
<body>
<div class="container">
<header>
<h2>File Upload</h2>
</header>
<main>
<form action="#" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="input-group vertical">
<input type="file" name="file" required>
<button type="submit">Upload</button>
</div>
</form>
<div th:if="${message}" class="alert"
th:classappend="${message.contains('Success') ? 'alert-success' : 'alert-error'}"
th:text="${message}"></div>
</main>
</div>
</body>
</html>
enctype=multipart/form-data
. Senza di esso, il browser non invierà il file come dati multipart e il caricamento non funzionerà come previsto.Dopo il form, abbiamo aggiunto un placeholder per visualizzare i messaggi.
Nel nostro template, stiamo utilizzando mini.css in modo da non doverci occupare dello stile dell’applicazione, ma evitando comunque l’aspetto predefinito del browser.
Questo è tutto per il lato front-end.
5.2. Configurazione dello Storage
Successivamente, dobbiamo decidere dove salvare i file che i nostri utenti caricano. Aggiungiamo una proprietà files.upload-dir
nel file application.properties
:
server.port=8090
files.upload-dir=./uploadedfiles
Questo sarà il luogo in cui salveremo i file.
5.3 Controller Caricamento File
Ora implementeremo un controller chiamato FileUploadController
che espone un endpoint per servire il template che abbiamo creato al browser:
@Controller
public class FileUploadController {
@GetMapping("/")
public String fileUpload() {
return "file-upload";
}
}
Dobbiamo solo assicurarci che la stringa che restituiamo corrisponda al nome del template che abbiamo creato in precedenza.
Successivamente, nello stesso controller, creeremo un endpoint POST /upload
per gestire le richieste di caricamento file.
Questo endpoint utilizzerà un MultipartFile
come parametro della richiesta per ricevere il file. MultipartFile
è un’interfaccia che estende InputStreamSource
:
@Value("${files.upload-dir}")
private String uploadDir;
@PostMapping("/upload")
public String handleUpload(
@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes
) {
if (file.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "Please select a file");
return "redirect:/";
}
try {
String filename = StringUtils.cleanPath(file.getOriginalFilename());
Path destination = Paths.get(uploadDir, filename);
Files.createDirectories(destination.getParent());
file.transferTo(destination);
redirectAttributes.addFlashAttribute("message", "Success: " + filename + " (" + file.getSize() + " bytes)");
return "redirect:/";
} catch (IOException e) {
redirectAttributes.addFlashAttribute("message", "Error: " + e.getMessage());
return "redirect:/";
}
}
In breve, prima otteniamo il nome del file dall’oggetto file
di input. Poi, creiamo le cartelle necessarie per salvare destination
utilizzando StringUtils.cleanPath
in modo da prevenire attacchi di simple path traversal. Successivamente, salviamo i dati del file in arrivo in destination
e inviamo un messaggio di successo. In caso di fallimento, notifichiamo l’utente di conseguenza, giusto per fargli sapere cosa sta succedendo.
Inoltre, nel controller, abbiamo iniettato la directory di upload utilizzando @Value
e implementato un metodo @PostMapping
che accetta il MultipartFile
insieme a un oggetto RedirectAttributes
. L’interfaccia RedirectAttributes
in Spring MVC viene utilizzata per passare dati durante un reindirizzamento. In particolare, abbiamo utilizzato addFlashAttribute
per salvare temporaneamente il messaggio nella sessione, in modo che sia disponibile dopo il reindirizzamento quando il template viene elaborato, permettendo di visualizzare il feedback corretto.
StringUtils.cleanPath
non dovrebbe essere utilizzato in un contesto di sicurezza. Dovrebbero essere utilizzati altri meccanismi per prevenire problemi di traversata dei percorsi.Ora, se eseguiamo l’applicazione utilizzando mvn spring-boot:run
e andiamo su localhost:8090
, dovremmo vedere il nostro form e poter caricare piccoli file.
6. Gestione di File di Grandi Dimensioni
Possiamo ora caricare file di dimensioni ridotte senza problemi. Ma se la dimensione del file è superiore a 1MB (il limite predefinito), la nostra applicazione non avrà successo e il browser potrebbe visualizzare un errore. Se vogliamo consentire agli utenti di caricare file di dimensioni maggiori, possiamo configurare una soglia più alta.
6.1 Configurare la Dimensione Massima del File
Per configurare la dimensione massima del file consentita, cambiamo le proprietà spring.servlet.multipart.max-file-size
e spring.servlet.multipart.max-request-size
in application.properties
:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=11MB
Questo non è sufficiente da solo. Dobbiamo anche configurare il nostro servlet container affinché non abortisca una connessione se la richiesta in arrivo supera il suo valore massimo predefinito prima che la gestione dei multipart di Spring entri in azione. La nostra applicazione Spring Boot utilizza Tomcat incorporato per impostazione predefinita. Possiamo configurarlo per accettare richieste POST di dimensioni maggiori (o praticamente illimitate) in modo che i controlli delle dimensioni siano gestiti esclusivamente dalla configurazione dei multipart di Spring:
server.tomcat.max-http-form-post-size=-1
server.tomcat.max-swallow-size=-1
Ora, se proviamo a caricare file più grandi di 10MB, Spring MVC impedirà il caricamento, ma il comportamento predefinito potrebbe comunque risultare in una pagina di errore non gestita (come una Whitelabel Error Page che mostra un errore 500 relativo a MaxUploadSizeExceededException
). Se vogliamo personalizzare questo comportamento affinché l’interazione nell’interfaccia utente sia più fluida, possiamo aggiungere una gestione degli errori personalizzata.
6.2 Gestione degli Errori Personalizzata
Dietro le quinte, Spring MVC solleva una MaxUploadSizeExceededException
quando un file supera il limite di dimensione configurato. Possiamo creare un @ControllerAdvice
per catturare MultipartException
a livello globale e gestire questi errori in modo elegante. Catturiamo MultipartException
perché MaxUploadSizeExceededException
la estende, permettendoci di gestire anche altri potenziali errori correlati a multipart:
@ControllerAdvice
public class FileUploadExceptionHandler {
@ExceptionHandler(MultipartException.class)
public String handleSizeExceeded(RedirectAttributes attrs) {
attrs.addFlashAttribute("message",
"File exceeds size limit!");
return "redirect:/";
}
}
Configurare un @ControllerAdvice
offre un modo centralizzato per gestire le eccezioni tra più controller e restituire messaggi di errore comprensibili per l’utente o effettuare un reindirizzamento con feedback appropriato.
7. Suggerimenti per Ambienti di Produzione
Sebbene questo tutorial spieghi un semplice caricamento di file, ci sono diverse considerazioni aggiuntive per gli ambienti di produzione che non vengono trattate qui.
Prima di tutto, non stiamo limitando il tipo di file consentiti per il caricamento. Potremmo risolvere questo problema con un semplice controllo dell’estensione del file o implementare una validazione più approfondita, ad esempio controllando il tipo MIME o scansionando alla ricerca di virus e malware.
In ambienti di produzione dove si prevedono file di grandi dimensioni, consideriamo di utilizzare un approccio di streaming per evitare di caricare l’intero file in memoria sul server. In alternativa, valutiamo di utilizzare upload a pezzi dal lato client, dove il file è suddiviso in parti più piccole, caricate separatamente e poi ricomposte dal lato server.
8. Conclusione
In questo articolo, abbiamo imparato come implementare il caricamento di file in Spring Boot utilizzando un semplice HTML form, Thymeleaf e un controller Spring MVC. Abbiamo anche visto come configurare i limiti per i file di grande dimensione e personalizzare il processo di gestione degli errori per le eccezioni relative alle dimensioni.
Puoi trovare il codice completo su GitHub.