Tip Struts 2 #06: descarga de ficheros


VERSIONES

  1. 12/09/2013 (Primera publicación)
  2. 23/11/2013: Añadida obtención de Mime Type
  3. 07/12/2015: Actualizado a Struts 2.3.24.1

struts2 tip

La sección de tips Struts 2 se «inauguró» con dos pequeños artículos sobre cómo subir ficheros. En este artículo vamos a ver lo contrarío, esto es, cómo devolver un fichero binario al navegador de nuestro usuario. En el proyecto de ejemplo se van a implementar dos estrategias:

  1. Enviar el fichero escribiendo directamente en el Response. Esta técnica no tiene nada que ver con Struts 2 y podemos utilizarla en cualquier aplicación web Java.
  2. Utilizar el response stream ofrecido por defecto por Struts 2. En realidad, se trata de hacer lo mismo que la estrategia anterior pero cambiando código Java por la configuración de un result por lo que la solución es más elegante.

El punto de partida es una pantalla en la que se permitirá al usuario solicitar la obtención de un fichero pdf de prueba seleccionando uno de los mecanismos que se han comentado. Este es el formulario inicial form.jsp (el proyecto de ejemplo tiene la misma estructura que la empleada en los demás tips):

<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html> 
<html>
	<head>
		<%@include file="/jsp/head.jsp" %>
	</head>
	
	<body>
	
	<s:actionerror cssStyle="font-style:bold;background-color:#ff6e6e;width:95%"/>
	
		<s:form action="fileDownloadResponse" theme="simple">					
			<s:submit key="response"/>
		</s:form>
		<s:form action="fileDownloadStream" theme="simple">					
			<s:submit key="result"/>
		</s:form>
		<%@include file="/jsp/footer.jsp" %>
	</body>
</html>

struts 2 file download

Obsérvese el uso de la etiqueta s:actionerror que ya vimos en el tip anterior para mostrar posibles errores. Para devolver el pdf escribiendo directamente en el response usaremos el siguiente método comentado y que puede usarse a modo de plantilla ya que casi siempre será igual y generalmente sólo habrá que cambiar el MIME type (valor fundamental para que el navegador pueda interpretar qué es lo que está recibiendo) y el nombre del fichero.


private static final String PATH = ServletActionContext.getServletContext().getRealPath("/") + "WEB-INF" + File.separator + "test.pdf";

public String response()
	{
		File file = new File(PATH);

		byte[] result = null;
		try
		{
			result = IOUtils.toByteArray(new FileInputStream(file));
                        //las FileUtils de Apache son dependencia de Struts 2
			FileUtils.writeByteArrayToFile(file, result);
			HttpServletResponse response = ServletActionContext.getResponse();
			response.setContentLength(result.length);
			response.setContentType("application/pdf");
			//attachment: el fichero se ofrece para descargar
			//inline: se solicita al navegador que lo abra él mismo si tiene el plugin adecuado
			//o puede mostrar el tipo de archivo
			response.setHeader("Content-Disposition", "attachment; filename=\"test.pdf\"");

			ServletOutputStream out = response.getOutputStream();
			out.write(result);
			out.flush();
		}
		catch (Exception ex)
		{
			LOGGER.error(ex.getMessage(), ex);
			addActionError(ex.getMessage());
			return INPUT;
		}
		return NONE;
	}

Algunas aclaraciones adicionales:

  • Se ha optado por acceder al response a través de la clase ServletActionContext, otra alternativa es hacer que el Action implemente ServletResponseAware.
  • El action devuelve NONE puesto que el response ya ha sido devuelto al cliente y si dejamos que Struts 2 continue el flujo obtendremos una excepción aunque sin ninguna consecuencia para el usuario:
    java.lang.IllegalStateException: getOutputStream() has already been called for this response

El result stream permite enviar un fichero o flujo de bytes al navegador. Lo que haremos en realidad es lo mismo que hemos visto pero configurando este result. Ahora nuestro método quedaría así:


private InputStream pdf = null;

	private long contentLength = 0;

	private String contentName;

public String resultStream() 
	{
		File file = new File(PATH);
		try 
		{
			this.pdf = new FileInputStream(file);
		} 
		catch (FileNotFoundException ex) 
		{
			LOGGER.error(ex.getMessage(), ex);
			addActionError(ex.getMessage());
			return INPUT;
		}
		this.contentLength = file.length();
		this.contentName = "test.pdf";
		
		return "stream";
	}

El struts.xml con el result comentado con sus princiaples parámetros es el siguiente:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
        "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>

	<constant name="struts.devMode" value="true" />
	<constant name="struts.custom.i18n.resources" value="global" />	

	<package name="default" namespace="/" extends="struts-default">
	
	<global-results>
		<result name="input">/jsp/form.jsp</result>
	</global-results>

		<action name="fileDownload"
			class="com.danielme.tips.struts2.actions.FileDownloadAction">
		</action>

		<action name="fileDownloadResponse"
			class="com.danielme.tips.struts2.actions.FileDownloadAction" method="response"/>


		<action name="fileDownloadStream"
			class="com.danielme.tips.struts2.actions.FileDownloadAction" method="resultStream">


			<result name="stream" type="stream">
				<!-- nombre del atributo del action que contiene el InputStream a devolver, 
					por defecto se considera inputStream -->
				<param name="inputName">pdf</param>
				<!-- nombre del atributo con el tamaño del fichero -->
				<param name="contentLength">${contentLength}</param>
				<!-- mime type del fichero, también se podría usar un atributo. Por defecto 
					es text/plain -->
				<param name="contentType">application/pdf</param>
				<!-- el content disposition del header del response. En este ejemplo 
					vamos a usar un valor fijo pero tomando como nombre de archivo un atributo 
					del action -->
				<param name="contentDisposition">inline;filename="${contentName}"</param>
				<!-- Por defecto es true y habilita el "cacheo" del fichero en el navegador. 
					Si este puede ser variable conviene deshabilitarlo para evitar posibles efectos 
					colaterales -->
				<param name="allowCaching">false</param><!-- default true -->
			</result>

		</action>

	</package>

</struts>

Si pulsamos response en el formulario, deberíamos ver una ventana para abrir o descargar el fichero, en función de su mimetype y del navegador empleado, ya que lo estamos enviando desde el servidor como «attachment».

save as

En cambio, si solicitamos «Result STREAM», como es inline, el navegador intentará mostrar el fichero él mismo si es que puede hacerlo para ese tipo de fichero.

inline

Por último, un pequeño tip: podemos obtener el Mime Type correspondiente a una extensión con el siguiente snippet:

String contentType = ServletActionContext.getServletContext().getMimeType(fileName);

El Mime Type se obtiene de la configuración del contenedor (se devuelve null si no se encuentra). En el caso de Tomcat, esta configuración se encuentra en el fichero /conf/web.xml por lo que es fácilmente configurable. Asimismo, se puede definir una configuración específica en el web.xml de nuestra aplicación.

El proyecto completo para Maven se encuentra en Github. Para más información sobre cómo utilizar GitHub, consultar este artículo.

<< TIPS STRUTS 2

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.