Ich arbeite an einer Spring-MVC-Anwendung, in der Benutzer Dateien herunterladen können. Die Benutzer können auf einen Anhang klicken, der einen Download-Mechanismus auslöst.Spring, Java: Streaming-Datei-Download zur Vermeidung von Speicherfehlern
Gestern, als mehrere Downloads und zwei davon ungefähr 2 GB-Dateien hatten, verursachte es einen Speichermangelfehler (log unten).
Um dieses Problem zu vermeiden, schien eine Möglichkeit, dieses Problem zu lösen, darin zu bestehen, die Download-Daten in Chunks zu streamen und diese Chunks nur in der Service-Schicht und nicht in der gesamten Datei zu verarbeiten.
Leider weiß ich nicht, wie man damit voran kommt, jede Hilfe wäre nett. Wenn diese Option nicht fliegen kann, gibt es Empfehlungen, wie Sie dieses Problem lösen können.
Fehlerprotokoll:
HTTP Status 500 - Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory
type Exception report
message Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory
description The server encountered an internal error that prevented it from fulfilling this request.
exception
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory
org.springframework.web.servlet.DispatcherServlet.triggerAfterCompletionWithError(DispatcherServlet.java:1303)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:977)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
Controller-Code:
@RequestMapping(value = "/download/attachment/{attachid}", method = RequestMethod.GET)
public void getAttachmentFromDatabase(@PathVariable("attachid") int attachid,
, HttpServletResponse response,) {
response.setContentType("application/octet-stream");
GroupAttachments groupAttachments = this.groupAttachmentsService.getAttachmenById(attachid);
response.setHeader("Content-Disposition", "attachment; filename=\"" + groupAttachments.getFileName() + "\"");
response.setContentLength(groupAttachments.getSendAttachment().length);
FileCopyUtils.copy(groupAttachments.getSendAttachment(), response.getOutputStream());
response.flushBuffer();
}
Service-Schicht:
@Override
public GroupAttachments getAttachmenById(int attachId) {
Person person = this.personService.getCurrentlyAuthenticatedUser();
GroupAttachments groupAttachments = this.groupAttachmentsDAO.getAttachmenById(attachId);
GroupMembers groupMembers = this.groupMembersService.returnMembersMatchingUsernameAccountId(person.getUsername(),
groupAttachments.getGroupId());
if (!(groupMembers == null)) {
if (person.getUsername().equals(groupMembers.getMemberUsername())) {
try {
Path path = Paths.get(msg + groupAttachments.getGroupId() + "/" +
groupAttachments.getFileIdentifier());
groupAttachments.setSendAttachment(Files.readAllBytes(path));
return groupAttachments;
} catch (IOException ignored) {
this.groupAttachmentsDAO.removeAttachment(attachId);
return null;
}
}
return null;
} else {
return null;
}
}
Danke. :-)
aktualisieren
Neuer Download-Mechanismus:
Controller:
public ResponseEntity<byte[]> getAttachmentFromDatabase(@PathVariable("attachid") int attachid,
@PathVariable("groupaccountid") Long groupAccountId, @PathVariable("api") String api,
HttpServletResponse response,
@PathVariable("type") boolean type) {
Path path = this.groupAttachmentsService.getAttachmentPathById(attachid);
GroupAttachments groupAttachments = this.groupAttachmentsService.getAttachmentObjectOnlyById(attachid);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\""+groupAttachments.getFileName()+"\"");
try {
OutputStream outputStream = response.getOutputStream();
Files.copy(path,outputStream);
outputStream.flush();
outputStream.close();
response.flushBuffer();
}
Service-Schicht:
@Override
public Path getAttachmentPathById(int attachId){
Person person = this.personService.getCurrentlyAuthenticatedUser();
GroupAttachments groupAttachments = this.groupAttachmentsDAO.getAttachmenById(attachId);
GroupMembers groupMembers = this.groupMembersService.returnMembersMatchingUsernameAccountId(person.getUsername(),
groupAttachments.getGroupId());
if (!(groupMembers == null)) {
if (person.getUsername().equals(groupMembers.getMemberUsername())) {
try {
return Paths.get(msg + groupAttachments.getGroupId() + "/" +
groupAttachments.getFileIdentifier());
} catch (Exception ignored) {
return null;
}
}
return null;
} else {
return null;
}
}
Sie lesen alles in den Speicher, tun Sie das nicht ... Die 'Files.readAllBytes' lädt die ganze' 2Gb' in den Speicher. Einfach nicht. Verzögern Sie das Laden und lesen Sie es nicht zuerst, lesen Sie es Stück für Stück und streamen Sie es direkt. Verwenden Sie stattdessen 'Files.copy (Pfad, OutputStream)' '. –
Kopieren wird es streamen, anstatt es zu laden. Bitte lesen Sie den Kommentar ... –
@ M.Deinum: Entschuldigung für das Missverständnis. Ich habe den Code aktualisiert und auch mit Files.copy (Pfad, outputStream) versucht, funktioniert gut. Ich habe den Code als Update im Hauptpost veröffentlicht, können Sie bitte überprüfen, ob eine mögliche Optimierung. Vielen Dank. –