introduction
Pour parcourir et sélectionner un fichier à télécharger, vous avez besoin d'un <input type="file">
champ HTML dans le formulaire. Comme indiqué dans la spécification HTML, vous devez utiliser la POST
méthode et l' enctype
attribut du formulaire doit être défini sur "multipart/form-data"
.
<form action="upload" method="post" enctype="multipart/form-data">
<input type="text" name="description" />
<input type="file" name="file" />
<input type="submit" />
</form>
Après avoir soumis un tel formulaire, les données du formulaire binaire en plusieurs parties sont disponibles dans le corps de la demande dans un format différent de celui qui enctype
n'est pas défini.
Avant Servlet 3.0, l'API Servlet ne supportait pas nativement multipart/form-data
. Il prend uniquement en charge le type de formulaire par défaut de application/x-www-form-urlencoded
. Les request.getParameter()
consorts et retourneraient tous null
lors de l'utilisation de données de formulaire en plusieurs parties. C'est là que le fameux Apache Commons FileUpload est entré en scène.
Ne pas analyser manuellement!
Vous pouvez en théorie analyser vous-même le corps de la demande ServletRequest#getInputStream()
. Cependant, c'est un travail précis et fastidieux qui nécessite une connaissance précise de la RFC2388 . Vous ne devriez pas essayer de le faire par vous-même ou copopastez du code local sans bibliothèque trouvé ailleurs sur Internet. De nombreuses sources en ligne ont échoué dans ce domaine, comme roseindia.net. Voir aussi téléchargement du fichier pdf . Vous devriez plutôt utiliser une vraie bibliothèque qui est utilisée (et implicitement testée!) Par des millions d'utilisateurs pendant des années. Une telle bibliothèque a prouvé sa robustesse.
Lorsque vous êtes déjà sur Servlet 3.0 ou plus récent, utilisez l'API native
Si vous utilisez au moins Servlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3, etc.), vous pouvez simplement utiliser l'API standard fournie HttpServletRequest#getPart()
pour collecter les éléments de données de formulaire multiparties individuels (la plupart des implémentations Servlet 3.0 utilisent réellement Apache Commons FileUpload sous les couvertures pour cela!). De plus, les champs de formulaire normaux sont disponibles de getParameter()
la manière habituelle.
Annotez d'abord votre servlet avec @MultipartConfig
afin de lui permettre de reconnaître et d'accompagner les multipart/form-data
requêtes et ainsi getPart()
de travailler:
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
// ...
}
Ensuite, implémentez son doPost()
comme suit:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String description = request.getParameter("description"); // Retrieves <input type="text" name="description">
Part filePart = request.getPart("file"); // Retrieves <input type="file" name="file">
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
InputStream fileContent = filePart.getInputStream();
// ... (do your job here)
}
Notez le Path#getFileName()
. Il s'agit d'un correctif MSIE quant à l'obtention du nom de fichier. Ce navigateur envoie de façon incorrecte le chemin d'accès complet au fichier au lieu du seul nom de fichier.
Dans le cas où vous avez un <input type="file" name="file" multiple="true" />
téléchargement multi-fichiers, collectez-les comme ci-dessous (malheureusement, il n'existe pas de méthode telle que request.getParts("file")
):
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// ...
List<Part> fileParts = request.getParts().stream().filter(part -> "file".equals(part.getName()) && part.getSize() > 0).collect(Collectors.toList()); // Retrieves <input type="file" name="file" multiple="true">
for (Part filePart : fileParts) {
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
InputStream fileContent = filePart.getInputStream();
// ... (do your job here)
}
}
Lorsque vous n'êtes pas encore sur Servlet 3.1, obtenez manuellement le nom du fichier soumis
Notez que cela a Part#getSubmittedFileName()
été introduit dans Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4, etc.). Si vous n'êtes pas encore sur Servlet 3.1, vous avez besoin d'une méthode utilitaire supplémentaire pour obtenir le nom du fichier soumis.
private static String getSubmittedFileName(Part part) {
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
return fileName.substring(fileName.lastIndexOf('/') + 1).substring(fileName.lastIndexOf('\\') + 1); // MSIE fix.
}
}
return null;
}
String fileName = getSubmittedFileName(filePart);
Notez le correctif MSIE quant à l'obtention du nom de fichier. Ce navigateur envoie de façon incorrecte le chemin d'accès complet au fichier au lieu du seul nom de fichier.
Lorsque vous n'êtes pas encore sur Servlet 3.0, utilisez Apache Commons FileUpload
Si vous n'êtes pas encore sur Servlet 3.0 (n'est-il pas temps de mettre à niveau?), La pratique courante consiste à utiliser Apache Commons FileUpload pour analyser les demandes de données de formulaire en plusieurs parties. Il a un excellent guide de l'utilisateur et une FAQ (parcourez attentivement les deux). Il y a aussi le O'Reilly (" cos ") MultipartRequest
, mais il a quelques bugs (mineurs) et n'est plus activement maintenu depuis des années. Je ne recommanderais pas de l'utiliser. Apache Commons FileUpload est toujours activement maintenu et actuellement très mature.
Pour utiliser Apache Commons FileUpload, vous devez avoir au moins les fichiers suivants dans votre application Web /WEB-INF/lib
:
Votre tentative initiale a échoué très probablement parce que vous avez oublié l'IO commun.
Voici un exemple de lancement à quoi pourrait ressembler doPost()
votre UploadServlet
lorsque vous utilisez Apache Commons FileUpload:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
// Process regular form field (input type="text|radio|checkbox|etc", select, etc).
String fieldName = item.getFieldName();
String fieldValue = item.getString();
// ... (do your job here)
} else {
// Process form file field (input type="file").
String fieldName = item.getFieldName();
String fileName = FilenameUtils.getName(item.getName());
InputStream fileContent = item.getInputStream();
// ... (do your job here)
}
}
} catch (FileUploadException e) {
throw new ServletException("Cannot parse multipart request.", e);
}
// ...
}
Il est très important que vous ne l' appelez getParameter()
, getParameterMap()
, getParameterValues()
, getInputStream()
, getReader()
, etc sur la même demande au préalable. Sinon, le conteneur de servlet lira et analysera le corps de la requête et donc Apache Commons FileUpload obtiendra un corps de requête vide. Voir aussi ao ServletFileUpload # parseRequest (request) renvoie une liste vide .
Notez le FilenameUtils#getName()
. Il s'agit d'un correctif MSIE quant à l'obtention du nom de fichier. Ce navigateur envoie de façon incorrecte le chemin d'accès complet au fichier au lieu du seul nom de fichier.
Alternativement, vous pouvez également envelopper tout cela dans un Filter
qui les analyse tous automatiquement et remettre les choses dans le parametermap de la demande afin que vous puissiez continuer à utiliser request.getParameter()
la manière habituelle et récupérer le fichier téléchargé par request.getAttribute()
. Vous pouvez trouver un exemple dans cet article de blog .
Solution de contournement pour le bug GlassFish3 de getParameter()
retournull
Notez que les versions de Glassfish antérieures à 3.1.2 avaient un bug dans lequel le getParameter()
retourne toujours null
. Si vous ciblez un tel conteneur et ne pouvez pas le mettre à niveau, vous devez extraire la valeur getPart()
à l'aide de cette méthode utilitaire:
private static String getValue(Part part) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(part.getInputStream(), "UTF-8"));
StringBuilder value = new StringBuilder();
char[] buffer = new char[1024];
for (int length = 0; (length = reader.read(buffer)) > 0;) {
value.append(buffer, 0, length);
}
return value.toString();
}
String description = getValue(request.getPart("description")); // Retrieves <input type="text" name="description">
Enregistrement du fichier téléchargé (ne pas utiliser getRealPath()
ni part.write()
!)
Consultez les réponses suivantes pour plus de détails sur l'enregistrement correct de la copie obtenue InputStream
(la fileContent
variable indiquée dans les extraits de code ci-dessus) sur le disque ou la base de données:
Servir le fichier téléchargé
Consultez les réponses suivantes pour plus de détails sur la manière de bien servir le fichier enregistré du disque ou de la base de données au client:
Ajaxification du formulaire
Consultez les réponses suivantes pour télécharger à l'aide d'Ajax (et de jQuery). Notez que le code de servlet pour collecter les données du formulaire n'a pas besoin d'être modifié pour cela! Seule la façon dont vous répondez peut être modifiée, mais c'est plutôt trivial (c'est-à-dire qu'au lieu de transférer vers JSP, imprimez simplement du JSON ou du XML ou même du texte brut en fonction de ce que le script responsable de l'appel Ajax attend).
J'espère que tout cela vous aidera :)