有没有办法从JSF支持bean操作方法提供文件下载?
我尝试过很多东西。主要问题是我无法弄清楚如何获取响应的OutputStream
以便将文件内容写入。我知道如何使用Servlet
来完成它,但是这不能从JSF表单调用并需要新的请求。
如何从当前OutputStream
FacesContext
答案 0 :(得分:212)
您可以通过ExternalContext
获取所有内容。在JSF 1.x中,您可以通过ExternalContext#getResponse()
获取原始HttpServletResponse
对象。在JSF 2.x中,您可以使用一组新的委托方法,如ExternalContext#getResponseOutputStream()
,而无需从JSF引擎盖下获取HttpServletResponse
。
在响应中,您应设置Content-Type
标头,以便客户端知道与提供的文件关联的应用程序。并且,您应该设置Content-Length
标头,以便客户端可以计算下载进度,否则将是未知的。并且,如果您需要另存为对话框,则应将Content-Disposition
标头设置为attachment
,否则客户端将尝试将其显示为内联。最后,只需将文件内容写入响应输出流。
最重要的部分是调用FacesContext#responseComplete()
告知JSF在将文件写入响应后不应执行导航和呈现,否则响应的结尾将被HTML内容污染在页面或旧的JSF版本中,当JSF实现调用IllegalStateException
来呈现HTML时,您将获得getoutputstream() has already been called for this response
消息,如getWriter()
。
您只需要确保ajax请求调用的操作方法不,但是当您使用<h:commandLink>
和{{1}触发时,它会被正常请求调用}}。 Ajax请求和远程命令由JavaScript处理,由于安全原因,它不会强制使用ajax响应的内容强制另存为对话。
如果你正在使用例如PrimeFaces <h:commandButton>
,然后您需要确保通过<p:commandXxx>
属性明确关闭ajax。如果您正在使用ICEfaces,那么您需要在命令组件中嵌套ajax="false"
。
<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.
}
如果您需要流式传输动态生成的文件(例如PDF或XLS),则只需在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);
处提供所使用的API所需的output
。
E.g。 iText PDF:
OutputStream
E.g。 Apache POI HSSF:
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();
请注意,您无法在此处设置内容长度。因此,您需要删除该行以设置响应内容长度。这在技术上没有问题,唯一的缺点是最终用户将呈现未知的下载进度。如果这很重要,那么你真的需要先写一个本地(临时)文件,然后按照前一章所示提供它。
如果您正在使用JSF实用程序库OmniFaces,那么您可以使用三种方便的Faces#sendFile()
方法中的一种来获取String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-excel";
// ...
HSSFWorkbook workbook = new HSSFWorkbook();
// Build XLS content here.
workbook.write(output);
workbook.close();
或File
,或者InputStream
,并指定该文件是作为附件(byte[]
)还是内联(true
)下载。
false
是的,此代码按原样完成。您不需要自己调用public void download() throws IOException {
Faces.sendFile(file, true);
}
等等。此方法还可以正确处理特定于IE的标头和UTF-8文件名。您可以找到source code here。
答案 1 :(得分:4)
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();
}
答案 2 :(得分:3)
这对我有用:
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();
}
答案 3 :(得分:0)
这是我的解决方案,扩展BalusC's answer
public static void download(
ByteArrayOutputStream baos,
String downloadFileName,
String contentType
) {
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext externalContext = context.getExternalContext();
externalContext.responseReset();
HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
response.reset();
response.setContentType(contentType);
response.setHeader("Expires", "0");
response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
Integer size = baos.size();
response.setHeader("Content-Length", size.toString());
response.setHeader(
"Content-Disposition",
"attachment; filename=\"" + downloadFileName + "\""
);
try {
try (OutputStream responseOs = response.getOutputStream()) {
baos.writeTo(responseOs);
}
}
catch (IOException e) {
throw new IOUncheckedException(e);
}
context.responseComplete();
}
答案 4 :(得分:-2)
这是完整的代码段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();
}
}
如果希望在运行时生成文件,可以更改文件读取逻辑。