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


90

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

どうすればOutputStream現在の応答を取得できFacesContextますか?

回答:


236

前書き

あなたはすべてを得ることができます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をオフにする/リモートコマンドを使用しない!

アクションメソッドがajaxリクエストによって呼び出されないことを確認するだけでよく、<h:commandLink>andで起動するときに通常のリクエストによって呼び出されることを確認する必要があります<h:commandButton>。Ajax要求とリモートコマンドはJavaScriptによって処理されますが、JavaScriptにはセキュリティ上の理由から、ajax応答のコンテンツを使用して[名前を付けて保存]ダイアログを強制する機能はありません。

たとえば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などの動的に生成されたファイルをストリーミングする必要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を、あなたは3つの便利なのいずれかを使用することができますFaces#sendFile()いずれかの服用方法File、またはInputStream、またはbyte[]、およびファイルが添付(としてダウンロードする必要があるかどうかを指定するtrue)またはインライン(false)。

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

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


1
とても簡単!PrimeFacesのショーケースに応じてダウンロードを利用できるようにする方法を考えていました。これには、のInputStreamインフラストラクチャが必要でありp:fileDownload、に変換OutputStreamする方法を管理できなかったためInputStreamです。これで、アクションリスナーでも応答のコンテンツタイプを変更でき、応答はユーザーエージェント側でのファイルダウンロードとして尊重されることは明らかです。ありがとうございました!
Lyubomyr Shaydariv 2012年

1
HTTP POST(h:commandButtonおよびh:commandLink)の代わりにHTTP GETを使用してこれを行う方法はありますか?
アルフレドオソリオ2013

@Alfredo:はい、preRenderViewマークアップのないビューでリスナーを使用します。ダウンロードするための同様の質問(も、サービス提供)JSONが、ここで回答されていますstackoverflow.com/questions/8358006/...
BalusC

w3schools.com/media/media_mimeref.aspリンクが壊れています。多分これは適切です:iana.org/assignments/media-types
Zakhar

2
@BalusCあなたはすべてのjsfトピックをカバーしています-私の人生をより簡単にしてくれてありがとう!
バティンガー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営業日後、これは私の変更点を少し変更して解決しました:)どうもありがとうございました。
オマールタスキ

@Ö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 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.