Un de nos fichiers de fuites d'application gère et nous n'avons pas encore trouvé la cause de cela.
Dans le code, je peux voir plusieurs fonctions similaires à ceci:
public ResponseEntity<InputStreamResource> getFoo( ... ) {
InputStream content = getContent(...)
InputStreamResource isr = new InputStreamResource(content);
return ResponseEntity.status(HttpServletResponse.SC_OK).body(isr);
}
(if
vérifie et try
/catch
supprimés par souci de concision)
Je suis sûr que cette section provoque le problème car lorsque je teste ce code spécifique avec JMeter, je peux voir que getContent()
échoue à cette étape:
is = Files.newInputStream(f.toPath());
Normalement, je ferme le InputStream
mais parce que ce code court et simple, je ne peux pas fermer le flux avant return
ou l'appel de body
.
Lorsque j'exécute lsof
(le code s'exécute sur Linux), je peux voir que des milliers de fichiers sont ouverts en mode lecture. Je suis donc sûr que ce problème est dû au fait que le flux ne se ferme pas.
Existe-t-il un code de bonnes pratiques que je devrais échanger?
vous pouvez essayer d'utiliser StreamingResponseBody
StreamingResponseBody
Un type de valeur de retour de méthode de contrôleur pour le traitement de demande asynchrone où l'application peut écrire directement dans la réponse OutputStream sans tenir le thread du conteneur Servlet.
Étant donné que vous travaillez sur un thread distinct, en écrivant directement dans la réponse, votre problème pour appeler close()
avant que return
soit résolu.
vous pouvez probablement commencer par l'exemple suivant
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);
}
De plus, au lieu d'utiliser InputStream, vous pouvez également essayer d'utiliser Files
(puisque Java 7)
vous n'avez donc pas à gérer InputStream
File file = new File("/path/to/example/file");
StreamingResponseBody responseBody = outputStream -> {
Files.copy(file.toPath(), outputStream);
};
Vous pouvez refactoriser toutes vos méthodes de contrôleur qui lisent les fichiers locaux et définissez leur contenu comme le corps de la réponse HTTP:
Au lieu d'utiliser l'approche ResponseEntity
, vous injectez le HttpServletResponse
sous-jacent et copiez les octets du flux d'entrée renvoyé par votre méthode getContent(...)
vers le flux de sortie du HttpServletResponse
, par exemple en utilisant les méthodes utilitaires liées aux IO d'Apache CommonsIO ou de la bibliothèque Google Guava. Dans tous les cas, assurez-vous de fermer le flux d'entrée! Le code ci-dessous le fait implicitement en utilisant une instruction 'try-with-resources' qui ferme le flux d'entrée déclaré à la fin de l'instruction.
@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();
}
}
référence:
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
(surtout regardez les méthodes public static int copy(InputStream input, OutputStream output) throws IOException
et public static int copyLarge(InputStream input, OutputStream output) throws IOException
de la classe org.Apache.commons.io.IOUtils
)
En supposant que vous utilisez Spring, votre méthode pourrait renvoyer un Resource et laisser Spring gérer le reste (y compris la fermeture du flux sous-jacent). Il y a peu d'implémentations de Resource sont disponibles dans Spring API ou bien vous devez implémenter la vôtre. En fin de compte, votre méthode deviendrait simple et voudrait quelque chose comme ci-dessous
public ResponseEntity<Resource> getFo0(...) {
return new InputStreamResource(<Your input stream>);
}
Parce que ce InputStream
provient essentiellement d'un simple fichier, un bon remplacement est ce code:
FileSystemResource fsr = new FileSystemResource(fileName);
return ResponseEntity.status(HttpServletResponse.SC_OK).body(fsr);
FileSystemResource
peut prendre un Java.util.File
, une Java.nio.file.Path
ou même un String
pointant vers le fichier correspondant.