如何从JSF支持bean提供文件下载?
有没有提供从JSF后台bean操作方法下载文件的方法? 我尝试了很多东西。 主要的问题是,我无法确定如何获取响应的OutputStream
以便将文件内容写入。 我知道如何用Servlet
,但是这不能从JSF表单调用,并需要一个新的请求。
我如何从当前的FacesContext
获得响应的OutputStream
?
介绍
你可以通过ExternalContext
得到所有的东西。 在JSF 1.x中,您可以通过ExternalContext#getResponse()
获取原始的HttpServletResponse
对象。 在JSF 2.x中,您可以使用一组新的委托方法(如ExternalContext#getResponseOutputStream()
而无需从JSF引擎中获取HttpServletResponse
。
在响应中,您应该设置Content-Type
标头,以便客户端知道与提供的文件关联的应用程序。 而且,您应该设置Content-Length
标头,以便客户端可以计算下载进度,否则将是未知的。 而且,如果需要“ 另存为”对话框,则应将Content-Disposition
标题设置为attachment
,否则客户端将尝试以内联方式显示它。 最后只需将文件内容写入响应输出stream。
最重要的部分是调用FacesContext#responseComplete()
来通知JSF在将文件写入响应之后不应该执行导航和渲染,否则响应的结束将被页面的HTML内容污染,或者在较老的JSF版本中,当JSF实现调用getWriter()
来呈现HTML时,您将得到一个类似getoutputstream() has already been called for this response
的消息的IllegalStateException
getoutputstream() has already been called for this response
。
通用的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. }
通用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. }
常见的静态文件示例
如果您需要从本地磁盘文件系统stream式传输静态文件,请将代码replace为以下代码:
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);
常见的dynamic文件示例
如果您需要stream式传输dynamic生成的文件(例如PDF或XLS),则只需在那里提供output
,其中使用的API需要OutputStream
。
例如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();
例如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();
请注意,您无法在此设置内容长度。 所以你需要删除行来设置响应内容的长度。 这在技术上没有问题,唯一的缺点是最终用户会被呈现一个未知的下载进度。 如果这一点很重要,那么你真的需要先写一个本地(临时)文件,然后按照上一章的内容提供它。
关掉阿贾克斯!
您只需要确保action方法不是由ajax请求调用,而是在您使用<h:commandLink>
和<h:commandButton>
触发时由正常请求调用。 Ajax请求由JavaScript处理,由于安全原因,这些请求又没有设置强制与Ajax响应内容的另存为对话。
如果你正在使用例如PrimeFaces <p:commandXxx>
,那么你需要确保你通过ajax="false"
属性明确地closures了ajax。 如果使用ICEfaces,则需要在命令组件中嵌套<f:ajax disabled="true" />
。
效用方法
如果使用的是JSF实用程序库OmniFaces ,那么可以使用三种方便的Faces#sendFile()
方法中的一种,使用File
或InputStream
或byte[]
,并指定文件是否应作为附件( true
)或内联( false
)。
public void download() throws IOException { Faces.sendFile(file, true); }
是的,这个代码是完整的。 你不需要自己调用responseComplete()
等等。 这个方法也正确地处理IE特定的头文件和UTF-8文件名。 你可以在这里find源代码 。
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(); }
这对我来说是有效的:
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(); }
这里是完整的代码片段http://bharatonjava.wordpress.com/2013/02/01/downloading-file-in-jsf-2/
@ManagedBean(name = "formBean") @SessionScoped public class FormBean implements Serializable { private static final long serialVersionUID = 1L; /** * Download file. */ public void downloadFile() throws IOException { File file = new File("C:\\docs\\instructions.txt"); InputStream fis = new FileInputStream(file); byte[] buf = new byte[1024]; int offset = 0; int numRead = 0; while ((offset < buf.length) && ((numRead = fis.read(buf, offset, buf.length -offset)) >= 0)) { offset += numRead; } fis.close(); HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance() .getExternalContext().getResponse(); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=instructions.txt"); response.getOutputStream().write(buf); response.getOutputStream().flush(); response.getOutputStream().close(); FacesContext.getCurrentInstance().responseComplete(); } }
如果您希望在运行时生成文件,则可以更改文件读取逻辑。