2016-05-12 4 views
1

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; 
     } 
    } 
+1

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)' '. –

+0

Kopieren wird es streamen, anstatt es zu laden. Bitte lesen Sie den Kommentar ... –

+0

@ 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. –

Antwort

2

Erster Halt den ganzen Inhalt in Ihrem Service Laden , da lädt man das wh viel Inhalt der Datei in den Speicher.

Erstellen Sie eine Methode, die die Path für die GroupAttachments konstruiert, würde ich das auf der GroupAttachments es selbst erstellen.

public class GroupAttachments { 

    public Path getPath() { 
     return Paths.get(msg + getGroupId() + "/" + getFileIdentifier()); 
    } 
} 

Dann in Ihrem Controller tut einfach

@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); 
    Path path = groupAttachmetns.getPath(); // calculates the java.nio.file.Path 
    response.setHeader("Content-Disposition", "attachment; filename=\"" + path.getFileName() + "\""); 
    response.setContentLength(Files.size(path); 
    Files.copy(path, response.getOutputStream()); 
    response.flushBuffer(); 

} 

Es gibt keine Notwendigkeit, es komplexe imho zu machen.

+0

Abgesehen von der Länge des Inhalts ist alles gleich. Danke vielmals. :-) –

+0

Einige Browser benötigen eine Inhaltslänge, andere funktionieren ohne sie. –

+0

Bereits hinzugefügt, aber ich musste es umsetzen, 'response.setContentLength ((int) Files.size (path));' –