web-dev-qa-db-ja.com

JSFバッキングBeanからファイルをダウンロードするにはどうすればよいですか?

JSFバッキングBeanアクションメソッドからファイルのダウンロードを提供する方法はありますか?私は多くのことを試しました。主な問題は、ファイルの内容を書き込むために応答のOutputStreamを取得する方法を理解できないことです。 Servletを使用してそれを行う方法は知っていますが、これはJSFフォームから呼び出すことはできず、新しいリクエストが必要です。

現在のOutputStreamから応答のFacesContextを取得するにはどうすればよいですか?

85
zmeda

前書き

ExternalContext ですべてを取得できます。 JSF 1.xでは、 ExternalContext#getResponse() で生のHttpServletResponseオブジェクトを取得できます。 JSF 2.xでは、JSFフードの下からHttpServletResponseを取得する必要なく、 ExternalContext#getResponseOutputStream() のような一連の新しいデリゲートメソッドを使用できます。

応答で、Content-Typeヘッダーを設定して、提供されたファイルに関連付けるアプリケーションをクライアントが認識できるようにする必要があります。また、クライアントがダウンロードの進行状況を計算できるようにContent-Lengthヘッダーを設定する必要があります。そうしないと、不明になります。また、Save Asダイアログが必要な場合はContent-Dispositionヘッダーをattachmentに設定する必要があります。設定しない場合、クライアントはインラインで表示しようとします。最後に、ファイルのコンテンツを応答出力ストリームに書き込みます。

最も重要な部分は、 FacesContext#responseComplete() を呼び出して、応答にファイルを書き込んだ後、ナビゲーションとレンダリングを実行してはならないことをJSFに通知することです。そうしないと、応答の終わりがページ、または古いJSFバージョンでは、JSF実装がgetoutputstream() has already been called for this responseを呼び出してHTMLをレンダリングすると、getWriter()のようなメッセージを含むIllegalStateExceptionを取得します。

Ajaxをオフにする/リモートコマンドを使用しないでください!

アクションメソッドがnotajaxリクエストによって呼び出されることを確認する必要がありますが、<h:commandLink>で起動すると通常のリクエストによって呼び出されます。および<h:commandButton>。 AjaxリクエストとリモートコマンドはJavaScriptによって処理されますが、セキュリティ上の理由により、JavaScriptはajaxレスポンスのコンテンツとSave Asダイアログを強制する機能を持ちません。

あなたが使用している場合などPrimeFaces <p:commandXxx>、その後、ajax="false"属性を介して明示的にajaxをオフにする必要があります。 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などの動的に生成されたファイルをストリーミングする必要がある場合は、使用するAPIがoutputを想定している場所に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 を使用している場合、3つの便利な Faces#sendFile() メソッドのいずれかを使用して、File、またはInputStream、またはbyte[]を指定し、ファイルを添付ファイル(true)またはインライン(false)としてダウンロードするかどうか。

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

はい、このコードはそのままです。 responseComplete()などを自分で呼び出す必要はありません。このメソッドは、IE固有のヘッダーとUTF-8ファイル名も適切に処理します。 ソースコードはこちら にあります。

216
BalusC
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();

}
4
John Mendes

これは私のために働いたものです:

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