Làm cách nào để cung cấp tải xuống tệp từ một bean sao lưu JSF?


91

Có cách nào cung cấp tải xuống tệp từ phương thức hành động bean hỗ trợ JSF không? Tôi đã thử rất nhiều thứ. Vấn đề chính là tôi không thể tìm cách lấy OutputStreamphản hồi để ghi nội dung tệp vào. Tôi biết cách làm điều đó với a Servlet, nhưng điều này không thể được gọi từ biểu mẫu JSF và yêu cầu một yêu cầu mới.

Làm thế nào tôi có thể nhận được OutputStreamphản hồi từ hiện tại FacesContext?

Câu trả lời:


238

Giới thiệu

Bạn có thể vượt qua mọi thứ ExternalContext. Trong JSF 1.x, bạn có thể lấy HttpServletResponseđối tượng thô bằng cách ExternalContext#getResponse(). Trong JSF 2.x, bạn có thể sử dụng một loạt các phương thức ủy nhiệm mới ExternalContext#getResponseOutputStream()mà không cần phải lấy HttpServletResponsetừ bên dưới các phần mềm JSF.

Trên phản hồi, bạn nên đặt Content-Typetiêu đề để khách hàng biết ứng dụng nào sẽ liên kết với tệp được cung cấp. Và, bạn nên đặt Content-Lengthtiêu đề để máy khách có thể tính toán tiến trình tải xuống, nếu không sẽ không xác định được. Và, bạn nên đặt Content-Dispositiontiêu đề thành attachmentnếu bạn muốn có hộp thoại Lưu dưới dạng , nếu không ứng dụng khách sẽ cố gắng hiển thị nội tuyến. Cuối cùng chỉ cần ghi nội dung tệp vào luồng đầu ra phản hồi.

Phần quan trọng nhất là gọi FacesContext#responseComplete()để thông báo cho JSF rằng nó không nên thực hiện điều hướng và hiển thị sau khi bạn đã ghi tệp vào phản hồi, nếu không phần cuối của phản hồi sẽ bị ô nhiễm với nội dung HTML của trang hoặc trong các phiên bản JSF cũ hơn , bạn sẽ nhận được IllegalStateExceptionmột thông báo như getoutputstream() has already been called for this responsekhi triển khai JSF gọi getWriter()để hiển thị HTML.

Tắt ajax / không sử dụng lệnh từ xa!

Bạn chỉ cần đảm bảo rằng phương thức hành động không được gọi bởi một yêu cầu ajax, mà nó được gọi bởi một yêu cầu bình thường khi bạn kích hoạt với <h:commandLink><h:commandButton>. Các yêu cầu Ajax và các lệnh từ xa được JavaScript xử lý, do đó, vì lý do bảo mật, không có cơ sở nào để buộc đối thoại Lưu dưới dạng với nội dung của phản hồi ajax.

Trong trường hợp bạn đang sử dụng ví dụ như PrimeFaces <p:commandXxx>, thì bạn cần đảm bảo rằng bạn đã tắt ajax qua ajax="false"thuộc tính một cách rõ ràng . Trong trường hợp bạn đang sử dụng ICEfaces, thì bạn cần lồng một dấu <f:ajax disabled="true" />vào thành phần lệnh.

Ví dụ chung về 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.
}

Ví dụ chung về 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.
}

Ví dụ về tệp tĩnh phổ biến

Trong trường hợp bạn cần truyền tệp tĩnh từ hệ thống tệp đĩa cục bộ, hãy thay thế mã như sau:

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

Ví dụ về tệp động phổ biến

Trong trường hợp bạn cần phát trực tuyến tệp được tạo động, chẳng hạn như PDF hoặc XLS, thì chỉ cần cung cấp outputở đó nơi API đang được sử dụng mong đợi OutputStream.

Ví dụ: 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();

Ví dụ: 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();

Lưu ý rằng bạn không thể đặt độ dài nội dung ở đây. Vì vậy, bạn cần xóa dòng để thiết lập độ dài nội dung phản hồi. Về mặt kỹ thuật, điều này không có vấn đề gì, điều bất lợi duy nhất là người dùng cuối sẽ có tiến trình tải xuống không xác định. Trong trường hợp điều này là quan trọng, thì bạn thực sự cần phải ghi vào một tệp cục bộ (tạm thời) trước và sau đó cung cấp nó như được hiển thị trong chương trước.

Phương thức tiện ích

Nếu bạn đang sử dụng thư viện tiện ích JSF OmniFaces , thì bạn có thể sử dụng một trong ba Faces#sendFile()phương pháp thuận tiện lấy a File, hoặc an InputStream, hoặc a byte[]và chỉ định xem tệp nên được tải xuống dưới dạng tệp đính kèm ( true) hay inline ( false).

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

Có, mã này hoàn chỉnh như hiện tại. Bạn không cần phải gọi responseComplete()và cứ như vậy cho mình. Phương pháp này cũng xử lý đúng các tiêu đề cụ thể của IE và tên tệp UTF-8. Bạn có thể tìm thấy mã nguồn ở đây .


1
Quá dễ! Tôi đã tự hỏi làm thế nào để cung cấp bản tải xuống cho PrimeFaces theo bản giới thiệu của họ, bởi vì nó yêu cầu InputStreamcơ sở hạ tầng p:fileDownloadvà tôi chưa quản lý cách chuyển đổi OutputStreamsang InputStream. Bây giờ rõ ràng là ngay cả một trình nghe hành động cũng có thể thay đổi loại nội dung phản hồi và sau đó phản hồi sẽ được coi là tệp tải xuống ở phía tác nhân người dùng. Cảm ơn bạn!
Lyubomyr Shaydariv

1
Có cách nào để thực hiện việc này bằng cách sử dụng HTTP GET thay vì HTTP POST (h: commandButton và h: commandLink) không?
Alfredo Osorio

@Alfredo: vâng, sử dụng trình preRenderViewnghe trong chế độ xem không đánh dấu. Câu hỏi tương tự để tải xuống (tốt, đang phân phát) JSON được trả lời tại đây: stackoverflow.com/questions/8358006/…
BalusC

liên kết w3schools.com/media/media_mimeref.asp bị hỏng. Có thể cái này phù hợp: iana.org/assignments/media-types
Zakhar

2
@BalusC bạn bao gồm mọi chủ đề jsf - cảm ơn bạn đã làm cho cuộc sống của tôi dễ dàng hơn thưa ông!
Buttinger Xaver,

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

}

3

Đây là những gì làm việc cho tôi:

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

1
Sau 2 ngày làm việc, điều này đã giải quyết được vấn đề của tôi với một chút thay đổi :) cảm ơn bạn rất nhiều.
ÖMER TAŞCI

@ ÖMERTAŞCI: Điều gì thay đổi,
Kukeltje

-3

đây là đoạn mã hoàn chỉnh 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();
   }
 }

Bạn có thể thay đổi logic đọc tệp trong trường hợp bạn muốn tệp được tạo trong thời gian chạy.


Điều này sẽ chỉ giúp bạn nhận được một phần của tệp đầu vào, nếu tệp lớn hơn 1024 byte đó!
hinneLinks
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.