Accès rapide :
Le formulaire HTML
La servlet permettant de recevoir et de sauvegarder vos fichiers
Sécuriser les fichiers téléchargés
Décodage et renvoi des images à l'utilisateur
Nous allons voir, dans ce chapitre, comment coder une servlet permettant le transfert de fichers du navigateur vers le serveur Java EE. On parle d'upload de fichier.
Le langage HTML permet de définir un formulaire d'upload (de transfert) de fichier sur le serveur Web. L'exemple de code suivant permet de sélectionner un ou plusieurs fichiers d'images à « uploader ».
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Veuillez choisir les images à uploader.</title> <script> /* Cette fonction permet d'afficher une vignette pour chaque image sélectionnée */ function readFilesAndDisplayPreview(files) { let imageList = document.querySelector('#list'); imageList.innerHTML = ""; for ( let file of files ) { let reader = new FileReader(); reader.addEventListener( "load", function( event ) { let span = document.createElement('span'); span.innerHTML = '<img height="60" src="' + event.target.result + '" />'; imageList.appendChild( span ); }); reader.readAsDataURL( file ); } } </script> </head> <body style="text-align: center"> <header> <h1>Veuillez choisir les images à uploader.</h1> </header> <form method="post" action="upload" enctype="multipart/form-data"> Fichiers sélectionnés : <input type="file" name="multiPartServlet" accept="image/*" multiple onchange="readFilesAndDisplayPreview(this.files);" /> <br/> <input type="submit" value="Upload" /> <br/> <div id="list"></div> </form> </body> </html> |
Pour ce qui est du tag <form>
correspondant au formulaire :
L'attribut method="POST"
permet de choisir le mode de soumission du formulaire. Le mode POST
est requis pour transférer le
contenu du fichier.
L'attribut action="/upload
correspond à l'URL de la servlet à laquelle envoyer les fichiers.
L'attribut enctype="multipart/form-data"
permet de soumettre des fichiers et non pas des champs de formulaires
(ce qui est fait par défaut). Cet attribut est obligatoire dans notre exemple.
Pour ce qui est du tag <input />
de sélection de fichiers :
L'attribut accept
permet de filter les fichier et de vous proposer que des fichiers d'images.
L'attribut multiple
permet d'accepter la sélection multiple de fichiers.
Il nous faut maintenant passer au code côté serveur : une servlet de sauvegarde des fichiers transférés. Voici son contenu et quelques explications suivront.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
package fr.koor.webstore.ihm; import java.io.File; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; /* * Notre serlvet permettant de récupérer les fichiers côté serveur. * Elle répondra à l'URL /upload dans l'application Web considérée. */ @WebServlet( urlPatterns = "/upload" ) @MultipartConfig( fileSizeThreshold = 1024 * 1024, maxFileSize = 1024 * 1024 * 5, maxRequestSize = 1024 * 1024 * 5 * 5 ) public class UploadServlet extends HttpServlet { private static final long serialVersionUID = 1273074928096412095L; /* * Chemin dans lequel les images seront sauvegardées. */ public static final String IMAGES_FOLDER = "/Images"; public String uploadPath; /* * Si le dossier de sauvegarde de l'image n'existe pas, on demande sa création. */ @Override public void init() throws ServletException { uploadPath = getServletContext().getRealPath( IMAGES_FOLDER ); File uploadDir = new File( uploadPath ); if ( ! uploadDir.exists() ) uploadDir.mkdir(); } /* * Récupération et sauvegarde du contenu de chaque image. */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { for ( Part part : request.getParts() ) { String fileName = getFileName( part ); String fullPath = uploadPath + File.separator + fileName; part.write( fullPath ); } } /* * Récupération du nom du fichier dans la requête. */ private String getFileName( Part part ) { for ( String content : part.getHeader( "content-disposition" ).split( ";" ) ) { if ( content.trim().startsWith( "filename" ) ) return content.substring( content.indexOf( "=" ) + 2, content.length() - 1 ); } return "Default.file"; } } |
Le fait que le formulaire soit posté en mode multipart/form-data
fait qu'on peut recevoir plusieurs parties (fichiers) côté serveur.
La méthode request.getParts()
permet donc de récupérer un ensemble d'objets, de type javax.servlet.http.Part
.
Chaque instance contiendra le nom et le contenu de chaque fichier.
Une fois ces informations récupérées (et notamment les octets de chaque fichier), il faut les sauvegarder en local sur le serveur.
En fait, vous n'avez quasiment rien à faire étant données que Java EE et plus précisément l'API des servlets propose du code prêt à l'emploi :
la méthode part.write( fullPath )
écrit les octets de l'image considérée dans le fichier spécifié en premier paramètre.
Dans notre exemple, les images sont stockées dans un répertoire visible du site Web : vous pouvez donc tester le bon fonctionnement de votre
code avec un simple navigateur. En envoyant une URL du genre http://localhost:8080/YourApp/Images/imagesName.png
vous devriez
pouvoir constater la présence de votre image sur le serveur.
Si les informations transférées sont sensibles en termes de sécurité, vous pouvez envisager quelques améliorations. En premier lieu, vous pouvez
déplacer le répertoire de sauvegarde de vos fichiers en dehors d'un répertoire visible par HTTP. En second lieu, vous pouvez aussi envisager
de chiffrer (de crypter) vos fichiers en utilisant les possibilités du paquetage javax.crypto
.
Voici quelques changements, en ce sens, que vous pourriez apporter à votre servlet d'upload.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
package fr.koor.webstore.ihm; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import javax.crypto.Cipher; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; /* * Notre serlvet permettant de récupérer les fichiers côté serveur. * Elle répondra à l'URL /upload dans l'application Web considérée. */ @WebServlet( urlPatterns = "/upload" ) @MultipartConfig( fileSizeThreshold = 1024 * 1024, maxFileSize = 1024 * 1024 * 5, maxRequestSize = 1024 * 1024 * 5 * 5 ) public class UploadServlet extends HttpServlet { private static final long serialVersionUID = 1273074928096412095L; @Override public void init() throws ServletException { File uploadDir = new File( Shared.IMAGES_FOLDER ); if ( ! uploadDir.exists() ) uploadDir.mkdir(); } /* * Récupération et sauvegarde du contenu de chaque image. * Puis on encrypte le contenu du fichier */ @Override protected void doPost( HttpServletRequest request, HttpServletResponse resp ) throws ServletException, IOException { for ( Part part : request.getParts() ) { String fileName = getFileName( part ); String fullPath = Shared.IMAGES_FOLDER + File.separator + fileName; part.write( fullPath ); // Ecriture du fichier original File srcFile = new File( fullPath ); // On récupère ses octets byte [] buffer = new byte[ (int) srcFile.length() ]; try ( FileInputStream inputStream = new FileInputStream( srcFile ) ) { inputStream.read( buffer ); } try { // On encrypte son contenu avec AES Cipher cipher = Cipher.getInstance( "AES" ); cipher.init( Cipher.ENCRYPT_MODE, Shared.SECRET_KEY ); try (FileOutputStream outputStream = new FileOutputStream( fullPath ) ) { byte [] outputBytes = cipher.doFinal( buffer ); outputStream.write( outputBytes ); } } catch( Exception exception ) { exception.printStackTrace(); } } } /* * Récupération du nom du fichier dans la requête. */ private String getFileName( Part part ) { for ( String content : part.getHeader( "content-disposition" ).split( ";" ) ) { if ( content.trim().startsWith( "filename" ) ) return content.substring( content.indexOf( "=" ) + 2, content.length() - 1 ); } return "Default.file"; } } |
OutOfMemoryError
), il pourrait être judicieux de limiter la taille des fichiers
téléchargés. En effet, tel que le code vous est proposé, on charge tout le fichier en mémoire pour l'encrypter. Je vous laisse améliorer ce
point.
Il faudra aussi ajouter cette classe pour partager ses informations avec le prochain extrait de code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package fr.koor.webstore.ihm; import java.security.Key; import javax.crypto.spec.SecretKeySpec; public class Shared { public static final String IMAGES_FOLDER = "/aPath/Images"; public static final Key SECRET_KEY = new SecretKeySpec( "Une clé secrête : chut !!!!!!!".getBytes(), "AES" ); } |
L'algorithme de chiffrage utilisé est symétrique : cela signifie que c'est la même clé qui permet le chiffrage et le déchiffrage. A titre d'exemple, voici un dernier bout de code qui montre comment décoder en renvoyer une image en clair à un utilisateur. Afin de simplifier vos tests, je propose que le nom de l'image demandée soit passé en paramètre de l'URL. Vous pourriez aussi améliorer ce point.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package fr.koor.webstore.ihm; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import javax.crypto.Cipher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(urlPatterns = "/getimage") public class ImageServlet extends HttpServlet { private static final long serialVersionUID = 1252164443969770001L; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String filename = request.getParameter( "filename" ); if ( filename == null ) return; String fullPath = getServletContext().getRealPath(Shared.IMAGES_FOLDER) + File.separator + filename; File srcFile = new File( fullPath ); byte [] buffer = new byte[(int)srcFile.length()]; try ( FileInputStream inputStream = new FileInputStream(srcFile)) { inputStream.read(buffer); } response.setContentType( "image/png" ); try { Cipher cipher = Cipher.getInstance( "AES" ); cipher.init( Cipher.DECRYPT_MODE, Shared.SECRET_KEY ); byte [] imageBytes = cipher.doFinal( buffer ); try ( OutputStream outputStream = response.getOutputStream() ) { outputStream.write( imageBytes ); } } catch( Exception exception ) { exception.printStackTrace(); } } } |
image/png
.
Vous pourriez le rendre dynamique (image/gif
, image/jpeg
, ...) en fonction de l'extension du fichier demandé.
Pour tester cette servlet, demandez une URL du type http://localhost:8080/YourApp/getimage?filename=file.png
à votre navigateur.
Votre image devrait être renvoyée et correctement s'afficher dans le navigateur.
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :