1. ホーム
  2. jsf

[解決済み] JSFバッキングBeanからファイルのダウンロードを提供する方法は?

2023-03-06 12:31:55

質問

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

どうすれば OutputStream からレスポンスの FacesContext ?

どのように解決するのですか?

はじめに

あなたは、すべてのものを ExternalContext . JSF 1.xでは、生の HttpServletResponse オブジェクトを ExternalContext#getResponse() . JSF 2.xでは、以下のような新しいデリゲートメソッドの束を使うことができます。 ExternalContext#getResponseOutputStream() のような新しいデリゲートメソッドを使うことができます。 HttpServletResponse を取得することなく、JSFのフードの下にある

レスポンス上では Content-Type ヘッダを設定し、クライアントがどのアプリケーションが提供されたファイルに関連づけられるかを知ることができるようにします。そして Content-Length ヘッダを設定し、クライアントがダウンロードの進捗を計算できるようにします(そうしないと、不明になってしまいます)。そして Content-Disposition ヘッダを attachment が必要な場合は として保存 ダイアログが必要な場合、そうでなければ、クライアントはそれをインラインで表示しようとします。最後に、ファイルの内容をレスポンス出力ストリームに書き込むだけです。

最も重要な部分は FacesContext#responseComplete() を呼び出して、JSFに、レスポンスにファイルを書き込んだ後はナビゲーションとレンダリングを実行しないように通知します。そうしないと、レスポンスの最後がページのHTMLコンテンツで汚染されるか、古いJSFのバージョンでは IllegalStateException のようなメッセージとともに getoutputstream() has already been called for this response を呼び出すと、JSF の実装は getWriter() を呼び出してHTMLをレンダリングします。

ajaxをオフにする/リモートコマンドを使用しない!

あなたはただ、アクションメソッドが ではなく によって呼び出されるのではなく、通常のリクエストによって呼び出されることを確認するだけです。 <h:commandLink><h:commandButton> . Ajax リクエストとリモートコマンドは JavaScript で処理され、JavaScript にはセキュリティ上の理由から、強制的に として保存 ダイアログを表示させる機能がありません。

PrimeFacesなどを使っている場合は <p:commandXxx> を使用している場合、以下のように明示的に ajax をオフにする必要があります。 ajax="false" 属性で明示的にオフにする必要があります。ICEfaces を使用している場合は、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 を指定します。 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のユーティリティ・ライブラリである オムニフェーズ を使用しているのであれば、3つの便利な Faces#sendFile() メソッドを使うことができます。 File または InputStream または byte[] で、添付ファイルとしてダウンロードするかどうかを指定する ( true ) か、インライン ( false ).

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

はい、このコードはそのままで完全です。を呼び出す必要はありません。 responseComplete() などを自分で呼び出す必要はありません。この方法は、IE特有のヘッダやUTF-8のファイル名にも適切に対応します。を見つけることができます。 ソースコードはこちら .