Introduction
Le téléchargemant de fichiers est une opération courante en développement backend. Deux approches principales sont utilisées : l'une via un serveur web comme Nginx pour les fichiers publics, et l'autre via le backend pour intégrer une logique métier. Cet article détaille la seconde méthode, qui implique la lecture des fichiers en octets et leur écriture dans le flux de réponse HTTP.
- Chargement complet en mémoire
Cette technique est adaptée aux fichiers de petite taille. L'exemple ci-dessous montre comment lire intégralement un fichier local et le transmettre au client :
@GetMapping("/telechargement/complet")
public void transfertComplet(HttpServletResponse reponse, @RequestParam("chemin") String cheminFichier) {
File fichierSource = new File(cheminFichier);
if (!fichierSource.exists()) {
throw new BusinessException("Fichier introuvable. Veuillez vérifier le chemin.");
}
try (InputStream fluxLecture = new BufferedInputStream(Files.newInputStream(fichierSource.toPath()))) {
byte[] contenu = new byte[fluxLecture.available()];
fluxLecture.read(contenu);
reponse.reset();
reponse.setCharacterEncoding("UTF-8");
reponse.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fichierSource.getName(), "UTF-8"));
reponse.addHeader("Content-Length", String.valueOf(fichierSource.length()));
OutputStream fluxEcriture = new BufferedOutputStream(reponse.getOutputStream());
reponse.setContentType("application/octet-stream");
fluxEcriture.write(contenu);
fluxEcriture.flush();
fluxEcriture.close();
} catch (IOException e) {
throw new RuntimeException("Erreur lors du transfert complet.", e);
}
}
Attention : cette méthode peut entraîner des problèmes de mémoire vive pour les fichiers volumineux.
- Transfert par morceaux via un flux
Pour une gestion efficace des fichiers de toute taille, il est recommandé d'utiliser un flux avec une lecture par blocs :
@GetMapping("/telechargement/flux")
public void transfertParFlux(HttpServletResponse reponse, @RequestParam("chemin") String cheminFichier) {
File fichierSource = new File(cheminFichier);
if (!fichierSource.exists()) {
throw new BusinessException("Fichier non trouvé.");
}
reponse.reset();
reponse.setCharacterEncoding("UTF-8");
reponse.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fichierSource.getName(), "UTF-8"));
reponse.setContentType("application/octet-stream");
try (InputStream fluxLecture = new BufferedInputStream(Files.newInputStream(fichierSource.toPath()))) {
OutputStream fluxEcriture = new BufferedOutputStream(reponse.getOutputStream());
byte[] tampon = new byte[1024];
int octetsLus;
while ((octetsLus = fluxLecture.read(tampon)) != -1) {
fluxEcriture.write(tampon, 0, octetsLus);
}
fluxEcriture.close();
} catch (IOException e) {
throw new RuntimeException("Échec du transfert par morceaux.", e);
}
}
- Récupération depuis une source réseau
Pour télécharger un fichier à partir d'une URL distante, on peut établir une connexion et transmettre les données au frontend :
@GetMapping("/telechargement/reseau")
public void transfertDepuisReseau(HttpServletResponse reponse, @RequestParam("url") String urlCible, @RequestParam("nom") String nomFichier) {
try {
URL urlRessource = new URL(urlCible);
URLConnection connexion = urlRessource.openConnection();
InputStream fluxLecture = connexion.getInputStream();
reponse.reset();
reponse.setContentType(connexion.getContentType());
reponse.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(nomFichier, "UTF-8"));
byte[] tampon = new byte[1024];
int longueur;
OutputStream fluxEcriture = reponse.getOutputStream();
while ((longueur = fluxLecture.read(tampon)) > 0) {
fluxEcriture.write(tampon, 0, longueur);
}
fluxLecture.close();
} catch (IOException e) {
throw new RuntimeException("Problème lors du téléchargement réseau.", e);
}
}
- Sauvegarde locale depuis une URL
Cette méthode permet de récupérer un contenu distant et de le stocker sur le serveur :
@GetMapping("/sauvegarde/reseau")
public void sauvegardeDepuisReseau(@RequestParam("url") String urlSource, @RequestParam("dest") String cheminDestination) {
try {
URL urlRessource = new URL(urlSource);
URLConnection connexion = urlRessource.openConnection();
InputStream fluxLecture = connexion.getInputStream();
FileOutputStream fluxFichier = new FileOutputStream(cheminDestination);
byte[] tampon = new byte[1024];
int octetsLus;
while ((octetsLus = fluxLecture.read(tampon)) != -1) {
fluxFichier.write(tampon, 0, octetsLus);
}
fluxFichier.close();
fluxLecture.close();
} catch (IOException e) {
throw new RuntimeException("Erreur de sauvegarde locale.", e);
}
}
Considérations techniques
En Java, les flux d'entrée (InputStream) sont destinés à la lecture de données, tandis que les flux de sortie (OutputStream) gèrent l'écriture. Lors du renvoi de fichiers au frontend, le flux de sortie est automatiquement géré par l'objet HttpServletResponse ; il est donc inutile de retourner explicitement la réponse.