アプリケーションの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で実行されます)、何千ものファイルが読み取りモードで開いていることがわかります。したがって、この問題はストリームが閉じられないことが原因であると確信しています。
下取りすべきベストプラクティスコードはありますか?
あなたは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);
};
ローカルファイルを読み取り、その内容を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>);
}
このInputStream
は基本的に単純なファイルからのものであるため、次のコードが適切な置き換えです。
FileSystemResource fsr = new FileSystemResource(fileName);
return ResponseEntity.status(HttpServletResponse.SC_OK).body(fsr);
FileSystemResource
はJava.util.File
、Java.nio.file.Path
またはString
でさえ、関連ファイルを指します。