Spring Controllerからファイルをダウンロードする


387

WebサイトからPDFをダウンロードする必要があるという要件があります。PDFはコード内で生成する必要があります。これは、フリーマーカーとiTextのようなPDF生成フレームワークの組み合わせになると思いました。もっと良い方法は?

ただし、私の主な問題は、ユーザーがSpring Controllerを介してファイルをダウンロードできるようにする方法です。


2
Spring Frameworkは2011年から大幅に変更されたので、リアクティブな方法で変更できることにも言及する価値があります。ここに例を示します
Krzysztof Skrzynecki

回答:


397
@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
public void getFile(
    @PathVariable("file_name") String fileName, 
    HttpServletResponse response) {
    try {
      // get your file as InputStream
      InputStream is = ...;
      // copy it to response's OutputStream
      org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
      response.flushBuffer();
    } catch (IOException ex) {
      log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
      throw new RuntimeException("IOError writing file to output stream");
    }

}

一般的に言って、がある場合response.getOutputStream()、そこに何でも書き込むことができます。この出力ストリームを、生成されたPDFをジェネレーターに配置する場所として渡すことができます。また、送信するファイルの種類がわかっている場合は、

response.setContentType("application/pdf");

4
これは私が言おうとしていたことのほとんどですが、おそらく応答タイプのヘッダーもファイルに適したものに設定する必要があります。
GaryF 2011

2
はい、投稿を編集しました。さまざまなファイルの種類を生成したので、拡張子に基づいてファイルのコンテンツの種類を判断するのはブラウザに任せました。
Infeligo

投稿のおかげで、flushBufferを忘れました。なぜ私の鉱山が機能しなかったのかわかりました:-)
Jan Vladimir Mostert '27年

35
IOUtilsSpringの代わりにApacheを使用する特別な理由はFileCopyUtils何ですか?
Powerlord 2012

3
ここでは、より良い解決策がある:stackoverflow.com/questions/16652760/...
ドミトロPlekhotkin

290

私は、SpringのResourceHttpMessageConverterの組み込みサポートを使用することで、これを合理化することができました。MIMEタイプを判別できる場合、これはcontent-lengthおよびcontent-typeを設定します

@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
@ResponseBody
public FileSystemResource getFile(@PathVariable("file_name") String fileName) {
    return new FileSystemResource(myService.getFileFor(fileName)); 
}

10
これは機能します。しかし、ファイル(.csvファイル)はブラウザーに表示され、ダウンロードされません。ブラウザーを強制的にダウンロードするにはどうすればよいですか?
chzbrgla 2013

41
生成した= MediaType.APPLICATION_OCTET_STREAM_VALUEを@RequestMappingに追加してダウンロードを強制できます
David Kago

8
また、<bean class = "org.springframework.http.converter.ResourceHttpMessageConverter" />をmessageConvertersリスト(<mvc:annotation-driven> <mvc:message-converters>)に追加する必要があります
Sllouyssgort 2013年

4
Content-Dispositionこの方法でヘッダーを設定する方法はありますか?
ラルフ

8
必要はありませんでしたが、メソッドにパラメーターとしてHttpResponseを追加し、次に「response.setHeader( "Content-Disposition"、 "attachment; filename = somefile.pdf");」を追加できると思います。
スコットカールソン

82

応答に直接ファイルを書き込むことができるはずです。何かのようなもの

response.setContentType("application/pdf");      
response.setHeader("Content-Disposition", "attachment; filename=\"somefile.pdf\""); 

次に、ファイルをバイナリストリームとしてに書き込みますresponse.getOutputStream()response.flush()最後に忘れずに実行してください。


8
'Spring'でコンテンツタイプをこのように設定しないのですか?@RequestMapping(value = "/foo/bar", produces = "application/pdf")
ブラック

4
@Francisアプリケーションが異なる種類のファイルをダウンロードした場合はどうなりますか?Lobster1234の回答では、コンテンツの処理を動的に設定できます。
ローズ

2
それは本当の@Roseですが、フォーマットごとに異なるエンドポイントを定義するほうがよいと思います
Black

3
スケーラブルではないので、そうは思いません。現在、12種類のリソースをサポートしています。ユーザーが何をアップロードしたいかに基づいて、より多くのファイルタイプをサポートする可能性があります。私見では、ダウンロードのエンドポイントは1つだけで、さまざまな種類のファイルを処理できます。@フランシス
ローズ

3
それは絶対に「スケーラブル」ですが、それがベストプラクティスであるかどうかに同意しないことに同意することができます
Black

74

Spring 3.0では、HttpEntityreturnオブジェクトを使用できます。これを使用すると、コントローラーにHttpServletResponseオブジェクトが不要になるため、テストが容易になります。 これを除いて、この答えはInfeligoの答えと相対的に等しくなります。

PDFフレームワークの戻り値がバイト配列の場合(他の戻り値については私の回答の2番目の部分を読んでください)

@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
                 @PathVariable("fileName") String fileName) throws IOException {

    byte[] documentBody = this.pdfFramework.createPdf(filename);

    HttpHeaders header = new HttpHeaders();
    header.setContentType(MediaType.APPLICATION_PDF);
    header.set(HttpHeaders.CONTENT_DISPOSITION,
                   "attachment; filename=" + fileName.replace(" ", "_"));
    header.setContentLength(documentBody.length);

    return new HttpEntity<byte[]>(documentBody, header);
}

PDFフレームワークの戻り値の型(documentBbody)がまだバイト配列ではない(そしてもないByteArrayInputStream)場合は、最初にバイト配列にしない方が賢明です。代わりに、それを使用することをお勧めします:

の例FileSystemResource

@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
                 @PathVariable("fileName") String fileName) throws IOException {

    File document = this.pdfFramework.createPdf(filename);

    HttpHeaders header = new HttpHeaders();
    header.setContentType(MediaType.APPLICATION_PDF);
    header.set(HttpHeaders.CONTENT_DISPOSITION,
                   "attachment; filename=" + fileName.replace(" ", "_"));
    header.setContentLength(document.length());

    return new HttpEntity<byte[]>(new FileSystemResource(document),
                                  header);
}

11
-1これにより、メモリ内のファイル全体が不必要にロードされ、OutOfMemoryErrorsが簡単に発生する可能性があります。
Faisal Feroz 2014年

1
@FaisalFeroz:はい、これは正しいですが、ファイルドキュメントはメモリ内に作成されます(質問:「PDFをコード内で生成する必要がある」を参照してください)。とにかく-この問題を解決するあなたの解決策は何ですか?
ラルフ

1
応答httpステータスコードを指定できるHttpEntityのスーパーであるResponseEntityを使用することもできます。例:return new ResponseEntity<byte[]>(documentBody, headers, HttpStatus.CREATED)
Amr Mostafa 2014年

@Amr Mostafa:一方ResponseEntity、サブクラスですHttpEntity(ただし、取得します)201 CREATEDは、データのビューのみを返すときに使用するものではありません。(201 CREATED についてはw3.org/Protocols/rfc2616/rfc2616-sec10.htmlを参照)
Ralph

1
ファイル名の空白をアンダースコアに置き換える理由はありますか?引用符で囲んで実際の名前を送信できます。
Alexandru Severin

63

もし、あんたが:

  • byte[]応答に送信する前に、ファイル全体をにロードしたくない。
  • それが経由してダウンロード/送信したい/必要InputStream
  • 送信されるMIMEタイプとファイル名を完全に制御したい。
  • あなたの@ControllerAdviceために(またはそうではない)他のピックアップ例外があります。

以下のコードはあなたが必要とするものです:

@RequestMapping(value = "/stuff/{stuffId}", method = RequestMethod.GET)
public ResponseEntity<FileSystemResource> downloadStuff(@PathVariable int stuffId)
                                                                      throws IOException {
    String fullPath = stuffService.figureOutFileNameFor(stuffId);
    File file = new File(fullPath);
    long fileLength = file.length(); // this is ok, but see note below

    HttpHeaders respHeaders = new HttpHeaders();
    respHeaders.setContentType("application/pdf");
    respHeaders.setContentLength(fileLength);
    respHeaders.setContentDispositionFormData("attachment", "fileNameIwant.pdf");

    return new ResponseEntity<FileSystemResource>(
        new FileSystemResource(file), respHeaders, HttpStatus.OK
    );
}

ファイルの長さの部分について:File#length()一般的なケースでは十分なはずですが、遅くなる可能性があるため、この観察を行うと思いました。その場合、以前に(たとえば、DBに)保存しておく必要があります。ファイルが大きい場合、特にファイルがリモートシステムにある場合や、そのようなより詳細なものである場合-データベースなど。



InputStreamResource

リソースがファイルではない場合、たとえばDBからデータを取得する場合は、を使用する必要がありますInputStreamResource。例:

    InputStreamResource isr = new InputStreamResource(new FileInputStream(file));
    return new ResponseEntity<InputStreamResource>(isr, respHeaders, HttpStatus.OK);

FileSystemResourceクラスの使用についてアドバイスしませんか?
ステファン

実はFileSystemResourceそちらを使っても大丈夫だと思います。リソースがファイルの場合もお勧めです。このサンプルでFileSystemResourceは、どこにでも使用できますInputStreamResource
acdcjunior 2015

ファイル長計算の部分について:あなたが心配しているなら、心配しないでください。File#length()一般的なケースでは十分なはずです。ので、私はそれを言及し、それが遅くなることができないデータベース多分、?。 -ファイルがリモート・システムまたは複数のそのよう精緻なものである特別な場合には、しかし、それが問題になる場合(または、それが問題になる証拠がある場合)は心配する必要はありません。重要な点は、ファイルをストリーミングするための努力をしていることです。それ以前にすべてをプリロードする必要がある場合、ストリーミングは最終的には何の違いも生じません。
acdcjunior 2015年

上記のコードが機能しないのはなぜですか?0バイトのファイルをダウンロードします。ByteArray&ResourceMessageコンバーターが存在することを確認しました。何か不足していますか?
coding_idiot

なぜByteArray&ResourceMessageコンバーターについて心配しているのですか?
acdcjunior 2015年

20

このコードは、jspのリンクをクリックすると、Spring Controllerから自動的にファイルをダウンロードするように機能します。

@RequestMapping(value="/downloadLogFile")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
    try {
        String filePathToBeServed = //complete file name with path;
        File fileToDownload = new File(filePathToBeServed);
        InputStream inputStream = new FileInputStream(fileToDownload);
        response.setContentType("application/force-download");
        response.setHeader("Content-Disposition", "attachment; filename="+fileName+".txt"); 
        IOUtils.copy(inputStream, response.getOutputStream());
        response.flushBuffer();
        inputStream.close();
    } catch (Exception e){
        LOGGER.debug("Request could not be completed at this moment. Please try again.");
        e.printStackTrace();
    }

}

14

以下のコードは、テキストファイルを生成してダウンロードするのに役立ちました。

@RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity<byte[]> getDownloadData() throws Exception {

    String regData = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
    byte[] output = regData.getBytes();

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("charset", "utf-8");
    responseHeaders.setContentType(MediaType.valueOf("text/html"));
    responseHeaders.setContentLength(output.length);
    responseHeaders.set("Content-disposition", "attachment; filename=filename.txt");

    return new ResponseEntity<byte[]>(output, responseHeaders, HttpStatus.OK);
}

5

私がすぐに考えることができるのは、pdfを生成してコードからwebapp / downloads / <RANDOM-FILENAME> .pdfに保存し、HttpServletRequestを使用してこのファイルに転送することです。

request.getRequestDispatcher("/downloads/<RANDOM-FILENAME>.pdf").forward(request, response);

または、ビューリゾルバを次のように構成できる場合は、

  <bean id="pdfViewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"
              value="org.springframework.web.servlet.view.JstlView" />
    <property name="order" value=”2″/>
    <property name="prefix" value="/downloads/" />
    <property name="suffix" value=".pdf" />
  </bean>

その後、ただ戻ります

return "RANDOM-FILENAME";

1
2つのビューリゾルバーが必要な場合、リゾルバーの名前を返したり、コントローラーでそれを選択するにはどうすればよいですか?
azerafati 14年

3

次の解決策は私のために働きます

    @RequestMapping(value="/download")
    public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
        try {

            String fileName="archivo demo.pdf";
            String filePathToBeServed = "C:\\software\\Tomcat 7.0\\tmpFiles\\";
            File fileToDownload = new File(filePathToBeServed+fileName);

            InputStream inputStream = new FileInputStream(fileToDownload);
            response.setContentType("application/force-download");
            response.setHeader("Content-Disposition", "attachment; filename="+fileName); 
            IOUtils.copy(inputStream, response.getOutputStream());
            response.flushBuffer();
            inputStream.close();
        } catch (Exception exception){
            System.out.println(exception.getMessage());
        }

    }

2

以下のようなもの

@RequestMapping(value = "/download", method = RequestMethod.GET)
public void getFile(HttpServletResponse response) {
    try {
        DefaultResourceLoader loader = new DefaultResourceLoader();
        InputStream is = loader.getResource("classpath:META-INF/resources/Accepted.pdf").getInputStream();
        IOUtils.copy(is, response.getOutputStream());
        response.setHeader("Content-Disposition", "attachment; filename=Accepted.pdf");
        response.flushBuffer();
    } catch (IOException ex) {
        throw new RuntimeException("IOError writing file to output stream");
    }
}

PDFを表示するか、ここに例をダウンロードできます


1

それが誰かを助けるなら。Infeligoが受け入れた回答が示唆することを実行できますが、強制ダウンロードのコードにこの余分なビットを挿入するだけです。

response.setContentType("application/force-download");


0

私の場合、オンデマンドでファイルを生成しているため、URLも生成する必要があります。

私にとってはそのようなものを働きます:

@RequestMapping(value = "/files/{filename:.+}", method = RequestMethod.GET, produces = "text/csv")
@ResponseBody
public FileSystemResource getFile(@PathVariable String filename) {
    String path = dataProvider.getFullPath(filename);
    return new FileSystemResource(new File(path));
}

非常に重要なのはMIMEタイプの入力でproducesあり、ファイル名はリンクの一部であるため、を使用する必要があります@PathVariable

HTMLコードは次のようになります。

<a th:href="@{|/dbreport/files/${file_name}|}">Download</a>

${file_name}コントローラーのThymeleafによって生成される場所は、つまり、result_20200225.csvです。そのため、リンク全体のURLは次のようになりexample.com/aplication/dbreport/files/result_20200225.csvます。

リンクブラウザーをクリックした後、ファイルをどうするかを尋ねられます-保存または開く。

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