Java EE 6: Subida de imágenes al servidor (Servlet 3.0)

Vamos a ir poco a poco centrándonos en algunas partes concretas del proyecto que presente de la tienda online y así veremos en detalle determinadas partes. Hoy nos vamos a centrar en ofrecer la posibilidad de que el cliente de nuestra web pueda subir imágenes a nuestro servidor web de una manera muy sencilla

Antes de la especificación 3.0 de los Servlets esta tarea se tenía que  hacer usando las librerías de Apache Commons y se convertía en una tarea más larga y menos clara; desde la nueva especificación no es necesario usar ninguna librería externa dado que nos da esta posibilidad de serie y de una manera muy sencilla. Pues bien, comencemos.

El formulario

Lo primero que hay que hacer es una pequeña modificación en el formulario donde se quiera subir la imagen, puesto que ahora los datos no se van a enviar en modo texto sino como un flujo binario, y teniendo en cuenta esto último el verbo HTTP usado será POST y nunca GET; de manera que la cabecera de nuestro formulario tendra una forma como la que os muestro.

Una vez que ya tenemos la estructura del formulario HTML, ya podemos añadir los campos que queramos al formulario al igual que lo hacemos normalmente, sabiendo que el campo para seleccionar la imagen sera de tipo file. Una vez hecho nos quedaría algo como lo que sigue.

Procesando el formulario

Una vez que ya tenemos el formulario HTML preparado ya estaríamos en condiciones de pasar a la parte de servidor para procesar este formulario que hemos creado; es decir, crear el Servlet encargado de procesar este formulario; que veremos que se procesa de una forma algo distinta a como estamos acostumbrados; principalmente debido a que ahora no hemos recibido texto sino un flujo de datos y de ahí tendremos que obtener el texto normal y la fotografía. En el formulario he puesto tres tipos de campos para que veamos como procesar cada uno de ellos (texto, archivo, textarea).

En primer lugar el Servlet encargado de procesar el formulario deberá llevar la anotación @MultipartConfig. Y ahora puede haber varias preguntas clave.

  • ¿Cómo se si se ha enviado la imagen o no? (el usuario pudo no poner la imagen). Sencillo, si el Part (nombre que se le da a un campo en un formulario de este tipo) ocupa cero bits.
  • ¿Cómo se si el archivo que se ha seleccionado es una imagen? Hay un método para saber el tipo de archivo subido. Si contiene la cadena image sabremos que es una imagen (image/jpeg, etc…)
  • ¿Cómo guardamos la imagen en disco? Tendremos que obtener el InputStream del Part.
  • ¿Cómo obtenemos los campos que son de tipo texto, ahora no recibo texto? Ahora veremos que hay una manera sencilla de hacerlo. Os enseñaré los métodos de la clase estática Tools.

Ahora os muestro el código del Servlet completo, he puesto algunos comentarios en el mismo. Está basado en uno de los Servlets de tienda online, he quitado bastante código para mostrar prácticamente solo lo referente al tema que nos concierne ahora en concreto, obviamente entendiendo lo fundamental podemos modificarlo para que cumpla con la función que cada uno desee. En el código veremos que se resaltan las líneas fundamentales que responden a cada una de las preguntas que he planteado en el párrafo anterior. Por supuesto el Servlet estará dado de alta en el descriptor de despliegue y atendiendo peticiones en la ruta a la que se envía el formulario HTML.

Resumiendo, lo primero que hace el servlet es comprobar que se reciben los parámetros correctos y no menos, si la validación ha sido correcta se obtienen los campos de texto como veremos ahora. Posteriormente comprobamos si hay archivo o no, y en caso de que lo haya comprobamos que es una imagen y que el tamaño no exceda de 8 Mb, posteriormente se guarda en disco el archivo. Si queréis obtener más información sobre el archivo os animo a que investiguéis los métodos del objeto Part. Es enviado un error 404 para una petición GET que llegue a este servlet debido a que no tendría porque accederse por dicho verbo HTTP (no hay ninguna razón para hacerlo) y podríamos evitarnos así posibles problemas, evitamos que se “hagan experimentos” con nuestra aplicación web.

Los atributos que yo añado a la petición son usados para dar el mensaje correspondiente en la jsp a la que se redirige, por lo tanto vosotros hacer caso solo al texto de los mensajes.

Y ahora nos falta ya por concretar como obtener los campos de texto y como guardar la imagen en disco que tal como aparece en el Servlet aparece simplificado al aparecer en una única línea, pero claro, hay que ver lo que hace esa línea.

Para el caso del texto veremos que podemos obtener un InputStream del Part del formulario y podemos leerlo por ejemplo con la clase Scanner de Java. La única diferencia entre obtener el texto de un campo de texto y un textarea será que tenemos que mantener los saltos de línea para el segundo como veremos ahora. Mientras que en un campo de texto solo habrá una línea. Otro apunte importante sería especificar la codificación para evitar errores con caracteres como tildes, eñes o similares. Por último decir que observéis como se abren y se cierran los objetos de tipo Scanner asegurándonos de que se cierren siempre ocurra o error o no.

Bueno, y ahora ya solo nos faltaría ver como guardar la imagen en disco, como hemos dicho obteniendo el InputStream del Part del formulario (que ya lo obtuvimos en el Servlet y se lo pasamos ahora a este método estático) y también asegurándonos de cerrar los flujos de datos en todos los casos de error y de éxito.

Hasta aquí creo que no se me queda ningún aspecto por cubrir, únicamente añadir alguna anotación. Comentar que sería interesante para administrar las imágenes guardar sus nombres en una base de datos haciéndolas corresponder con productos por ejemplo en el caso de una tienda. Y aspectos a corregir de mi modo de hacerlo en el proyecto de la tienda sería guardar las imágenes con extensión, puede ser que no todos los navegadores muestren la imagen correctamente, también sería interesante guardar las imágenes con nombres normales y no con códigos, esto último más que nada para el SEO (Search Engine Optimization), debido a que con nombres normales las imágenes se indexarán mejor en los buscadores.

Ahora ya si que he dicho todo lo que tenía que decir y se han cubierto todos los aspectos sobre el tema. Espero que os sea de utilidad esta guía y como siempre digo estoy abierto a dudas sugerencias o lo que os haga falta.


34 Comentarios

  1. A mi me da un error en la importacion de: import control.Tools;
    Tengo Eclipse Kepler en Mac. De donde puedo bajar esta librería para importarla?

    Saludos.

  2. no me reconoce el import control.tools; trabajo en eclipse kleper ayuda

    • Hola:
      ¿Me puedes decir en que lugar no te lo reconoce? Comprueba que hayas descargado bien el proyecto y tengas todos los ficheros. Un saludo!

  3. Miguel

    Exelentes tutoriales .. Estoy muy agradecido .. ! Gracias x compartir tus Conocimientos con los demas ! Saludos .. Sigue asi !! (y)

    • Muchas gracias por tu comentario, me alegro que te haya sido de utilidad el tutorial :) Estos comentarios animan a seguir con estas cosas :)

      Un saludo!

  4. como lo pudiste resolver JeanPierre yo tengo el mismo problema

    • Jean Pierre

      MM mira, mi error estaba en la linea :
      //Obtenemos la ruta absoluta del sistema donde queremos guardar la imagen
      String fileName = this.getServletContext().getRealPath(“/images/products/imagen”);

      filename no es la ruta una carpeta, si no de un archivo (la imagen que estamos creando) por lo tanto añadi la extension .jpg , o sea quedaria:
      String fileName = this.getServletContext().getRealPath(“/images/products/imagen.jpg”);
      Recuerda tener la vista de las extenciones de los archivos activadas, si estas trabajando en windows.

      Y eso sería, saludos.

    • Lo de la extensión no tiene porque ser un problema porque en la tienda online yo los guardaba sin extensión y las imágenes se muestran perfectamente en el navegador. La extensión no es lo que indica el tipo de el archivo, la extensión forma parte del nombre pero nada más.

      Un saludo

    • ya lo pude resolver jean pierre muchas gracias y pues sigo trabajando en eso de las imagenes , estoy tratando de modificar las imagenes del producto por separado, y pues esta muy bien el proyecto saludos

    • Me alegro de que quedara solucionado. Muchas gracias por tus agradecimientos y si tienes algún problema más no dudes en preguntarlo.
      Un saludo!

  5. Jean Pierre

    Estimado gracias por el tutorial, está muy bueno, pero me lanza lo siguiente al probarlo

    java.lang.NullPointerException
    AddServlet.guardarImagenDeProdructoEnElSistemaDeFicheros(AddServlet.java:146)

    y en el codigo de, por ejemplo el método guardarImagenDeProdructoEnElSistemaDeFicheros me lanza el warning posible referencia de puntero nulo.

    estaria muy agradecido si me responde, de antemano, muchismas gracias.

    • Te he editado el comentario porque al tener tanto código se marcaba como SPAM. Lo que marca como posible nulo si que es cierto, quizás debería haber puesto dentro del finally un if para comprobar si son nulos o no. Si te da ese error desde aquí no puedo decirte lo que puede estar pasando. Debes de pasar con el debugger para ver que es lo que es nulo y de donde viene el nulo para que puedas observar el error. Si después de hacer esto sigues con la duda me lo comentas y lo seguimos viendo.

      Un saludo!

    • Jean Pierre

      Muchisimas gracias por la respuesta anterior, pude resolver el problema, ahora me arroja lo siguiente

      may 30, 2013 5:55:54 PM AddServlet guardarImagenDeProdructoEnElSistemaDeFicheros
      SEVERE: B:\Proyectos (Acceso denegado)
      exection file not found
      Error guardando imagen

      El tema del acceso denegado me imagino que es por el tema de los permisos, pero a que archivo tengo que cambiarle estos permisos, a la ruta donde guardare la imagen, al netbeans o al tomcat?? probé con los 3 y no me ha resultado. :s se agradeceria mucho me pudiera orientar con el tema, saludos cordiales.

    • Esto posiblemente se deba a un problema de permisos a nivel de Sistema Operativo más que de Java. ¿Con que sistema Operativo estás trabajando? ¿Cual es la ruta en la que estás escribiendo los archivos?

      Si estás trabajando en un sistema unix yo te aconsejaría que mirases con que usuario correo el demonio de tomcat y mires si en la carpeta que escribes el usuario o el grupo del usuario de tomcat tienen permisos de escritura.

      Saludos!

    • Jean Pierre

      Trabajo con Windows 8 y la ruta a la que estoy escribiendo es B:\Proyectos, probé con rutas relativas y me sucede lo mismo … Acceso denegado

      El tomcat lanza :
      may 30, 2013 5:55:54 PM AddServlet guardarImagenDeProdructoEnElSistemaDeFicheros
      SEVERE: B:\Proyectos (Acceso denegado)
      exection file not found
      Error guardando imagen

      Gracias de antemano por su respuesta.

    • Jean Pierre

      Estimado pude resolver el problema, me sirvio mucho el tutorial, se agradece mucho !! saludos!!

    • Me alegro de que pudieras resolver el problema. Gracias por los agradecimientos.
      Si quieres puedes dejar aquí a modo de comentario como solucionaste el problema por si a otros les sucede lo mismo para que puedan ver la solución.

      Un saludo.

  6. Hola, buen tuto……………sin embargo tengo un problema al ejecutarlo en eclipse

    18-abr-2013 12:59:11 trastienda.util.Tools guardarImagenDeProdructoEnElSistemaDeFicheros
    GRAVE: /home/julio/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/SISCON_RNDP/archivos (Is a directory)
    18-abr-2013 12:59:11 org.apache.catalina.core.StandardWrapperValve invoke
    GRAVE: Servlet.service() para servlet AddServlet lanzó excepción

    Me podrias ayudar con este tema……….gracias

    • Hola:

      Me alegro de que te haya servido. Respecto al error que me comentas, con el log que me das no puedo deducir de que puede tratarse, parece que el fallo está en el método de guardar la imagen, pero con esa información más no puedo deducir, mira haber si te sale algún error más. También puedes intentar seguir la ejecución con el debugger.

      Saludos

  7. sergio

    Buenas!! estoy haciendo una web y entre las secciones que quiero incluir me gustaria que hubiera una en la cual se pudiesen subir y bajar archivos grandes (musica, videos,etc) independientemente del formato. He buscado por los foros pero los ejemplos no me han servido. Si alguien tiene algo al respecto, agradecería lo comparta. De todas formas cuando lo consiga subire el resultado aqui. Nos vemos!!

    • Hola:

      Con el código de esta entrada yo diría que se puede subir cualquier tipo de archivo. Lo único que tendrías que quitar la restricción que pongo yo de los 8Mb y el tipo de archivo para que sea imagen.

      Lo único, que para subir archivos grandes esta solución valdría pero no es amigable porque mientras se sube no se indica nada, deberías hacer alguna implementación en cliente con HTML5 o algo parecido para mostrar al menos una barra de progreso que indique al usuario que se está subiendo y no es que se haya quedado tostado. Se que se puede hacer como te digo con HTML5, pero no te puedo decir como porque nunca he programado en HTML5.

      Saludos!

  8. Sergio

    Gracias a los aportes todos podemos aprender. Gracias!!. Compartir y más el conocimiento, es vivir.

    • Muchas gracias, me alegro de que haya sido de utilidad. Un placer poder ayudar :)

      Un saludo!

  9. Queria psar a dar las gracias por estearticulo.. pronto tengo que presentar un trabajo y me salvaste la vida! pero tengo una duda..
    por que ? cuando haces el while preguntas si leido es distinto a -1??

    while (leido != -1)

    Oo

    • Me alegro de que te haya servido. Gracias por el comentario.

      El método read() del InputStream devuelve el valor de 1 byte leído y si no encuentra el próximo devuelve -1, es decir que ha terminado. Por eso pregunto cuando devuelve -1. Te pego el javadoc del método que aparece en la API oficial de java.
      “Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. If no byte is available because the end of the stream has been reached, the value -1 is returned”

      Un saludo!

  10. daniel

    me da un error en la importacion
    import control.Tools;
    import javax.servlet.annotation.MultipartConfig;

    Que no los encuentra, donde puedo descargar esas Liberias?… tambien el “InputStream” me dice que no esta declarado. Gracias de antemano.

    • El control.Tools solo se usa para lo de anadirMensaje().
      Puedes ver más detalles aquí, ya que es un poco de código de la Tienda Online. http://jdiezfoto.es/informatica/ejemplo-javaee-una-tienda-online/

      El caso eso lo puedes quitar, peor lo de la annotation es estándar de Java EE, intenta trabajar con la última versión de NetBeans y GlassFish que tiene ya el Java EE actualizado y a la hora de crear un proyecto elige WebApplication.

      Saludos

  11. hola Juan, muy interesante el tema de tratamiento de las imagenes binarias, ya que había visto en muchas partes tratar estos con las librerías commons de apache, pero me gustaria saber, si sabes y puedes, como almacenar esta imagen binaria desde una base de datos como por ej.Postgres, luego recuperar esta imagen, para ser insertada en una pagina HTML… había visto por ahí que hay que utilizar la codificación base64, sera necesario almacenarla con este formato base64 en base de datos, para luego insertarla en el HTML, o no seria necesario ahora con los servelet 3.0 ??

    Saludos, y de ante mano muchas gracias, seria de gran aporte !!
    Diego :D

    • Gracias por el comentario, me alegro de que te haya sido útil .
      Siento no poder ayudarte con esto, yo no se como guardar la imagen en una base de datos, pero vamos lo que pides me parece un poco extraño, y no se si se podrá hacer, pero lo normal es que guardes en la base de datos la ruta de la imagen como un atributo de cada producto o lo que se sea.

      Un saludo!

  12. leonardo grimaldos

    muy buen aporte, en mi opinión esta muy completo, gracias.

    • Muchas gracias por el comentario, me alegro que te haya sido de utilidad.

      Si piensas que puede ser útil la realización de alguna guía en especial sobre lo que no haya mucha información o este difusa o no haya mucho en castellano no dudes en decírmelo.

      Saludos

  13. Celina

    Excelente ejempli, me ha servido como idea no tienes….Gracias a ti, solvente y salí a tiempo con mi trabajo. Muy agradecida.

  14. Muchas gracias por el comentario, me alegro mucho de que te haya sido de utilidad, con esa intención esta hecho.

    Saludos

Trackbacks/Pingbacks

  1. Bitacoras.com - Información Bitacoras.com...Valora en Bitacoras.com: No hay resumen disponible para esta anotación...

Deja un comentario

%d personas les gusta esto: