introduzione
Per sfogliare e selezionare un file per il caricamento è necessario un <input type="file">
campo HTML nel modulo. Come indicato nella specifica HTML, è necessario utilizzare il POST
metodo e l' enctype
attributo del modulo deve essere impostato su "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>
Dopo aver inviato tale modulo, i dati binari del modulo multipart sono disponibili nel corpo della richiesta in un formato diverso rispetto a quando enctype
non è impostato.
Prima di Servlet 3.0, l'API Servlet non supportava in modo nativo multipart/form-data
. Supporta solo il tipo di modulo predefinito di application/x-www-form-urlencoded
. I request.getParameter()
consorti andrebbero tutti restituiti null
quando si utilizzano dati di moduli multipart. È qui che è apparso il noto FileUpload di Apache Commons .
Non analizzarlo manualmente!
In teoria puoi analizzare te stesso il corpo della richiesta in base ServletRequest#getInputStream()
. Tuttavia, questo è un lavoro preciso e noioso che richiede una conoscenza precisa di RFC2388 . Non dovresti provare a farlo da solo o copiare un codice privo di librerie di casa trovato altrove su Internet. Molte fonti online hanno fallito duramente in questo, come roseindia.net. Vedi anche il caricamento di file pdf . Dovresti piuttosto usare una vera libreria che viene utilizzata (e implicitamente testata!) Da milioni di utenti per anni. Tale biblioteca ha dimostrato la sua solidità.
Quando sei già su Servlet 3.0 o versioni successive, utilizza l'API nativa
Se stai utilizzando almeno Servlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3, ecc.), Puoi semplicemente utilizzare l'API standard fornita HttpServletRequest#getPart()
per raccogliere i singoli elementi di dati del modulo multipart (la maggior parte delle implementazioni Servlet 3.0 utilizzano Apache Per questo, FileUpload sotto le coperte!). Inoltre, i normali campi modulo sono disponibili getParameter()
nel solito modo.
Prima annota il tuo servlet con lo @MultipartConfig
scopo di farlo riconoscere e supportare le multipart/form-data
richieste e quindi mettersi getPart()
al lavoro:
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
// ...
}
Quindi, implementa doPost()
come segue:
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)
}
Nota il Path#getFileName()
. Questa è una correzione MSIE per ottenere il nome del file. Questo browser invia erroneamente il percorso completo del file lungo il nome anziché solo il nome del file.
Nel caso in cui tu abbia un <input type="file" name="file" multiple="true" />
per il caricamento di più file, raccoglili come di seguito (sfortunatamente non esiste un metodo come 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)
}
}
Quando non sei ancora su Servlet 3.1, ottieni manualmente il nome del file inviato
Si noti che è Part#getSubmittedFileName()
stato introdotto in Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4, ecc.). Se non si è ancora su Servlet 3.1, è necessario un metodo di utilità aggiuntivo per ottenere il nome del file inviato.
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);
Notare la correzione MSIE per ottenere il nome del file. Questo browser invia erroneamente il percorso completo del file lungo il nome anziché solo il nome del file.
Quando non sei ancora su Servlet 3.0, usa Apache Commons FileUpload
Se non sei ancora su Servlet 3.0 (non è il momento di eseguire l'aggiornamento?), La pratica comune è quella di utilizzare FileUpload di Apache Commons per analizzare le richieste di dati in moduli multipart. Ha un'eccellente guida per l'utente e domande frequenti (esaminare attentamente entrambi). C'è anche O'Reilly (" cos ") MultipartRequest
, ma ha alcuni bug (minori) e non viene più mantenuto attivamente per anni. Non consiglierei di usarlo. Apache Commons FileUpload è ancora attivamente gestito e attualmente molto maturo.
Per utilizzare FileUpload di Apache Commons, devi avere almeno i seguenti file nel tuo webapp /WEB-INF/lib
:
Il tuo tentativo iniziale è fallito molto probabilmente perché hai dimenticato l'IO comune.
Ecco un esempio iniziale di come potrebbe apparire il doPost()
tuo UploadServlet
quando usi 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);
}
// ...
}
E 'molto importante che non si chiama getParameter()
, getParameterMap()
, getParameterValues()
, getInputStream()
, getReader()
, ecc sulla stessa richiesta in anticipo. Altrimenti il contenitore servlet leggerà e analizzerà il corpo della richiesta e quindi Apache Commons FileUpload otterrà un corpo della richiesta vuoto. Vedi anche ao ServletFileUpload # parseRequest (richiesta) restituisce un elenco vuoto .
Nota il FilenameUtils#getName()
. Questa è una correzione MSIE per ottenere il nome del file. Questo browser invia erroneamente il percorso completo del file lungo il nome anziché solo il nome del file.
In alternativa puoi anche racchiudere tutto questo in un oggetto Filter
che analizza tutto automaticamente e rimettere il materiale nella parametermap della richiesta in modo da poter continuare a utilizzare request.getParameter()
il solito modo e recuperare il file caricato da request.getAttribute()
. Puoi trovare un esempio in questo articolo del blog .
Soluzione per il bug di GlassFish3 di getParameter()
restituzione ancoranull
Si noti che le versioni di Glassfish precedenti alla 3.1.2 avevano un bug in cui i getParameter()
ritorni ancora null
. Se si sta prendendo di mira un tale contenitore e non è possibile aggiornarlo, è necessario estrarre il valore getPart()
con l'aiuto di questo metodo di utilità:
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">
Salvataggio del file caricato (non usare getRealPath()
né part.write()
!)
Vai alle seguenti risposte per i dettagli su come salvare correttamente il ottenuto InputStream
(la fileContent
variabile come mostrato nei frammenti di codice sopra) su disco o database:
Elaborazione del file caricato
Vai alle seguenti risposte per i dettagli su come pubblicare correttamente il file salvato dal disco o dal database al client:
Ajaxifying the form
Vai alle seguenti risposte su come caricare utilizzando Ajax (e jQuery). Si noti che non è necessario modificare il codice servlet per raccogliere i dati del modulo! Solo il modo in cui rispondi può essere cambiato, ma questo è piuttosto banale (cioè invece di inoltrare a JSP, basta stampare un po 'di JSON o XML o persino un testo semplice a seconda di ciò che lo script responsabile della chiamata Ajax si aspetta).
Spero che tutto ciò aiuti :)