web-dev-qa-db-ja.com

ResponseEntityを使用してストリーミングし、InputStreamを確実に閉じる適切な方法

アプリケーションの1つがファイルハンドルをリークし、その原因はまだ判明していません。

コードには、次のようないくつかの関数があります。

_public ResponseEntity<InputStreamResource> getFoo( ... ) {
    InputStream content = getContent(...)
    InputStreamResource isr = new InputStreamResource(content);
    return ResponseEntity.status(HttpServletResponse.SC_OK).body(isr);
}
_

ifチェックおよびtry/catch簡略化のために削除)

この特定のコードをJMeterでロードテストすると、この段階でgetContent()が失敗することがわかるので、このセクションで問題が発生すると確信しています。

_is = Files.newInputStream(f.toPath());
_

通常はInputStreamを閉じますが、この短くて単純なコードのため、returnまたはbodyの呼び出しの前にストリームを閉じることはできません。

lsofを実行すると(コードはLinuxで実行されます)、何千ものファイルが読み取りモードで開いていることがわかります。したがって、この問題はストリームが閉じられないことが原因であると確信しています。

下取りすべきベストプラクティスコードはありますか?

11
Marged

あなたはStreamingResponseBodyを使用しようとすることができます

StreamingResponseBody

アプリケーションがサーブレットコンテナースレッドを保持せずに応答OutputStreamに直接書き込むことができる非同期要求処理のコントローラーメソッドの戻り値の型。

別のスレッドで作業していて、応答に直接書き込むため、returnの前にclose()を呼び出すという問題は解決されます。

おそらく次の例から始めることができます

public ResponseEntity<StreamingResponseBody> export(...) throws FileNotFoundException {
    //...

    InputStream inputStream = new FileInputStream(new File("/path/to/example/file"));


    StreamingResponseBody responseBody = outputStream -> {

        int numberOfBytesToWrite;
        byte[] data = new byte[1024];
        while ((numberOfBytesToWrite = inputStream.read(data, 0, data.length)) != -1) {
            System.out.println("Writing some bytes..");
            outputStream.write(data, 0, numberOfBytesToWrite);
        }

        inputStream.close();
    };

    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=generic_file_name.bin")
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(responseBody);
}

さらに、InputStreamを使用する代わりに、Filesを使用することもできます(Java 7以降))

InputStreamを管理する必要はありません

    File file = new File("/path/to/example/file");

    StreamingResponseBody responseBody = outputStream -> {
        Files.copy(file.toPath(), outputStream);
    };
4
ValerioMC

ローカルファイルを読み取り、その内容をHTTP応答の本文として設定するすべてのコントローラーメソッドをリファクタリングできます。

ResponseEntityアプローチを使用する代わりに、基礎となるHttpServletResponseを注入し、getContent(...)メソッドから返された入力ストリームのバイトをHttpServletResponseの出力ストリームにコピーします、例えばApache CommonsIOまたはGoogle GuavaライブラリのIO関連のユーティリティメソッドを使用する。いずれにせよ、必ず入力ストリームを閉じてください!以下のコードは、宣言された入力ストリームをステートメントの最後で閉じる「try-with-resources」ステートメントを使用してこれを暗黙的に実行します。

_@RequestMapping(value="/foo", method=RequestMethod.GET)
public void getFoo(HttpServletResponse response) {
    // use Java7+ try-with-resources
    try (InputStream content = getContent(...)) {

        // if needed set content type and attachment header
        response.addHeader("Content-disposition", "attachment;filename=foo.txt");
        response.setContentType("txt/plain");

        // copy content stream to the HttpServletResponse's output stream
        IOUtils.copy(myStream, response.getOutputStream());

        response.flushBuffer();
    }
}
_

参照:

https://docs.Oracle.com/javase/7/docs/api/Java/io/InputStream.htmlhttps://docs.Oracle.com/javase/7/ docs/api/Java/lang/AutoCloseable.htmlhttps://docs.Oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.htmlhttps:// google.github.io/guava/releases/19.0/api/docs/com/google/common/io/ByteStreams.htmlhttps://commons.Apache.org/proper/commons-io/ javadocs/api-release/index.html

(特に、クラス_org.Apache.commons.io.IOUtils_のメソッドpublic static int copy(InputStream input, OutputStream output) throws IOExceptionおよびpublic static int copyLarge(InputStream input, OutputStream output) throws IOExceptionを見てください)

Springを使用していると仮定すると、メソッドは Resource を返し、残りの部分(基になるストリームのクローズを含む)をSpringに処理させることができます。 Spring API内で使用できるResourceの実装はほとんどありません または、独自に実装する必要があります。結局、あなたの方法は単純になり、以下のようなものを望みます

public ResponseEntity<Resource> getFo0(...) {
    return new InputStreamResource(<Your input stream>);
}
2
Stackee007

このInputStreamは基本的に単純なファイルからのものであるため、次のコードが適切な置き換えです。

FileSystemResource fsr = new FileSystemResource(fileName);
return ResponseEntity.status(HttpServletResponse.SC_OK).body(fsr);

FileSystemResourceJava.util.FileJava.nio.file.PathまたはStringでさえ、関連ファイルを指します。

0
Marged