Servlet para carga de archivo

(Junio de 2007)

Introducción

Un problema típico es conseguir que nuestro servidor almacene en el sistema de ficheros o en base de datos un archivo del cliente. El proceso es sencillo, el cliente indica en el navegador (por medio de un parámetro de request) el archivo y a continuación invoca al servlet. El servlet recupera en un stream el contenido y lo vuelca en un archivo o base de datos.

Para el servlet vamos a utilizar una librería del proyecto Jakarta: Jakarta Commons FileUpload package: commons-fileupload-1.2.jar. Se consigue en la página de commons/fileupload del proyecto Jakarta. Además esta librería requiere Jakarta Commons IO librarie: commons-io-1.3.1.jar. Se consigue en la página de commons/io del proyecto Jakarta.



El formulario

Un formulario de ejemplo que invoca a nuestro servlet:


<form action="http://localhost:8060/ejerWeb_ServletUpload/servBlob" enctype="multipart/form-data" method="post">
    NIF:   <input type="text" name="NIF">
    <br />
    Curriculum:   <input type="file" name="cv" size="70" accept="text/plain;image/jpeg">
    <br /><br />
    <input type="submit" name="enviar" value="Enviar">
</form>
    

El formulario debe incluir:

Es necesario especificar en el elemento <input type="file"> el atributo accept. Esta recomendación se hace aunque haya navegadores que transfieren el archivo a pesar de no pertenecer a uno de los tipos especificados en accept, es decir, accept no filtra. Mantenemos la recomendación de usar este atributo porque evita errores en algunas versiones de navegador, en concreto, el no procesar el elemento de tipo "file".



El servlet: init

Empezamos usando el ServletContext para construir el path del directorio donde guardamos el archivo:


public class ServletBlob extends HttpServlet {
	String directorioArchivos;		// Donde guardaré archivos

	/************************************************************************************
	 * Obtengo el directorio donde guardaré los archivos
	 ************************************************************************************/
	public void init(ServletConfig config) throws ServletException {
		ServletContext sc = config.getServletContext(); // Conseguimos un contexto

		directorioArchivos = sc.getRealPath("/") + "archivos";  // El directorio donde estarán los archivos
	}
	...
   


El servlet: manejo de FileItem

Con un formulario multipart tratado con la librería de Jakarta todos los parámetros recibidos son FileItem (sean 'simples' o del tipo 'file'). Con servletFileUpload.parseRequest(request) recibimos una lista de FileItem:


		response.setContentType("text/html");
		PrintWriter out = response.getWriter();

		try {

			//// Inicio de pagina
			out.println("<html>");
			out.println("<head><title</title></head>");
			out.println("<body bgcolor=\"#FFFF9D\"><FONT color=\"#000080\" FACE=\"Arial,Helvetica,Times\" SIZE=2>"+
			"<CENTER><H3>Carga de Blob</H3></CENTER><HR>");

			out.println( "<p>Directorio de archivos: " + directorioArchivos + "</p>");

			//// Si la request es del tipo multipart ...
			if (ServletFileUpload.isMultipartContent(request)){

				//// fileItemsList contendrá una lista de items de archivo que  son instancias de FileItem
				//// Un item de archivo puede contener un archivo para upload o un campo del formulario
				//// con la estructura simple nombre-valor (ejemplo: <input name="text_field" type="text" />)
				ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory());
				List fileItemsList = servletFileUpload.parseRequest(request);
	...
   

Por defecto la instancia de ServletFileUpload tiene los siguientes valores:

Podemos cambiar las opciones mediante setSizeThreshold() y setRespository() de la clase DiskFileItemFactory y el método setSizeMax() de la clase ServletFileUpload, por ejemplo:


						DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
						diskFileItemFactory.setSizeThreshold(40960); // bytes

						File repositoryPath = new File("/temp");
						diskFileItemFactory.setRepository(repositoryPath);

						ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
						servletFileUpload.setSizeMax(81920); // bytes
   

En nuestro ejemplo vamos a trabajar con generalidad: programaremos como si quisieramos leer todos los campos sean 'simples' o 'file'. Por ello iteramos sobre todos los FileItem que recibimos. Los parámetros simples los diferenciaremos de los parámetros 'file' por medio del método isFormField(). El método getPost():


				//// Itero para obtener todos los FileItem
				Iterator it = fileItemsList.iterator();
				while (it.hasNext()){
					FileItem fileItem = (FileItem)it.next();

					//// El FileItem es un campo simple, del tipo nombre-valor
					if (fileItem.isFormField()){
						String nombre = fileItem.getFieldName();
						String valor = fileItem.getString();
						out.println( "<p>Parámetro:" + nombre + "   Valor:" + valor + "</p>");
					}
					//// El FileItem contiene un archivo para upload
					else{

						//// Atributo "name" del elemento input type="file"
						String nombreCampo = fileItem.getFieldName();

						//// Tamaño de archivo en bytes
						long tamanioArchivo = fileItem.getSize();

						//// Nombre del archivo en el cliente. Algunos navegadores (por ej. IE 6)
						//// incluyen el path completo, lo que puede implicar separar path
						//// de nombre.
						String nombreArchivo = fileItem.getName();

						//// Content type (tipo MIME) del archivo.
						//// Esta información la proporciona el navegador del cliente.
						//// Algunos ejemplos: .jpg = image/jpeg, .txt = text/plain
						String contentType = fileItem.getContentType();

						//// Obtengo caracteristicas de campo y archivo
						out.println( "<p>--> Name:" + nombreCampo + "</p>");
						out.println( "<p>--> Tamaño archivo:" + tamanioArchivo + "</p>");
						out.println( "<p>--> Nombre archivo del cliente:" + nombreArchivo + "</p>");
						out.println( "<p>--> contentType:" + contentType + "</p>");

						//// Obtengo extensión del archivo de cliente
						String extension = nombreArchivo.substring(nombreArchivo.indexOf("."));
						out.println( "<p>--> Extensión del archivo:" + extension + "</p>");

						//// Guardo archivo del cliente en servidor, con un nombre 'fijo' y la
						//// extensión que me manda el cliente
						File archivo = new File(directorioArchivos + "/cv" + extension);
						fileItem.write(archivo);
						if ( archivo.exists() )
							out.println( "<p>--> GUARDADO " + archivo.getAbsolutePath() + "</p>");
						else
							out.println( "<p>--> FALLO AL GUARDAR. NO EXISTE " + archivo.getAbsolutePath() + "</p>");

					}	//// FIN: es un archivo para upload
				}	//// FIN: iteración de FileItems
			}	//// FIN: la request es del tipo multipart
		}
		catch (Exception e) {
			e.printStackTrace(out);
			e.printStackTrace();
		}
		finPagina(out);
	}
	

El nombre del archivo (para el cliente) se consigue por medio de fileItem.getName(). Este método devuelve en algunos navegadores (por ejmplo, IE 6) la ruta completa que el archivo tenía en el puesto cliente. A partir de aquí se obtiene la extensión del archivo.

Creamos un File, a partir de tres datos que ya tenemos: el directorio (directorioArchivos), el nombre del archivo ("cv") y la extensión. El punto esencial de la carga es ordenar al FileItem que escriba su contenido en el archivo, por medio de fileItem.write(archivo). Terminamos comprobando que el archivo existe usando el método exists().

¿Qué ocurre cuando pulso "Enviar" en el formulario, sin haber indicado archivo.? Ocurre lo mismo que si el archivo estuviera vacio: su tamaño es cero. Ya tiene una pequeña mejora que hacer con este ejemplo.



El servlet: tratar el contenido del campo 'file'

Si no se quiere sólo guardar el archivo, sino que además se quiere procesar, podemos usar:



Consideraciones de seguridad

En PHP y JSP los usuarios maliciosos pueden dar un nombre de archivo que modifique archivos del servidor, normalmente señalando rutas relativas dentro del servidor. Por ejemplo: ../../password/password.dat, lo cual puede modificar un archivo de passwords. Soluciones:



Reconocimiento

Nos hemos apoyado parcialmente en un tutorial sencillo y bien explicado: developershome. Aquí puede también ver un ejemplo de cómo hacerlo con un JSP.


Volver al índice