JSF backing bean action 메소드에서 파일 다운로드를 제공하는 방법이 있습니까? 나는 많은 것을 시도했다. 주요 문제는 OutputStream
파일 내용을 쓰기 위해 응답 을 얻는 방법을 알 수 없다는 것입니다. 을 사용하여 수행하는 방법을 알고 Servlet
있지만 JSF 양식에서 호출 할 수 없으며 새 요청이 필요합니다.
OutputStream
현재 응답을 어떻게 얻을 수 FacesContext
있습니까?
답변:
을 통해 모든 것을 얻을 수 있습니다 ExternalContext
. JSF의 1.x에서, 당신은 원시 얻을 수 HttpServletResponse
에 의해 개체를 ExternalContext#getResponse()
. JSF 2.x에서는 JSF 후드 아래에서 ExternalContext#getResponseOutputStream()
가져올 필요없이 새로운 델리게이트 메서드를 사용할 수 있습니다 HttpServletResponse
.
응답 Content-Type
에서 클라이언트가 제공된 파일과 연결할 애플리케이션을 알 수 있도록 헤더를 설정해야 합니다. 그리고 Content-Length
클라이언트가 다운로드 진행률을 계산할 수 있도록 헤더를 설정해야합니다 . 그렇지 않으면 알 수 없습니다. 그리고 다른 이름 으로 저장 대화 상자 를 원하면 Content-Disposition
헤더를로 설정해야합니다 . 그렇지 않으면 클라이언트가 인라인으로 표시하려고합니다. 마지막으로 응답 출력 스트림에 파일 내용을 작성하십시오.attachment
가장 중요한 부분은 FacesContext#responseComplete()
응답에 파일을 작성한 후 탐색 및 렌더링을 수행하지 않아야한다고 JSF에 알리기 위해 호출 하는 것입니다. 그렇지 않으면 응답의 끝이 페이지의 HTML 콘텐츠 또는 이전 JSF 버전으로 오염됩니다. , JSF 구현이 HTML 렌더링을 호출 할 때 IllegalStateException
와 같은 메시지가 표시됩니다.getoutputstream() has already been called for this response
getWriter()
당신은 액션 메소드가되어 있는지 확인 할 필요가 없습니다 아약스 요청에 의해 호출하지만, 당신이 화재로는 보통의 요청에 의해 호출되는 것을 <h:commandLink>
하고 <h:commandButton>
. Ajax 요청 및 원격 명령은 JavaScript에 의해 처리되며 보안상의 이유로 ajax 응답의 내용으로 다른 이름 으로 저장 대화 를 강제하는 기능이 없습니다 .
예를 들어 PrimeFaces를 사용하는 경우 속성을 <p:commandXxx>
통해 명시 적으로 ajax를 해제해야 ajax="false"
합니다. 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 를 사용 하는 경우 ,, 또는 a 를 사용하여 파일을 첨부 파일 ( ) 또는 인라인 ( ) 으로 다운로드할지 여부를 지정 하는 세 가지 편리한 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
. 이제 액션 리스너도 응답 콘텐츠 유형을 변경할 수 있으며 응답은 어쨌든 사용자 에이전트 측에서 파일 다운로드로 간주됩니다. 감사합니다!