如何从 JSF 支持 bean 提供文件下载? [英] How to provide a file download from a JSF backing bean?

查看:25
本文介绍了如何从 JSF 支持 bean 提供文件下载?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有办法从 JSF 支持 bean 操作方法提供文件下载?我已经尝试了很多东西.主要问题是我无法弄清楚如何获取响应的 OutputStream 以将文件内容写入其中.我知道如何使用 Servlet 来实现,但这不能从 JSF 表单中调用并且需要一个新的请求.

如何从当前FacesContext获取响应的OutputStream?

解决方案

简介

您可以通过ExternalContext.在 JSF 1.x 中,您可以通过 ExternalContext#getResponse().在 JSF 2.x 中,您可以使用一堆新的委托方法,例如 ExternalContext#getResponseOutputStream() 而无需从 JSF 引擎盖下获取 HttpServletResponse.

在响应中,您应该设置 Content-Type 标头,以便客户端知道要与提供的文件关联的应用程序.并且,您应该设置Content-Length 标头,以便客户端可以计算下载进度,否则将未知.并且,如果您想要一个 另存为 对话框,您应该将 Content-Disposition 标头设置为 attachment,否则客户端将尝试内联显示它.最后只是将文件内容写入响应输出流.

最重要的部分是调用 FacesContext#responseComplete() 通知 JSF 在你将文件写入响应后它不应该执行导航和渲染,否则响应的结尾将被污染页面的 HTML 内容,或者在较早的 JSF 版本中,当 JSF 实现时,您将收到一个 IllegalStateException 消息,例如 getoutputstream() has been called for this response调用 getWriter() 来呈现 HTML.

关闭ajax/不要使用远程命令!

您只需要确保操作方法由ajax请求调用,而是在使用触发时由普通请求调用..Ajax 请求和远程命令由 JavaScript 处理,由于安全原因,JavaScript 又没有强制另存为与 ajax 响应内容对话的工具.

如果您正在使用例如PrimeFaces <p:commandXxx>,那么你需要确保通过 ajax="false" 属性显式关闭 ajax.如果您使用 ICEfaces,则需要在命令组件中嵌套 <f:ajax disabled="true"/>.

通用 JSF 2.x 示例

public void download() 抛出 IOException {FacesContext fc = FacesContext.getCurrentInstance();ExternalContext ec = fc.getExternalContext();ec.responseReset();//某些 JSF 组件库或某些过滤器可能事先在缓冲区中设置了一些头文件.我们想摆脱它们,否则它可能会发生冲突.ec.setResponseContentType(contentType);//检查所有类型的 http://www.iana.org/assignments/media-types.必要时使用 ExternalContext#getMimeType() 进行基于文件名的自动检测.ec.setResponseContentLength(contentLength);//用文件大小设置它.此标头是可选的.如果省略它会起作用,但下载进度将未知.ec.setResponseHeader("Content-Disposition", "attachment; filename="" + fileName + """);//Save As 弹出魔术在这里完成.你可以给它任何你想要的文件名,这只在 MSIE 中不起作用,它将使用当前请求 URL 作为文件名.OutputStream 输出 = ec.getResponseOutputStream();//现在你可以按照通常的方式将文件的InputStream写入到上面的OutputStream中了.//...fc.responseComplete();//重要的!否则 JSF 将尝试呈现显然会失败的响应,因为它已经用文件写入并关闭.}

通用 JSF 1.x 示例

public void download() 抛出 IOException {FacesContext fc = FacesContext.getCurrentInstance();HttpServletResponse 响应 = (HttpServletResponse) fc.getExternalContext().getResponse();response.reset();//某些 JSF 组件库或某些过滤器可能事先在缓冲区中设置了一些头文件.我们想摆脱它们,否则它可能会发生冲突.response.setContentType(contentType);//检查所有类型的 http://www.iana.org/assignments/media-types.必要时使用 ServletContext#getMimeType() 进行基于文件名的自动检测.response.setContentLength(contentLength);//用文件大小设置它.此标头是可选的.如果省略它会起作用,但下载进度将未知.response.setHeader("Content-Disposition", "attachment; filename="" + fileName + """);//Save As 弹出魔术在这里完成.你可以给它任何你想要的文件名,这只在 MSIE 中不起作用,它将使用当前请求 URL 作为文件名.OutputStream 输出 = response.getOutputStream();//现在你可以按照通常的方式将文件的InputStream写入到上面的OutputStream中了.//...fc.responseComplete();//重要的!否则 JSF 将尝试呈现显然会失败的响应,因为它已经用文件写入并关闭.}

常用静态文件示例

如果您需要从本地磁盘文件系统流式传输静态文件,请替换如下代码:

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(), 输出);

常用动态文件示例

如果您需要流式传输动态生成的文件,例如 PDF 或 XLS,只需在所使用的 API 需要 OutputStream 的地方提供 output.>

例如iText PDF:

String fileName = "dynamic.pdf";String contentType = "应用程序/pdf";//...文档文档 = 新文档();PdfWriter writer = PdfWriter.getInstance(document, output);文档.open();//在此处构建 PDF 内容.文档.close();

例如Apache POI HSSF:

String fileName = "dynamic.xls";String contentType = "application/vnd.ms-excel";//...HSSFWorkbook 工作簿 = new HSSFWorkbook();//在此处构建 XLS 内容.工作簿.写(输出);workbook.close();

请注意,您不能在此处设置内容长度.所以你需要删除设置响应内容长度的行.这在技术上没有问题,唯一的缺点是最终用户会呈现未知的下载进度.如果这很重要,那么您确实需要先写入本地(临时)文件,然后如前一章所示提供它.

实用方法

如果您使用的是 JSF 实用程序库 OmniFaces,那么您可以使用三个方便的 Faces#sendFile() 方法采用 FileInputStreambyte[],并指定文件是否应该下载为附件 (true) 或内嵌 (false).

public void download() 抛出 IOException {Faces.sendFile(file, true);}

是的,此代码按原样完成.您不需要自己调用 responseComplete() 等等.此方法还可以正确处理 IE 特定的标头和 UTF-8 文件名.你可以找到源代码这里.

Is there any way of providing a file download from a JSF backing bean action method? I have tried a lot of things. Main problem is that I cannot figure how to get the OutputStream of the response in order to write the file content to. I know how to do it with a Servlet, but this cannot be invoked from a JSF form and requires a new request.

How can I get the OutputStream of the response from the current FacesContext?

解决方案

Introduction

You can get everything through ExternalContext. In JSF 1.x, you can get the raw HttpServletResponse object by ExternalContext#getResponse(). In JSF 2.x, you can use the bunch of new delegate methods like ExternalContext#getResponseOutputStream() without the need to grab the HttpServletResponse from under the JSF hoods.

On the response, you should set the Content-Type header so that the client knows which application to associate with the provided file. And, you should set the Content-Length header so that the client can calculate the download progress, otherwise it will be unknown. And, you should set the Content-Disposition header to attachment if you want a Save As dialog, otherwise the client will attempt to display it inline. Finally just write the file content to the response output stream.

Most important part is to call FacesContext#responseComplete() to inform JSF that it should not perform navigation and rendering after you've written the file to the response, otherwise the end of the response will be polluted with the HTML content of the page, or in older JSF versions, you will get an IllegalStateException with a message like getoutputstream() has already been called for this response when the JSF implementation calls getWriter() to render HTML.

Turn off ajax / don't use remote command!

You only need to make sure that the action method is not called by an ajax request, but that it is called by a normal request as you fire with <h:commandLink> and <h:commandButton>. Ajax requests and remote commands are handled by JavaScript which in turn has, due to security reasons, no facilities to force a Save As dialogue with the content of the ajax response.

In case you're using e.g. PrimeFaces <p:commandXxx>, then you need to make sure that you explicitly turn off ajax via ajax="false" attribute. In case you're using ICEfaces, then you need to nest a <f:ajax disabled="true" /> in the command component.

Generic JSF 2.x example

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.
}

Generic JSF 1.x example

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.
}

Common static file example

In case you need to stream a static file from the local disk file system, substitute the code as below:

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);

Common dynamic file example

In case you need to stream a dynamically generated file, such as PDF or XLS, then simply provide output there where the API being used expects an OutputStream.

E.g. 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();

E.g. 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();

Note that you cannot set the content length here. So you need to remove the line to set response content length. This is technically no problem, the only disadvantage is that the enduser will be presented an unknown download progress. In case this is important, then you really need to write to a local (temporary) file first and then provide it as shown in previous chapter.

Utility method

If you're using JSF utility library OmniFaces, then you can use one of the three convenient Faces#sendFile() methods taking either a File, or an InputStream, or a byte[], and specifying whether the file should be downloaded as an attachment (true) or inline (false).

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

Yes, this code is complete as-is. You don't need to invoke responseComplete() and so on yourself. This method also properly deals with IE-specific headers and UTF-8 filenames. You can find source code here.

这篇关于如何从 JSF 支持 bean 提供文件下载?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆