JSF 백업 빈에서 파일 다운로드를 제공하는 방법은 무엇입니까?


91

JSF backing bean action 메소드에서 파일 다운로드를 제공하는 방법이 있습니까? 나는 많은 것을 시도했다. 주요 문제는 OutputStream파일 내용을 쓰기 위해 응답 을 얻는 방법을 알 수 없다는 것입니다. 을 사용하여 수행하는 방법을 알고 Servlet있지만 JSF 양식에서 호출 할 수 없으며 새 요청이 필요합니다.

OutputStream현재 응답을 어떻게 얻을 수 FacesContext있습니까?

답변:


238

소개

을 통해 모든 것을 얻을 수 있습니다 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 responsegetWriter()

ajax를 끄고 원격 명령을 사용하지 마십시오!

당신은 액션 메소드가되어 있는지 확인 할 필요가 없습니다 아약스 요청에 의해 호출하지만, 당신이 화재로는 보통의 요청에 의해 호출되는 것을 <h:commandLink>하고 <h:commandButton>. Ajax 요청 및 원격 명령은 JavaScript에 의해 처리되며 보안상의 이유로 ajax 응답의 내용으로 다른 이름 으로 저장 대화 를 강제하는 기능이 없습니다 .

예를 들어 PrimeFaces를 사용하는 경우 속성을 <p:commandXxx>통해 명시 적으로 ajax를 해제해야 ajax="false"합니다. ICEfaces를 사용하는 <f:ajax disabled="true" />경우 명령 구성 요소에 를 중첩해야합니다 .

일반 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.
}

일반 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.
}

일반적인 정적 파일 예

로컬 디스크 파일 시스템에서 정적 파일을 스트리밍해야하는 경우 아래 코드로 대체하십시오.

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()방법 중 하나를 사용할 수 있습니다 .FileInputStreambyte[]truefalse

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

예,이 코드는있는 그대로 완전합니다. 호출 할 필요가 없습니다 responseComplete(). 이 방법은 또한 IE 특정 헤더 및 UTF-8 파일 이름을 올바르게 처리합니다. 여기에서 소스 코드 를 찾을 수 있습니다 .


1
너무 쉽게! 나는 그것이 필요하기 때문에, 자신의 쇼케이스에 따라 PrimeFaces에 대한 다운로드를 사용할 수 있도록하는 방법을 궁금했는데 InputStream인프라를 p:fileDownload, 나는 변환하는 방법을 관리하지 OutputStreamInputStream. 이제 액션 리스너도 응답 콘텐츠 유형을 변경할 수 있으며 응답은 어쨌든 사용자 에이전트 측에서 파일 다운로드로 간주됩니다. 감사합니다!
Lyubomyr Shaydariv 2011

1
HTTP POST (h : commandButton 및 h : commandLink) 대신 HTTP GET을 사용하여이를 수행하는 방법이 있습니까?
Alfredo Osorio 2013

@Alfredo : 예, preRenderView마크 업없는보기에서 리스너를 사용 합니다. JSON 다운로드 (음, 제공)에 대한 유사한 질문에 대한 답변이 여기에 있습니다. stackoverflow.com/questions/8358006/…
BalusC

w3schools.com/media/media_mimeref.asp 링크가 끊어졌습니다. 어쩌면이 사람은 적합 : iana.org/assignments/media-types
Zakhar

2
@BalusC 당신은 모든 jsf 주제를 다루고 있습니다-내 인생을 더 쉽게 만들어 주셔서 감사합니다!
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

이것은 나를 위해 일한 것입니다.

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
2 근무일 후에 약간의 변경으로 내 문제가 해결되었습니다.) 대단히 감사합니다.
ÖMER TAŞCI

@ ÖMERTAŞCI : 어떤 변화,
Kukeltje

-3

여기에 완전한 코드 스 니펫이 있습니다. 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();
   }
 }

런타임에 파일을 생성하려는 경우 파일 읽기 논리를 변경할 수 있습니다.


1024 바이트보다 큰 경우 입력 파일의 일부만 가져옵니다!
hinneLinks
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.