有什么方法可以从JSF支持bean操作方法提供文件下载吗?我已经尝试了很多东西。主要问题是我无法弄清楚如何OutputStream
将响应写入文件内容。我知道如何使用进行操作Servlet
,但这不能从JSF表单调用,并且需要新的请求。
如何获得OutputStream
当前响应的FacesContext
?
Answers:
您可以解决所有问题ExternalContext
。在JSF 1.x中,可以通过获取原始HttpServletResponse
对象ExternalContext#getResponse()
。在JSF 2.x中,您可以使用许多新的委托方法,ExternalContext#getResponseOutputStream()
而无需HttpServletResponse
从JSF幕后抢手。
在响应上,您应该设置Content-Type
标题,以便客户端知道要与提供的文件关联的应用程序。并且,您应该设置Content-Length
标题,以便客户端可以计算下载进度,否则它将是未知的。并且,如果需要“ 另存为”对话框,则应将Content-Disposition
标题设置为,否则客户端将尝试内联显示它。最后,只需将文件内容写入响应输出流即可。attachment
最重要的部分是调用,FacesContext#responseComplete()
以告知JSF在将文件写入响应后,它不应执行导航和渲染,否则响应的结尾将被页面的HTML内容或在较旧的JSF版本中污染。 ,您将收到IllegalStateException
一条消息,例如getoutputstream() has already been called for this response
当JSF实现调用getWriter()
呈现HTML时。
您只需要确保操作方法不是由ajax请求调用的,而是在您使用<h:commandLink>
和触发时由普通请求调用的<h:commandButton>
。Ajax请求和远程命令由JavaScript处理,由于安全性原因,JavaScript又没有任何设施来强制使用ajax响应的内容进行另存为对话框。
如果使用的是PrimeFaces <p:commandXxx>
,则需要确保通过ajax="false"
attribute 明确关闭了ajax 。如果您使用的是ICEfaces,则需要<f:ajax disabled="true" />
在命令组件中嵌套一个。
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.
}
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.
}
如果需要从本地磁盘文件系统流式传输静态文件,请替换以下代码:
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);
如果您需要流式传输动态生成的文件(例如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();
请注意,您不能在此处设置内容长度。因此,您需要删除该行以设置响应内容的长度。从技术上讲这没有问题,唯一的缺点是将向最终用户显示未知的下载进度。如果这很重要,那么您确实确实需要先写入本地(临时)文件,然后如前一章所示将其提供。
如果您使用的是JSF实用工具库OmniFaces,则可以使用三种便捷Faces#sendFile()
方法之一,采用File
,,或InputStream
或byte[]
,并指定文件应作为附件(true
)还是内联(false
)下载。
public void download() throws IOException {
Faces.sendFile(file, true);
}
是的,此代码按原样完整。您不需要自己调用responseComplete()
等等。此方法还可以正确处理IE专用标头和UTF-8文件名。您可以在此处找到源代码。
preRenderView
在无标记视图中使用侦听器。在此处回答了有关下载(以及服务)JSON的类似问题:stackoverflow.com/questions/8358006/…–
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();
}
}
如果希望在运行时生成文件,则可以更改文件读取逻辑。
InputStream
基础架构p:fileDownload
,而且我还没有设法将其转换OutputStream
为InputStream
。现在很明显,即使动作侦听器也可以更改响应内容类型,然后无论如何在用户代理端将响应视为文件下载。谢谢!