Salta al contenuto principale

Come Caricare un File in Spring Boot

Spring Spring-Boot File-Upload Thymeleaf
Autore
Harpal Singh
Software Engineer
Tradotto da
Namastecode
Indice dei contenuti

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:

sequenceDiagram participant User as User participant Browser as Browser participant Controller as UploadController participant Filesystem as File System User->>Browser: Selects file and submits form Browser->>Controller: POST /upload (multipart/form-data) Controller->>Controller: Check if file is empty alt File is valid Controller->>Filesystem: Save file bytes to disk Controller-->>Browser: Redirect to / with success message else File is invalid Controller-->>Browser: Redirect to / with error message end Browser->>User: Display upload status message

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 incorporato
  • Thymeleaf: Un moderno motore di template Java lato server per ambienti web e standalone.
Suggerimento: Utilizza Spring Boot DevTools per riavvii automatici durante lo sviluppo.

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>
Un errore molto comune è dimenticare di aggiungere l’attributo 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
Errore Comune: La dimensione della richiesta deve essere maggiore della dimensione del file per tenere conto dell’overhead dei dati del form.

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
Preoccupazioni di Sicurezza: Impostare limiti a -1 non è generalmente raccomandato poiché consente richieste di dimensioni potenzialmente illimitate, il che può portare ad attacchi di Denial of Service (DoS). Idealmente, dovremmo limitare la dimensione della richiesta a un valore ragionevole appropriato per il caso d’uso dell’applicazione.

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.

Related

Richieste HTTP con Rest Client nel framework Spring 6
Spring Restclient HTTP Rest Openai
Comprendere la sintassi Cron di @Scheduled di Spring
Spring Cron Scheduling
Testcontainers nei Test di Integrazione di Spring Boot
Spring Testcontainers Testing