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 HttpServletResponse
từ bên dưới các phần mềm JSF.
Trên phản hồi, bạn nên đặt Content-Type
tiê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-Length
tiê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-Disposition
tiêu đề thành attachment
nế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 IllegalStateException
một thông báo như getoutputstream() has already been called for this response
khi 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>
và <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();
ec.setResponseContentType(contentType);
ec.setResponseContentLength(contentLength);
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
OutputStream output = ec.getResponseOutputStream();
fc.responseComplete();
}
Ví dụ chung về JSF 1.x
public void download() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();
response.reset();
response.setContentType(contentType);
response.setContentLength(contentLength);
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
OutputStream output = response.getOutputStream();
fc.responseComplete();
}
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();
document.close();
Ví dụ: Apache POI HSSF:
String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-excel";
HSSFWorkbook workbook = new HSSFWorkbook();
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 .
InputStream
cơ sở hạ tầngp:fileDownload
và tôi chưa quản lý cách chuyển đổiOutputStream
sangInputStream
. 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!