Saltar al contenido

¿Cómo proporcionar una descarga de archivo desde un bean de respaldo JSF?

Luego de tanto batallar pudimos hallar la contestación de esta incógnita que ciertos usuarios de nuestro sitio web han presentado. Si deseas aportar algún dato no dudes en aportar tu conocimiento.

Solución:

Introducción

Puedes pasar todo ExternalContext. En JSF 1.x, puede obtener el crudo HttpServletResponse objeto por ExternalContext#getResponse(). En JSF 2.x, puede usar un montón de nuevos métodos delegados como ExternalContext#getResponseOutputStream() sin la necesidad de agarrar el HttpServletResponse de debajo de las campanas JSF.

En la respuesta, debe establecer el Content-Type encabezado para que el cliente sepa qué aplicación asociar con el archivo proporcionado. Y debe configurar el Content-Length encabezado para que el cliente pueda calcular el progreso de la descarga, de lo contrario será desconocido. Y debe configurar el Content-Disposition encabezado a attachment si quieres un Guardar como cuadro de diálogo, de lo contrario, el cliente intentará mostrarlo en línea. Finalmente, simplemente escriba el contenido del archivo en el flujo de salida de respuesta.

La parte más importante es llamar FacesContext#responseComplete() para informar a JSF que no debe realizar navegación y renderizado después de haber escrito el archivo en la respuesta; de lo contrario, el final de la respuesta se contaminará con el contenido HTML de la página, o en versiones JSF anteriores, obtendrá un IllegalStateException con un mensaje como getoutputstream() has already been called for this response cuando la implementación JSF llama getWriter() para renderizar HTML.

¡Apague ajax / no use el comando remoto!

Solo necesita asegurarse de que el método de acción sea no llamado por una solicitud ajax, pero que es llamado por una solicitud normal cuando dispara con y . Las solicitudes de Ajax y los comandos remotos son manejados por JavaScript que a su vez, por razones de seguridad, no tiene facilidades para forzar un Guardar como diálogo con el contenido de la respuesta ajax.

En caso de que esté utilizando, por ejemplo, PrimeFaces , entonces debe asegurarse de desactivar explícitamente ajax a través de ajax="false" attribute. En caso de que esté usando ICEfaces, entonces necesita anidar un en el componente de comando.

Ejemplo genérico de JSF 2.x

public void download() throws IOException 
    FacesContext fc = FacesContext.getCurrentInstance();
    ExternalContext ec = fc.getExternalContext();

    ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
    ec.setResponseContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename.
    ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
    ec.setResponseHeader("Content-Disposition", "attachment; filename="" + fileName + """); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.

    OutputStream output = ec.getResponseOutputStream();
    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.

Ejemplo genérico de JSF 1.x

public void download() throws IOException 
    FacesContext fc = FacesContext.getCurrentInstance();
    HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();

    response.reset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
    response.setContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ServletContext#getMimeType() for auto-detection based on filename.
    response.setContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
    response.setHeader("Content-Disposition", "attachment; filename="" + fileName + """); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.

    OutputStream output = response.getOutputStream();
    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.

Común static ejemplo de archivo

En caso de que necesite transmitir un static archivo del sistema de archivos del disco local, sustituya el código como se muestra a continuación:

File file = new File("/path/to/file.ext");
String fileName = file.getName();
String contentType = ec.getMimeType(fileName); // JSF 1.x: ((ServletContext) ec.getContext()).getMimeType(fileName);
int contentLength = (int) file.length();

// ...

Files.copy(file.toPath(), output);

Ejemplo de archivo dinámico común

En caso de que necesite transmitir un archivo generado dinámicamente, como PDF o XLS, simplemente proporcione output allí donde la API que se utiliza espera un OutputStream.

Por ejemplo, iText PDF:

String fileName = "dynamic.pdf";
String contentType = "application/pdf";

// ...

Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, output);
document.open();
// Build PDF content here.
document.close();

Por ejemplo, Apache POI HSSF:

String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-excel";

// ...

HSSFWorkbook workbook = new HSSFWorkbook();
// Build XLS content here.
workbook.write(output);
workbook.close();

Tenga en cuenta que no puede establecer la longitud del contenido aquí. Por lo tanto, debe eliminar la línea para establecer la longitud del contenido de la respuesta. Técnicamente, esto no es un problema, la única desventaja es que al usuario final se le presentará un progreso de descarga desconocido. En caso de que esto sea importante, entonces realmente necesita escribir en un archivo local (temporal) primero y luego proporcionarlo como se muestra en el capítulo anterior.

Método de utilidad

Si está utilizando la biblioteca de utilidades JSF OmniFaces, puede utilizar una de las tres prácticas Faces#sendFile() métodos que toman un File, o un InputStreamo un byte[]y especificando si el archivo debe descargarse como adjunto (true) o en línea (false).

public void download() throws IOException 
    Faces.sendFile(file, true);

Sí, este código está completo tal como está. No es necesario invocar responseComplete() y así en ti mismo. Este método también trata adecuadamente los encabezados específicos de IE y los nombres de archivo UTF-8. Puede encontrar el código fuente aquí.

public void download() throws IOException


    File file = new File("file.txt");

    FacesContext facesContext = FacesContext.getCurrentInstance();

    HttpServletResponse response = 
            (HttpServletResponse) facesContext.getExternalContext().getResponse();

    response.reset();
    response.setHeader("Content-Type", "application/octet-stream");
    response.setHeader("Content-Disposition", "attachment;filename=file.txt");

    OutputStream responseOutputStream = response.getOutputStream();

    InputStream fileInputStream = new FileInputStream(file);

    byte[] bytesBuffer = new byte[2048];
    int bytesRead;
    while ((bytesRead = fileInputStream.read(bytesBuffer)) > 0) 
    
        responseOutputStream.write(bytesBuffer, 0, bytesRead);
    

    responseOutputStream.flush();

    fileInputStream.close();
    responseOutputStream.close();

    facesContext.responseComplete();


Esto es lo que funcionó para mí:

public void downloadFile(String filename) throws IOException 
    final FacesContext fc = FacesContext.getCurrentInstance();
    final ExternalContext externalContext = fc.getExternalContext();

    final File file = new File(filename);

    externalContext.responseReset();
    externalContext.setResponseContentType(ContentType.APPLICATION_OCTET_STREAM.getMimeType());
    externalContext.setResponseContentLength(Long.valueOf(file.lastModified()).intValue());
    externalContext.setResponseHeader("Content-Disposition", "attachment;filename=" + file.getName());

    final HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();

    FileInputStream input = new FileInputStream(file);
    byte[] buffer = new byte[1024];
    final ServletOutputStream out = response.getOutputStream();

    while ((input.read(buffer)) != -1) 
        out.write(buffer);
    

    out.flush();
    fc.responseComplete();

Calificaciones y reseñas

Finalizando este artículo puedes encontrar las crónicas de otros administradores, tú incluso tienes la opción de insertar el tuyo si lo crees conveniente.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *