Introducción
Para examinar y seleccionar un archivo para cargar, necesita un <input type="file">
campo HTML en el formulario. Como se indica en la especificación HTML , debe usar el POST
método y el enctype
atributo del formulario debe establecerse en "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>
Después de enviar dicho formulario, los datos del formulario multiparte binario están disponibles en el cuerpo de la solicitud en un formato diferente al que enctype
no está configurado.
Antes de Servlet 3.0, la API de Servlet no era compatible de forma nativa multipart/form-data
. Solo admite el tipo de formulario predeterminado de application/x-www-form-urlencoded
. El request.getParameter()
y consortes habría todos regresan null
al utilizar los datos del formulario de varias partes. Aquí es donde el conocido Apache Commons FileUpload entró en escena la .
¡No lo analices manualmente!
En teoría, puede analizar el cuerpo de la solicitud en función de ServletRequest#getInputStream()
. Sin embargo, este es un trabajo preciso y tedioso que requiere un conocimiento preciso de RFC2388 . No debe intentar hacer esto por su cuenta o copiar un código sin biblioteca propio que se encuentre en otra parte de Internet. Muchas fuentes en línea han fallado mucho en esto, como roseindia.net. Consulte también carga de archivo pdf . Debería utilizar una biblioteca real que millones de usuarios usan (¡y se prueban implícitamente!) Durante años. Dicha biblioteca ha demostrado su robustez.
Cuando ya esté en Servlet 3.0 o más reciente, use API nativa
Si está utilizando al menos Servlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3, etc.), puede usar la API estándar provista HttpServletRequest#getPart()
para recopilar los elementos de datos de formularios individuales (la mayoría de las implementaciones de Servlet 3.0 en realidad usan Apache Commons FileUpload debajo de las cubiertas para esto!). Además, los campos de formulario normales están disponibles de getParameter()
la manera habitual.
Primero anote su servlet con el @MultipartConfig
fin de permitirle reconocer y admitir multipart/form-data
solicitudes y así ponerse getPart()
a trabajar:
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
// ...
}
Luego, implemente doPost()
lo siguiente:
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)
}
Tenga en cuenta el Path#getFileName()
. Esta es una solución de MSIE para obtener el nombre del archivo. Este navegador envía incorrectamente la ruta completa del archivo a lo largo del nombre en lugar de solo el nombre del archivo.
En caso de que tenga una <input type="file" name="file" multiple="true" />
carga de varios archivos, recójalos de la siguiente manera (desafortunadamente no existe tal método 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)
}
}
Cuando aún no esté en Servlet 3.1, obtenga manualmente el nombre del archivo enviado
Tenga en cuenta que Part#getSubmittedFileName()
se introdujo en Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4, etc.). Si aún no está en Servlet 3.1, necesita un método de utilidad adicional para obtener el nombre del archivo enviado.
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);
Tenga en cuenta la corrección de MSIE para obtener el nombre del archivo. Este navegador envía incorrectamente la ruta completa del archivo a lo largo del nombre en lugar de solo el nombre del archivo.
Cuando aún no esté en Servlet 3.0, use Apache Commons FileUpload
Si aún no está en Servlet 3.0 (¿no es hora de actualizar?), La práctica común es utilizar Apache Commons FileUpload para analizar las solicitudes de datos de formularios de varias partes. Tiene una excelente Guía del usuario y preguntas frecuentes ( revise cuidadosamente ambas). También está el O'Reilly (" cos ") MultipartRequest
, pero tiene algunos errores (menores) y ya no se mantiene activamente durante años. No recomendaría usarlo. Apache Commons FileUpload todavía se mantiene activamente y actualmente es muy maduro.
Para utilizar Apache Commons FileUpload, debe tener al menos los siguientes archivos en su aplicación web /WEB-INF/lib
:
Su intento inicial falló muy probablemente porque olvidó los IO comunes.
Aquí hay un ejemplo inicial de cómo puede ser doPost()
tu UploadServlet
cuando usas 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);
}
// ...
}
Es muy importante que usted no llama getParameter()
, getParameterMap()
, getParameterValues()
, getInputStream()
, getReader()
, etc en la misma solicitud de antemano. De lo contrario, el contenedor de servlet leerá y analizará el cuerpo de la solicitud y, por lo tanto, Apache Commons FileUpload obtendrá un cuerpo de solicitud vacío. Consulte también ao ServletFileUpload # parseRequest (request) devuelve una lista vacía .
Tenga en cuenta el FilenameUtils#getName()
. Esta es una solución de MSIE para obtener el nombre del archivo. Este navegador envía incorrectamente la ruta completa del archivo a lo largo del nombre en lugar de solo el nombre del archivo.
Alternativamente, también puede envolver todo esto de manera Filter
que lo analice todo automáticamente y vuelva a colocar el material en el mapa de parámetros de la solicitud para que pueda continuar utilizando request.getParameter()
la forma habitual y recuperar el archivo cargado request.getAttribute()
. Puedes encontrar un ejemplo en este artículo de blog .
Solución alternativa para el error GlassFish3 de getParameter()
volver aúnnull
Tenga en cuenta que las versiones de Glassfish anteriores a 3.1.2 tenían un error en el que getParameter()
aún se devuelve null
. Si está apuntando a dicho contenedor y no puede actualizarlo, entonces necesita extraer el valor getPart()
con la ayuda de este método de utilidad:
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">
Guardando el archivo cargado (¡no use getRealPath()
ni part.write()
!)
Diríjase a las siguientes respuestas para obtener detalles sobre cómo guardar correctamente el obtenido InputStream
(la fileContent
variable como se muestra en los fragmentos de código anteriores) en el disco o la base de datos:
Sirviendo archivos cargados
Diríjase a las siguientes respuestas para obtener detalles sobre cómo servir correctamente el archivo guardado desde el disco o la base de datos al cliente:
Ajaxificando el formulario
Diríjase a las siguientes respuestas sobre cómo cargar usando Ajax (y jQuery). ¡Tenga en cuenta que el código de servlet para recopilar los datos del formulario no necesita ser cambiado para esto! Solo se puede cambiar la forma en que responde, pero esto es bastante trivial (es decir, en lugar de reenviar a JSP, simplemente imprima JSON o XML o incluso texto sin formato dependiendo de lo que el script responsable de la llamada Ajax esté esperando).
Espero que todo esto ayude :)