domingo, 30 de marzo de 2008

FileUpload Subida de ficheros al servidor

Descargar ficheros es sumamente sencillo desde el servidor, el problema surge cuando queremos subirlos, los JSP y Servlets de Java no tienen mecanismos para poder manejar la subida de ficheros desde formularios, siendo un problema tratar de extraer el contenido desde HTTP request. Una mejor opcion que nos puede ayudar es utilizar librerias de terceros.

Existe una robusta libreria open-source diseñada para este propósito del paquete
Apache Jakarta Commons FileUpload.

Requerimientos

  • Descargar la libreria Jakarta Commons FileUpload 1.2.1: commons-fileupload-1.2.1.jar
  • Ademas de esta libreria necesitaremos otra de complemento interno de Commons FileUpload llamada Jakarta Commons IO librarie: commons-io-1.4.jar. En este articulo se asume que usted esta utilizando estas librerias.
Software de prueba

El código ha sido probado en un entorno de Java EE5 con Tomcat 5.5.17.

Instalando Apache Jakarta Commons FileUpload y IO en el contenedor como Tomcat

El proximo paso es copiar los archivos jar en el directorio $CATALINA_HOME/webapps/NameProyect/WEB-INF/lib de su aplicación.


Diseñar el formulario


Para subir los ficheros al servidor debemos de crear un formulario un poco especial de tipo POST y MultiPart y que la peticion la dirija a un Servlet: subir.jsp
<form method="POST" enctype="multipart/form-data"
action="<%= request.getContextPath()%>/UploadFile">
Por favor, seleccione el trayecto del fichero a cargar
<br><input type="file" name="fichero">
<input type="submit">
</form>


getContextPatch() obtiene la ruta de su aplicación es este caso "/NameProyect"
Figura 1: formulario JSP para subir un archivo al servidor

Creando nuestro Servlet
Ahora hay que crear el Servlet bajo el nombre de UploadFile.java donde se mostará la forma básica de subir ficheros y como configurar algunos aspectos de la subida para un mejor control, como p.e el limitar el tamaño del fichero:
package servlets;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.File;

import java.util.List;
import java.util.Iterator;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;


/**
*
* @author hugo
* @version
*/
public class UploadFile extends HttpServlet {

private String dirUploadFiles; //directorio donde se guardara los archivos


protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

// 1. obtengo el directorio donde guardare los archivos, desde un parametro de
// contexto en el archivo web.xml
dirUploadFiles = getServletContext().getRealPath( getServletContext().getInitParameter( "dirUploadFiles" ) );

// 2. Si la peticion es de tipo multi-part,
// static boolean isMultipartContent() devuelve true/false
if( ServletFileUpload.isMultipartContent( request ) ){

// 3. crear el arhivo factory
// DiskFileItemfactory es una implementacion de FileItemfactory
// esta implementacion crea una instacia de FileItem que guarda su
// contenido ya sea en la memoria, para elementos pequeños,
// o en un archivo temporal en el disco, para los
// elementos de mayor tamaño
FileItemFactory factory = new DiskFileItemFactory();


// 4. crear el servlet upload
// es un API de alto nivel para procesar subida de archivos
// Por defecto la instancia de ServletFileUpload tiene los siguientes valores:
// * Size threshold = 10,240 bytes. Si el tamaño del archivo está por debajo del umbral,
// se almacenará en memoria. En otro caso se almacenara en un archivo temporal en disco.
// * Tamaño Maximo del cuerpo de la request HTTP = -1.
// El servidor aceptará cuerpos de request de cualquier tamaño.
// * Repository = Directorio que el sistema usa para archivos temporales.
// Se puede recuperar llamando a System.getProperty("java.io.tmpdir").
ServletFileUpload upload = new ServletFileUpload( factory );
/* 5. declaro listUploadFiles
* 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" />)
*
* 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

*
*/
// limito a 300 Kb el humbral del tamaño del archivo a subir
// Long.parseLong( getServletContext().getInitParameter( "maxFileSize" ) )
upload.setSizeMax( new Long( getServletContext().getInitParameter( "maxFileSize" ) ).longValue() ); // 1024 x 300 = 307200 bytes = 300 Kb

List listUploadFiles = null;
FileItem item = null;

try{
// 6. adquiere la lista de FileItem asociados a la peticion
listUploadFiles = upload.parseRequest( request );

/* 7. Iterar para obtener todos los FileItem
* 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()
*/
Iterator it = listUploadFiles.iterator();
while( it.hasNext() ){
item = ( FileItem ) it.next();
// 8. evaluamos si el campo es de tipo file, para subir al servidor
if( !item.isFormField() ){
//9. verificamos si el archivo es > 0
if( item.getSize() > 0 ){
// 10. obtener el nombre del archivo
String nombre = item.getName();
// 11. obtener el tipo de archivo
// e. .jpg = "image/jpeg", .txt = "text/plain"
String tipo = item.getContentType();
// 12. obtener el tamaño del archivo
long tamanio = item.getSize();
// 13. obtener la extension
String extension = nombre.substring( nombre.lastIndexOf( "." ) );

out.println( "Nombre: " + nombre + "<br>");
out.println( "Tipo: " + tipo + "<br>");
out.println( "Extension: " + extension + "<br>");
// 14. determinar si el caracter slash es de linux, o windows
//String slashType = ( nombre.lastIndexOf( "\\" ) > 0 ) ? "\\" : "/"; // Windows o Linux
// 15. obtener la ultima posicion del slash en el nombre del archivo
//int startIndex = nombre.lastIndexOf( slashType );
// 16. obtener el nombre del archivo ignorando la ruta completa
//String myArchivo = nombre.substring( startIndex + 1, nombre.length() );
// 17. Guardo archivo del cliente en servidor, con un nombre 'fijo' y la
// extensión que me manda el cliente,
// Create new File object
File archivo = new File( dirUploadFiles, nombre );

// 18. Write the uploaded file to the system
item.write( archivo );
if ( archivo.exists() ){
out.println( "GUARDADO " + archivo.getAbsolutePath() + "</p>");
}else{
// nunca se llega a ejecutar
out.println( "FALLO AL GUARDAR. NO EXISTE " + archivo.getAbsolutePath() + "</p>");
}

}
}
}

}catch( FileUploadException e ){
e.printStackTrace();
}catch (Exception e){
// poner respuesta = false; si existe alguna problema
e.printStackTrace();
}
}
out.println( "Fin de la operacion! ;)");
out.close();
}

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

}


Agreue la siguiente configuración al archivo web.xml:

<servlet>
<servlet-name>UploadFile</servlet-name>
<servlet-class>servlets.UploadFile</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadFile</servlet-name>
<url-pattern>/UploadFile</url-pattern>
</servlet-mapping>
<context-param>
<param-name>dirUploadFiles</param-name>
<param-value>/public/images/avatar</param-value>
</context-param>
<context-param>
<param-name>maxFileSize</param-name>
<param-value>307200</param-value>
</context-param>


Testeando:

Figura 2: Procesamiento de la subida de un fichero al servidor

Notas finales:

  1. Si el tamaño del fichero subido excede los 300 Kb, parametro que hemos configurado dentro de web.xml con el nombre de maxFileSize, no se mostrará ningun mensaje en pantalla luego de que el Servlet proceso la petición de subir el fichero, esto es debido a que detro del bloque try/catch del Servlet UploadFile.java se lanza una excepción del tipo FileUploadBase.FileSizeLimitExceededException por el método parseRequest(), para poder mejorar este comportamiento deberiamos crear un método por ejemplo processFormFiled() que procese la subida de un fichero desde un formulario y que devuelva true/flase como respuesta.
  2. Se puede mejorar la presentación del procesamiento de la subida del fichero mediante un progress bar utilizando Ajax.
Download source code


27 comentarios:

aronari dijo...

Excelente!!!!

Miguelillo! dijo...

Estaba buscando información sobre esto, espero que me sirva.Voy a probarlo! GRacias

Anónimo dijo...

Saludos, dices al principio de la página que descargar ficheros desde el servidor es muy sencillo...

pues bien, te agradecería que me dijeras cómo lo haces ya que me estoy volviendo loco! la subida ya la tengo, pero la bajada no se como meterle mano!

Muchas gracias.

Anónimo dijo...

Muchas graciasss
lo necesitaba mucho

Anónimo dijo...

muy buena!! informacion ... gracias a mi tmb m ayudo mucho ... saludos

Unknown dijo...

Muchas gracias por la info... claro que me gustaria saber como puedo descargar los archivos que se subieron al servidor.
Nuevamente Gracias

Anónimo dijo...

hola, el ejm me sirvio de gran ayuda y esta bueno, mas bien tengo un problema parecido y quisiera saber si hay alguna solucion para esto, tengo q hacer una aplicacion donde el servidro tiene q tener acceso a un directorio del cliente si q el cliente interactue, ya tendria la ruta en el servidor del directorio del cliente solo quisiera saber si hay un framework q permita esto, tiene q ser de manera transparente para el cliente, con la libreria commons fileupload el cliente siempre tiene q interactuar, hay alguna manera para q pueda hacer esto sin q el cliente intervenga

Anónimo dijo...

Vientos, tu aporte me sirvio de mucho, solo que tengo un problemas, y es que en en buenisimooooooooo IE a la hora de obtener elnombre del archivo me da todo el path, y yo solo necesito el nombre, el cual almacenare en una base de datos y no lo he podido resolver, podrias hecharme un mano?

Anónimo dijo...

Yo solucioné lo del internet explorer así:

int marcador = nombre.lastIndexOf('\\');
if (marcador > 0)
{
nombre = nombre.substring(marcador);
}

Seguro que hay formas mejores pero soy novata en Java. :P

Anónimo dijo...

Hola, me sirve de mucho tu post, pero ahora tengo un problemilla, es que al ponerle enctype="multipart/form-data" al formulario sólo sirve para enviar archivos, no puedo enviar otros datos como los valores de unas cajas de texto o cualquier otro. Es decir, no puedo obtenerlos usando un 'request.getparameter', devuelve 'null'. Los he obtenido usando parte de tu codigo, en el punto 8 antes de evaluar si el campo es del tipo 'file'. Sabes si hay otra manera? o solo la que hice y el crear otro formulario sin el 'multipart/form-data'?

YoMisma dijo...

Chapó!

jaja

Muchas gracias por la info, es un lujo que los agraciados en conocimientos los repartais con los humanitos medios

Saludos

Anónimo dijo...

Excelente tutorial, pero tengo mil dudas
te comento una de ellas, he seguido tus instrucciones y he cargado los archivos creado el servlet y el jsp, con el codigo que tienes (casi es una copia fiel de tu codigo XD) tambien los archivos org\apache\commons\fileupload y
org\apache\commons\IO en proyecto\web\WEB-INF\lib
ahora cuando ejecuto el archivo me manda una exepcion:
Compiling 99 source files to C:\proyecto\build\web\WEB-INF\classes
C:\proyecto\web\WEB-INF\lib\org\apache\commons\fileupload\portlet\PortletFileUpload.java:22: package javax.portlet does not exist
import javax.portlet.ActionRequest;
C:\proyecto\web\WEB-INF\lib\org\apache\commons\fileupload\portlet\PortletFileUpload.java:69: cannot find symbol
symbol : class ActionRequest...

soy novato en todo esto de los servlets y es mi primera aplicacion, para subir archivos, posiblemente tenga por ahi algun error "tonto" del que no me he dado cuenta, saludos.

Foryx dijo...

mira yo busco hacer lo siguiente pero no lo he logrado
quiero subir un archivo pero que yo no tenga que elejirlo desde el formulario, sino lo que quiero es que yo le predetermine la ruta de origen del archivo y automaticamente al dar clik en una pagina anterior direccionanado a upload.jsp cargue el archivo predeterminado y lo ponga en la ruta que yo le especifique.
espero me enetiendan.
gracias.
visto de otra manera lo que yo quiero hacer es una copia de una archivo desde una ubicacion a otra (de un servidor a otro) por medio de paginas .jsp o en su defecto saber como enviar variables desde perl a jsp por que no llegan.

Anónimo dijo...

Saludos !!

Muy buen aporte, sencillo, practico y entendible, gracias man

Anónimo dijo...

Muy buen tutorial.
Pero tengo una pregunta...
¿Porque tenemos que bajar las librerias de Jakarta Commons IO,si realmente estamos utilizando las de "java.io"

Saludos

Anónimo dijo...

Hola, el problema que tengo, es que me presenta que se sube de manera correcta al server, pero cuando voy al directorio no esta hay el archivo, peor aun, cuando veo el log., me dice que ocurrió una excepción con UploadFile.writer(file) es decir el error es este, aunque hay una traza completa, pero se que este fue la gota que derramo la copa, at org.apache.commons.fileupload.disk.DiskFileItem.write(DiskFileItem.java:439)

por favor, ayúdanos con esto, todos tenemos dudas, debes ayudarnos, ya que eres creador de este post, y es cierto que no es un foro, pero una aclaración no tiene nada de malo.

Anónimo dijo...

Excelente el codigo, muchas gracias, hace rato que buscaba un ejemplo como este. un saludo desde Colombia.

Anónimo dijo...

KALIMAN dijo...Usted dice: "1)Si el tamaño del fichero subido excede los 300 Kb,...se lanza una excepción del tipo FileUploadBase.FileSizeLimitExceededException por el método parseRequest(),..."
La excepción que se lanza no es como Usted dice, sino:

FileUploadBase.SizeLimitExceededException

(probado con Apache Tomcat 7.0.12)

Anónimo dijo...

Muchas gracias por tu aporte en la web, que Dios te bendiga muchooooo!!!

Harlem Arispe dijo...

Excelente codigo!

vaickiballs dijo...

Muy bueno!Gracias por compartirlo

Anónimo dijo...

Parcero, usted no se imagina cuanto e buscado esta info, deberas qu se le agradece....ufffffffff qu chimbaaa....gracias parcero

Claudia dijo...

excelente código me ha sido de gran ayuda, pero tengo una duda espero me la puedan responder me corre perfectamente el código y como se observa se corre todo mediante el net.beans y me genera la carpeta de public/imagenes/avarar , dentro del proyecto y ahi me sube los archivos , pero cuando le cambio la ruta , si me dice q tipo de archivo es , el nombre, la extencion , pero no me lo guarda enla nueva ruta por ejemplo c:/archivos y si existe el archivo , xq tampoco me sale el mensaje q no existe el archivo pero tampoco me sale q lo aguardo , sin mas por el momento agradezco de antemano su pronta respuesta saludos

dgaitan1 dijo...

>:C
El sistema no puede encontrar la ruta especificada
:´C

dgaitan1 dijo...

>:C
El sistema no puede encontrar la ruta especificada
:´C

Anónimo dijo...

muchas gracias, me has sacado de problemas

Anónimo dijo...

What i ԁο not understοod iѕ if tгuth be told hоw
you аre not reаllу a lot moгe smartly-appгеciаtеԁ than you might be
now. Үou're so intelligent. You already know thus considerably when it comes to this matter, made me in my opinion consider it from numerous varied angles. Its like women and men aren't fаѕсinated
unless it's something to accomplish with Girl gaga! Your own stuffs excellent. All the time care for it up!

Here is my page :: BlackChipPoker Offer ()