Participer au site avec un Tip
Rechercher
 

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 :

Servlet d'upload de fichiers

Utilisation des APIs EL et JSTL Servlet d'export de données au format XLSX



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 formulaire HTML

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>
Fichier upload.html : exemple d'un formulaire HTML permettant la sélection des fichiers à transférer.

Pour ce qui est du tag <form> correspondant au formulaire :

Pour ce qui est du tag <input /> de sélection de fichiers :

La servlet permettant de recevoir et de sauvegarder vos 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";
    }

}
Fichier UploadServlet.java

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.

Sécuriser les fichiers téléchargés

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";
    }

}
Enregistrement des fichiers d'images avec un chiffrement symétrique de type AES
afin d'éviter des crashs du serveur (en 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" );
       
}
Fichier Shared.java : on partage quelques informations.

Décodage et renvoi des images à l'utilisateur

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();
        }

    }
    
}
Exemple d'une servlet de décodage d'une image encriptée.
vous pourriez encore améliorer un point. Dans l'exemple, le content type renvoyé correspond à 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.



Utilisation des APIs EL et JSTL Servlet d'export de données au format XLSX