markdown Gestion de fichiers
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown Gestion de fichiers相关的知识,希望对你有一定的参考价值。
## Envoi de fichier- Pour envoyer un fichier depuis un formulaire, il faut utiliser une requête de type multipart via <form ... enctype="multipart/form-data">.- Pour récupérer les données d'une telle requête depuis le serveur, l'API Servlet 3.0 fournit la méthode request.getParts().- Pour rendre disponible cette méthode dans une servlet, il faut compléter sa déclaration dans le fichier web.xml avec une section <multipart-config>.- Pour vérifier si une Part contient un fichier, il faut vérifier son type en analysant son en-tête Content-Disposition.- Pour écrire le contenu d'un tel fichier sur le serveur, il suffit ensuite de récupérer le flux via la méthode part.getInputStream() et de le manipuler comme on manipulerait un fichier local.- Lors de la sauvegarde de fichiers sur un serveur, il faut penser aux contraintes imposées par le web : noms de fichiers identiques, contenus de fichiers identiques, faux fichiers, etc.## A voirhttp://balusc.omnifaces.org/2009/12/uploading-files-in-servlet-30.html## Comment gérer les fichiers de mêmes noms ? Le plus simple, c'est de toujours renommer les fichiers reçus avant de les enregistrer sur le disque. Tout un tas de solutions, que vous pouvez combiner, s'offrent alors à vous:- Ajouter un suffixe à votre fichier si un fichier du même nom existe déjà sur le disque.- Placer vos fichiers dans des répertoires différents selon leur type ou leur extension.- Renommer, préfixer ou suffixer vos fichiers par un timestamp.- Vous baser sur un hashCode du contenu binaire du fichier pour générer une arborescence et un nom unique.## Où stocker les fichiers reçus ?- Stocker les fichiers au sein de l'application web, dans un sous-répertoire du dossier WebContent d'Eclipse par exemple (Cette solution présente un inconvénient : en stockant les fichiers directement dans le conteneur de votre application, vous les rendez vulnérables à un écrasement lors d'un prochain redémarrage serveur ou d'un prochain redéploiement de l'application).- Stocker les fichiers en dehors de l'application, dans un répertoire du disque local (Cette solution peut poser un problème important : tous les fichiers placés en dehors de l'application, un peu à la manière des fichiers placés sous son répertoire /WEB-INF, sont invisibles au contexte web, c'est-à-dire qu'ils ne sont pas accessibles directement via une URL. Autrement dit, vous ne pourrez pas proposer aux utilisateurs de télécharger ces fichiers !).- Une des solutions possibles est alors de créer une servlet dont l'unique objectif est de charger ces fichiers depuis le chemin en dehors du conteneur web (ou depuis une base de données), et de les transmettre en flux continu.# Mise en place## 1: La vue```<form action="<c:url value="/upload" />" method="post" enctype="multipart/form-data"> <fieldset> <legend>Envoi de fichier</legend> <label for="description">Description du fichier</label> <input type="text" id="description" name="description" value="<c:out value="${fichier.description}"/>" /> <span class="erreur">${form.erreurs['description']}</span> <br /> <label for="fichier">Emplacement du fichier <span class="requis">*</span></label> <input type="file" id="fichier" name="fichier" value="<c:out value="${fichier.nom}"/>" /> <span class="erreur">${form.erreurs['fichier']}</span> <br /> <input type="submit" value="Envoyer" class="sansLabel" /> <br /> <p class="${empty form.erreurs ? 'succes' : 'erreur'}">${form.resultat}</p> </fieldset></form>```## 2: La section <multipart-config> dans le fichier web.xml- <location> contient une URL absolue vers un répertoire du système. Un chemin relatif au contexte de l'application n'est pas supporté dans cette balise, il s'agit bien là d'un chemin absolu vers le système. Cette URL sera utilisée pour stocker temporairement un fichier lors du traitement des fragments d'une requête, lorsque la taille du fichier est plus grande que la taille spécifiée dans <file-size-threshold>. Si vous précisez ici un répertoire qui n'existe pas sur le disque, alors Tomcat enverra une java.io.IOException lorsque vous tenterez d'envoyer un fichier plus gros que cette limite.- <file-size-threshold> précise la taille en octets à partir de laquelle un fichier reçu sera temporairement stocké sur le disque- <max-file-size> précise la taille maximum en octets autorisée pour un fichier envoyé. Si la taille d'un fichier envoyé dépasse cette limite, le conteneur enverra une exception. En l'occurrence, Tomcat lancera une IllegalStateException.- <max-request-size> précise la taille maximum en octets autorisée pour une requête multipart/form-data. Si la taille totale des données envoyées dans une seule requête dépasse cette limite, le conteneur enverra une exception.- En paramétrant ainsi notre servlet, toutes les données multipart/form-data seront disponibles à travers la méthode request.getParts(). Celle-ci retourne une collection d'éléments de type Part, et doit être utilisée en lieu et place de l'habituelle méthode request.getParameter() pour récupérer les contenus des champs de formulaire.```<web-app> <servlet> <servlet-name>Upload</servlet-name> <servlet-class>com.servlets.Upload</servlet-class> <init-param> <param-name>chemin</param-name> <param-value>/fichiers/</param-value> </init-param> <multipart-config> <location>c:/fichiers</location> <max-file-size>10485760</max-file-size> <!-- 10 Mo --> <max-request-size>52428800</max-request-size> <!-- 5 x 10 Mo --> <file-size-threshold>1048576</file-size-threshold> <!-- 1 Mo --> </multipart-config> </servlet> <servlet-mapping> <servlet-name>Upload</servlet-name> <url-pattern>/upload</url-pattern> </servlet-mapping></web-app>```## 3: Bean représentant un fichier```package com.beans;public class Fichier { private String description; private String nom; public String getDescription() { return description; } public void setDescription( String description ) { this.description = description; } public String getNom() { return nom; } public void setNom( String nom ) { this.nom = nom; }}```## 4: Objet métier en charge du traitement du formulaire```import com.beans.Fichier;public final class UploadForm { private static final String CHAMP_DESCRIPTION = "description"; private static final String CHAMP_FICHIER = "fichier"; private static final int TAILLE_TAMPON = 10240; // 10 ko private String resultat; private Map<String, String> erreurs = new HashMap<String, String>(); public String getResultat() { return resultat; } public Map<String, String> getErreurs() { return erreurs; } public Fichier enregistrerFichier( HttpServletRequest request, String chemin ) { /* Initialisation du bean représentant un fichier */ Fichier fichier = new Fichier(); /* Récupération du champ de description du formulaire */ String description = getValeurChamp( request, CHAMP_DESCRIPTION ); /* * Récupération du contenu du champ fichier du formulaire. Il faut ici * utiliser la méthode getPart(), comme nous l'avions fait dans notre * servlet auparavant. */ String nomFichier = null; InputStream contenuFichier = null; try { Part part = request.getPart( CHAMP_FICHIER ); /* * Il faut déterminer s'il s'agit bien d'un champ de type fichier : * on délègue cette opération à la méthode utilitaire * getNomFichier(). */ nomFichier = getNomFichier( part ); /* * Si la méthode a renvoyé quelque chose, il s'agit donc d'un * champ de type fichier (input type="file"). */ if ( nomFichier != null && !nomFichier.isEmpty() ) { /* * Antibug pour Internet Explorer, qui transmet pour une * raison mystique le chemin du fichier local à la machine * du client... * * Ex : C:/dossier/sous-dossier/fichier.ext * * On doit donc faire en sorte de ne sélectionner que le nom * et l'extension du fichier, et de se débarrasser du * superflu. */ nomFichier = nomFichier.substring( nomFichier.lastIndexOf( '/' ) + 1 ) .substring( nomFichier.lastIndexOf( '\\' ) + 1 ); /* Récupération du contenu du fichier */ contenuFichier = part.getInputStream(); } } catch ( IllegalStateException e ) { /* * Exception retournée si la taille des données dépasse les limites * définies dans la section <multipart-config> de la déclaration de * notre servlet d'upload dans le fichier web.xml */ e.printStackTrace(); setErreur( CHAMP_FICHIER, "Les données envoyées sont trop volumineuses." ); } catch ( IOException e ) { /* * Exception retournée si une erreur au niveau des répertoires de * stockage survient (répertoire inexistant, droits d'accès * insuffisants, etc.) */ e.printStackTrace(); setErreur( CHAMP_FICHIER, "Erreur de configuration du serveur." ); } catch ( ServletException e ) { /* * Exception retournée si la requête n'est pas de type * multipart/form-data. Cela ne peut arriver que si l'utilisateur * essaie de contacter la servlet d'upload par un formulaire * différent de celui qu'on lui propose... pirate ! :| */ e.printStackTrace(); setErreur( CHAMP_FICHIER, "Ce type de requête n'est pas supporté, merci d'utiliser le formulaire prévu pour envoyer votre fichier." ); } /* Si aucune erreur n'est survenue jusqu'à présent */ if ( erreurs.isEmpty() ) { /* Validation du champ de description. */ try { validationDescription( description ); } catch ( Exception e ) { setErreur( CHAMP_DESCRIPTION, e.getMessage() ); } fichier.setDescription( description ); /* Validation du champ fichier. */ try { validationFichier( nomFichier, contenuFichier ); } catch ( Exception e ) { setErreur( CHAMP_FICHIER, e.getMessage() ); } fichier.setNom( nomFichier ); } /* Si aucune erreur n'est survenue jusqu'à présent */ if ( erreurs.isEmpty() ) { /* Écriture du fichier sur le disque */ try { ecrireFichier( contenuFichier, nomFichier, chemin ); } catch ( Exception e ) { setErreur( CHAMP_FICHIER, "Erreur lors de l'écriture du fichier sur le disque." ); } } /* Initialisation du résultat global de la validation. */ if ( erreurs.isEmpty() ) { resultat = "Succès de l'envoi du fichier."; } else { resultat = "Échec de l'envoi du fichier."; } return fichier; } /* * Valide la description saisie. */ private void validationDescription( String description ) throws Exception { if ( description != null ) { if ( description.length() < 15 ) { throw new Exception( "La phrase de description du fichier doit contenir au moins 15 caractères." ); } } else { throw new Exception( "Merci d'entrer une phrase de description du fichier." ); } } /* * Valide le fichier envoyé. */ private void validationFichier( String nomFichier, InputStream contenuFichier ) throws Exception { if ( nomFichier == null || contenuFichier == null ) { throw new Exception( "Merci de sélectionner un fichier à envoyer." ); } } /* * Ajoute un message correspondant au champ spécifié à la map des erreurs. */ private void setErreur( String champ, String message ) { erreurs.put( champ, message ); } /* * Méthode utilitaire qui retourne null si un champ est vide, et son contenu * sinon. */ private static String getValeurChamp( HttpServletRequest request, String nomChamp ) { String valeur = request.getParameter( nomChamp ); if ( valeur == null || valeur.trim().length() == 0 ) { return null; } else { return valeur; } } /* * Méthode utilitaire qui a pour unique but d'analyser l'en-tête * "content-disposition", et de vérifier si le paramètre "filename" y est * présent. Si oui, alors le champ traité est de type File et la méthode * retourne son nom, sinon il s'agit d'un champ de formulaire classique et * la méthode retourne null. */ private static String getNomFichier( Part part ) { /* Boucle sur chacun des paramètres de l'en-tête "content-disposition". */ for ( String contentDisposition : part.getHeader( "content-disposition" ).split( ";" ) ) { /* Recherche de l'éventuelle présence du paramètre "filename". */ if ( contentDisposition.trim().startsWith( "filename" ) ) { /* * Si "filename" est présent, alors renvoi de sa valeur, * c'est-à-dire du nom de fichier sans guillemets. */ return contentDisposition.substring( contentDisposition.indexOf( '=' ) + 1 ).trim().replace( "\"", "" ); } } /* Et pour terminer, si rien n'a été trouvé... */ return null; } /* * Méthode utilitaire qui a pour but d'écrire le fichier passé en paramètre * sur le disque, dans le répertoire donné et avec le nom donné. */ private void ecrireFichier( InputStream contenu, String nomFichier, String chemin ) throws Exception { /* Prépare les flux. */ BufferedInputStream entree = null; BufferedOutputStream sortie = null; try { /* Ouvre les flux. */ entree = new BufferedInputStream( contenu, TAILLE_TAMPON ); sortie = new BufferedOutputStream( new FileOutputStream( new File( chemin + nomFichier ) ), TAILLE_TAMPON ); /* * Lit le fichier reçu et écrit son contenu dans un fichier sur le * disque. */ byte[] tampon = new byte[TAILLE_TAMPON]; int longueur = 0; while ( ( longueur = entree.read( tampon ) ) > 0 ) { sortie.write( tampon, 0, longueur ); } } finally { try { sortie.close(); } catch ( IOException ignore ) { } try { entree.close(); } catch ( IOException ignore ) { } } }}```## 5: La servlet pour afficher le résultat à la vueCertains serveurs d'applications ne prennent pas en charge le recupération données de formulaire via request.getParameter() quand le formulaire est de type multipart/form-data. Il faudra alors récuperer les données avec le contenu binaire de l'élément Part correspondant au champ de type texte, et le reconstruire sous forme d'un String.```import com.sdzee.beans.Fichier;import com.sdzee.forms.UploadForm;public class Upload extends HttpServlet { public static final String CHEMIN = "chemin"; public static final String ATT_FICHIER = "fichier"; public static final String ATT_FORM = "form"; public static final String VUE = "/WEB-INF/upload.jsp"; public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { /* Affichage de la page d'upload */ this.getServletContext().getRequestDispatcher( VUE ).forward( request, response ); } public void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { /* * Lecture du paramètre 'chemin' passé à la servlet via la déclaration * dans le web.xml */ String chemin = this.getServletConfig().getInitParameter( CHEMIN ); /* Préparation de l'objet formulaire */ UploadForm form = new UploadForm(); /* Traitement de la requête et récupération du bean en résultant */ Fichier fichier = form.enregistrerFichier( request, chemin ); /* Stockage du formulaire et du bean dans l'objet request */ request.setAttribute( ATT_FORM, form ); request.setAttribute( ATT_FICHIER, fichier ); this.getServletContext().getRequestDispatcher( VUE ).forward( request, response ); }}```
# Infos- Le téléchargement de fichiers peut être géré simplement via une servlet dédiée, chargée de faire la correspondance entre le pattern d'URL choisi côté public et l'emplacement du fichier physique côté serveur.- Elle se charge de transmettre les données lues sur le serveur au navigateur du client via la réponse HTTP.- Pour permettre un tel transfert, il faut réinitialiser une réponse HTTP, puis définir ses en-têtes Content-Type, Content-Length et Content-Disposition.- Pour envoyer les données au client, il suffit de lire le fichier comme n'importe quel fichier local et de recopier son contenu dans le flux de sortie accessible via la méthode response.getOutputStream().- GlassFish est livré avec une fonctionnalité qui permet de "monter" un répertoire externe au conteneur dans une application web : les Alternate Document Roots.## Comment proposer la reprise d'un téléchargement ?Depuis notre servlet, il faudrait manipuler au minimum trois nouveaux en-têtes de la réponse HTTP afin d'activer cette fonctionnalité.- Accept-Ranges : cet en-tête de réponse, lorsqu'il contient la valeur "bytes", informe le client que le serveur supporte les requêtes demandant une plage définie de données. Avec cette information, le client peut alors demander une section particulière d'un fichier à travers l'en-tête de requête Range.- ETag : cet en-tête de réponse doit contenir une valeur unique permettant d'identifier le fichier concerné. Il est possible d'utiliser le système de votre choix, il n'y a aucune contrainte : tout ce qui importe, c'est que chacune des valeurs associées à un fichier soit unique. Certains serveurs utilisent par exemple l'algorithme MD5 pour générer un hash basé sur le contenu du fichier, d'autres utilisent une combinaison d'informations à propos du fichier (son nom, sa taille, sa date de modification, etc.), d'autres génèrent un hash de cette combinaison... Avec cette information, le client peut alors renvoyer l'identifiant obtenu au serveur à travers l'en-tête de requête If-Match ou If-Range, et le serveur peut alors déterminer de quel fichier il est question.- Last-Modified : cet en-tête de réponse doit contenir un timestamp, qui représente la date de la dernière modification du fichier côté serveur. Avec cette information, le client peut alors renvoyer le timestamp obtenu au serveur à travers l'en-tête de requête If-Unmodified-Since, ou bien là encore If-Range.note importante : un timestamp Java est précis à la milliseconde près, alors que le timestamp attendu dans l'en-tête Last-Modified n'est précis qu'à la seconde près. Afin de combler cet écart d'incertitude, il est donc nécessaire d'ajouter une seconde à la date de modification retournée par le client dans sa requête, avant de la traiter.## Réaliser des statistiquesSi vous envisagez dans une application de réaliser des statistiques sur les téléchargements : combien de téléchargements par fichier, combien de fichiers par utilisateur, combien de téléchargements simultanés, la proportion d'images téléchargées par rapport aux autres types de fichiers, etc.. Le composant idéal pour s'occuper de ce type de traitements, c'est le filtre ! Il suffit en effet d'en appliquer un sur le pattern/fichiers/* pour que celui-ci ait accès à toutes les demandes de téléchargement effectuées par les clients. Le nombre et la complexité des traitements qu'il devra réaliser dépendront bien évidemment des informations que vous souhaitez collecter. En ce qui concerne la manière, tout va passer par la méthode doFilter() du filtre en question.# Mise en place## La vue```<form action="<c:url value="/upload" />" method="post" enctype="multipart/form-data"> <fieldset> <legend>Envoi de fichier</legend> <label for="description">Description du fichier</label> <input type="text" id="description" name="description" value="<c:out value="${fichier.description}"/>" /> <span class="erreur">${form.erreurs['description']}</span> <br /> <label for="fichier">Emplacement du fichier <span class="requis">*</span></label> <input type="file" id="fichier" name="fichier" value="<c:out value="${fichier.nom}"/>" /> <span class="erreur">${form.erreurs['fichier']}</span> <br /> <input type="submit" value="Envoyer" class="sansLabel" /> <br /> <p class="${empty form.erreurs ? 'succes' : 'erreur'}">${form.resultat}</p> </fieldset></form>```## Le fichier web.xml```<servlet> <servlet-name>Download</servlet-name> <servlet-class>com.tel.servlets.Download</servlet-class> <init-param> <param-name>chemin</param-name> <param-value>/Users/mika/fichiers</param-value> </init-param></servlet><servlet-mapping> <servlet-name>Download</servlet-name> <url-pattern>/fichiers/*</url-pattern></servlet-mapping>```# La servlet```public class Download extends HttpServlet { public static final int TAILLE_TAMPON = 10240; // 10ko public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { /* * Lecture du paramètre 'chemin' passé à la servlet via la déclaration * dans le web.xml */ String chemin = this.getServletConfig().getInitParameter( "chemin" ); /* * Récupération du chemin du fichier demandé au sein de l'URL de la * requète */ String fichierRequis = request.getPathInfo(); /* Vérifie qu'un fichier a bien été fourni */ if ( fichierRequis == null ) { /* * Si non, alors on envoie une erreur 404, qui signifie que la * ressource demandée n'existe pas */ response.sendError( HttpServletResponse.SC_NOT_FOUND ); return; } /* * Décode le nom de fichier récupéré, susceptible de contenir des * espaces et autres caractères spéciaux, et prépare l'objet File */ fichierRequis = URLDecoder.decode( fichierRequis, "UTF-8" ); File fichier = new File( chemin, fichierRequis ); /* Vérifie que le fichier existe bien */ if ( !fichier.exists() ) { /* * Si non, alors on envoie une erreur 404, qui signifie que la * ressource demandée n'existe pas */ response.sendError( HttpServletResponse.SC_NOT_FOUND ); return; } /* Récupère le type du fichier */ String type = getServletContext().getMimeType( fichier.getName() ); /* * Si le type de fichier est inconnu, alors on initialise un type par * défaut */ if ( type == null ) { type = "application/octet-stream"; } /* Initialise la réponse HTTP */ response.reset(); response.setBufferSize( TAILLE_TAMPON ); response.setContentType( type ); response.setHeader( "Content-Length", String.valueOf( fichier.length() ) ); response.setHeader( "Content-Disposition", "attachment; filename=\"" + fichier.getName() + "\"" ); /* Prépare les flux */ BufferedInputStream entree = null; BufferedOutputStream sortie = null; try { /* Ouvre les flux */ entree = new BufferedInputStream( new FileInputStream( fichier ), TAILLE_TAMPON ); sortie = new BufferedOutputStream( response.getOutputStream(), TAILLE_TAMPON ); /* Lit le fichier et écrit son contenu dans la réponse HTTP */ byte[] tampon = new byte[TAILLE_TAMPON]; int longueur; while ( ( longueur = entree.read( tampon ) ) > 0 ) { sortie.write( tampon, 0, longueur ); } } finally { sortie.close(); entree.close(); } }}```
以上是关于markdown Gestion de fichiers的主要内容,如果未能解决你的问题,请参考以下文章
markdown Definicion de inversion de control