453 Stimmen

Herunterladen einer Datei von Spring Controllern

Ich habe eine Anforderung, bei der ich eine PDF-Datei von der Website herunterladen muss. Das PDF muss innerhalb des Codes generiert werden. Ich dachte, das wäre eine Kombination aus Freemarker und einem PDF-Generierungs-Framework wie iText. Gibt es einen besseren Weg?

Mein Hauptproblem ist jedoch, wie kann ich dem Benutzer erlauben, eine Datei über einen Spring Controller herunterladen?

435voto

Infeligo Punkte 11505
@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
public void getFile(
    @PathVariable("file_name") String fileName, 
    HttpServletResponse response) {
    try {
      // get your file as InputStream
      InputStream is = ...;
      // copy it to response's OutputStream
      org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
      response.flushBuffer();
    } catch (IOException ex) {
      log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
      throw new RuntimeException("IOError writing file to output stream");
    }

}

Generell gilt: Wenn Sie response.getOutputStream() können Sie dort alles schreiben. Sie können diesen Ausgabestrom als Speicherort für die generierte PDF-Datei an Ihren Generator übergeben. Wenn Sie wissen, welchen Dateityp Sie senden wollen, können Sie auch

response.setContentType("application/pdf");

307voto

Scott Carlson Punkte 3694

Ich war in der Lage, diese durch die Verwendung der eingebauten Unterstützung in Spring mit ResourceHttpMessageConverter Stromlinie. Dieser setzt die Content-Länge und den Content-Type, wenn er den Mime-Type bestimmen kann

@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
@ResponseBody
public FileSystemResource getFile(@PathVariable("file_name") String fileName) {
    return new FileSystemResource(myService.getFileFor(fileName)); 
}

88voto

lobster1234 Punkte 7599

Sie sollten in der Lage sein, die Datei direkt auf die Antwort zu schreiben. Etwas wie

response.setContentType("application/pdf");      
response.setHeader("Content-Disposition", "attachment; filename=\"somefile.pdf\""); 

und schreiben Sie die Datei dann als Binärstrom auf response.getOutputStream() . Nicht vergessen zu tun response.flush() am Ende und das sollte genügen.

83voto

Ralph Punkte 114913

Mit Spring 3.0 können Sie die HttpEntity Objekt zurückgeben. Wenn Sie dies verwenden, benötigt Ihr Controller kein HttpServletResponse Objekt und ist daher einfacher zu testen. Abgesehen davon ist diese Antwort relativ gleichwertig mit der von Infeligo .

Wenn der Rückgabewert Ihres pdf-Frameworks ein Byte-Array ist (lesen Sie den zweiten Teil meiner Antwort für andere Rückgabewerte) :

@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
                 @PathVariable("fileName") String fileName) throws IOException {

    byte[] documentBody = this.pdfFramework.createPdf(filename);

    HttpHeaders header = new HttpHeaders();
    header.setContentType(MediaType.APPLICATION_PDF);
    header.set(HttpHeaders.CONTENT_DISPOSITION,
                   "attachment; filename=" + fileName.replace(" ", "_"));
    header.setContentLength(documentBody.length);

    return new HttpEntity<byte[]>(documentBody, header);
}

Wenn der Rückgabetyp Ihres PDF-Frameworks ( documentBbody ) nicht bereits ein Byte-Array ist (und auch keine ByteArrayInputStream ), dann wäre es ratsam NICHT um es zuerst zu einem Byte-Array zu machen. Stattdessen ist es besser, zu verwenden:

Beispiel mit FileSystemResource :

@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
                 @PathVariable("fileName") String fileName) throws IOException {

    File document = this.pdfFramework.createPdf(filename);

    HttpHeaders header = new HttpHeaders();
    header.setContentType(MediaType.APPLICATION_PDF);
    header.set(HttpHeaders.CONTENT_DISPOSITION,
                   "attachment; filename=" + fileName.replace(" ", "_"));
    header.setContentLength(document.length());

    return new HttpEntity<byte[]>(new FileSystemResource(document),
                                  header);
}

81voto

acdcjunior Punkte 123383

Wenn Sie:

  • Sie möchten nicht die gesamte Datei in eine byte[] bevor sie an die Antwort gesendet wird;
  • Sie möchten/müssen es senden/herunterladen über InputStream ;
  • Sie möchten die volle Kontrolle über den gesendeten Mime-Type und Dateinamen haben;
  • Haben andere @ControllerAdvice die Ausnahmen für Sie aufheben (oder auch nicht).

Sie benötigen den folgenden Code:

@RequestMapping(value = "/stuff/{stuffId}", method = RequestMethod.GET)
public ResponseEntity<FileSystemResource> downloadStuff(@PathVariable int stuffId)
                                                                      throws IOException {
    String fullPath = stuffService.figureOutFileNameFor(stuffId);
    File file = new File(fullPath);
    long fileLength = file.length(); // this is ok, but see note below

    HttpHeaders respHeaders = new HttpHeaders();
    respHeaders.setContentType("application/pdf");
    respHeaders.setContentLength(fileLength);
    respHeaders.setContentDispositionFormData("attachment", "fileNameIwant.pdf");

    return new ResponseEntity<FileSystemResource>(
        new FileSystemResource(file), respHeaders, HttpStatus.OK
    );
}

Mehr dazu setContentLength() : Erstens, die content-length Header ist gemäß dem HTTP 1.1 RFC optional . Wenn Sie jedoch einen Wert bieten können, ist es besser. Um einen solchen Wert zu erhalten, müssen Sie wissen, dass File#length() sollte im allgemeinen Fall ausreichen, so dass es eine sichere Standardwahl ist.
In ganz bestimmten Szenarien kann es jedoch kann langsam sein In diesem Fall sollte sie vorher gespeichert werden (z. B. in der DB) und nicht im laufenden Betrieb berechnet werden. Langsame Szenarien sind: wenn die Datei muy groß, vor allem, wenn es sich um ein entferntes System oder etwas anderes Kompliziertes handelt - eine Datenbank vielleicht.


InputStreamResource

Wenn Ihre Ressource keine Datei ist, d.h. wenn Sie die Daten aus der DB abrufen, sollten Sie InputStreamResource . Beispiel:

InputStreamResource isr = new InputStreamResource(...);
return new ResponseEntity<InputStreamResource>(isr, respHeaders, HttpStatus.OK);

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X