如何从 JSF 支持 bean 提供文件下载?

发布于 2025-01-08 18:01:44 字数 239 浏览 5 评论 0原文

有什么方法可以从 JSF 支持 bean 操作方法提供文件下载吗? 我已经尝试了很多事情。主要问题是我无法弄清楚如何获取响应的 OutputStream 以便写入文件内容。我知道如何使用 Servlet 来完成此操作,但这不能从 JSF 表单调用,并且需要新的请求。

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

一杯敬自由 2025-01-15 18:01:44

简介

你可以通过 ExternalContext 获取所有内容。有一堆 getResponseXxx()setResponseXxx() 方法委托给在幕后使用的 HttpServletResponse

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

最重要的部分是调用 FacesContext #responseComplete() 通知 Jakarta Faces 在将文件写入响应后不应执行导航和渲染,否则结束响应将被页面的 HTML 内容污染,或者在较旧的 Faces 版本中,您将收到 IllegalStateException ,其中包含类似 getoutputstream() 已为此响应调用 的消息> 当 Faces 实现调用 getWriter() 来呈现 HTML 时。

关闭阿贾克斯!

您只需确保操作方法不是由 ajax 请求调用,而是在您使用 。由 / 触发的 Ajax 请求由 JavaScript 处理,而出于安全原因,默认情况下没有任何设施可以处理 Ajax 请求。强制与 ajax 响应内容进行另存为对话。

如果您使用的是 PrimeFaces ,那么您需要确保通过 ajax="false" 属性显式关闭 ajax。 也不能使用。如果您使用 ICEfaces,则需要在命令组件中嵌套

如果您使用的是 PrimeFaces 10 或更高版本,则可以使用 在下载文件时使用 onstart、<代码>更新,<代码>oncomplete等等。

通用操作方法示例

<h:commandButton value="Download" action="#{bean.download}" />
public void download() throws IOException {
    String fileName = ...;
    String contentType = ...;
    long contentLength = ...;

    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();

    externalContext.responseReset(); // Some Faces 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.
    externalContext.setResponseContentType(contentType); // Check https://www.iana.org/assignments/media-types for all types.
    externalContext.setResponseContentLengthLong(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.
    externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want.

    OutputStream output = externalContext.getResponseOutputStream();

    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    facesContext.responseComplete(); // Important! Otherwise Faces 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 = externalContext.getMimeType(fileName);
    long contentLength = file.length();

    // ...

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

常见动态文件示例

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

例如 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();

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

实用方法

如果您使用 Faces 实用程序库 OmniFaces,那么您可以使用三个方便的 Faces#sendFile() 方法采用 PathFileInputStream 或 byte[],并指定文件应作为附件下载 (true) 还是内联下载 (false)。

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

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

Introduction

You can get everything through ExternalContext. There are a bunch of getResponseXxx() and setResponseXxx() methods which delegate to the HttpServletResponse being used under the covers.

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 Jakarta Faces 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 Faces versions, you will get an IllegalStateException with a message like getoutputstream() has already been called for this response when the Faces implementation calls getWriter() to render HTML.

Turn off Ajax!

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 fired by <f:ajax>/<h:commandScript> are handled by JavaScript which in turn has, due to security reasons, by default 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. The <p:remoteCommand> can also not be used. In case you're using ICEfaces, then you need to nest a <f:ajax disabled="true" /> in the command component.

In case you're using PrimeFaces 10 or newer, then you can use <p:fileDownload> to simulate Ajax-like behavior when downloading a file, with onstart, update, oncomplete and all.

Generic action method example

<h:commandButton value="Download" action="#{bean.download}" />
public void download() throws IOException {
    String fileName = ...;
    String contentType = ...;
    long contentLength = ...;

    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();

    externalContext.responseReset(); // Some Faces 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.
    externalContext.setResponseContentType(contentType); // Check https://www.iana.org/assignments/media-types for all types.
    externalContext.setResponseContentLengthLong(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.
    externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want.

    OutputStream output = externalContext.getResponseOutputStream();

    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    facesContext.responseComplete(); // Important! Otherwise Faces 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 = externalContext.getMimeType(fileName);
    long contentLength = 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 Faces utility library OmniFaces, then you can use one of the three convenient Faces#sendFile() methods taking either Path, File, InputStream, or byte[], and specifying whether the file should be downloaded as an attachment (true) or inline (false).

public void download() throws IOException {
    Faces.sendFile(path, 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.

七秒鱼° 2025-01-15 18:01:44
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 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();

}
自由如风 2025-01-15 18:01:44

这对我有用:

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

This is what worked for me:

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();
}
旧人哭 2025-01-15 18:01:44

这是我的解决方案,BalusC 的答案的扩展

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

This is my solution, an extension of 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();
}
懒的傷心 2025-01-15 18:01:44

这是完整的代码片段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();
   }
 }

如果您希望在运行时生成文件,您可以更改文件读取逻辑。

here is the complete code snippet 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();
   }
 }

You may change the file reading logic in case you want file to get generated at runtime.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文