Laden Sie eine Datei vom Spring Boot Rest Service herunter

78

Ich versuche, eine Datei von einem Spring Boot Rest-Dienst herunterzuladen.

@RequestMapping(path="/downloadFile",method=RequestMethod.GET)
    @Consumes(MediaType.APPLICATION_JSON_VALUE)
    public  ResponseEntity<InputStreamReader> downloadDocument(
                String acquistionId,
                String fileType,
                Integer expressVfId) throws IOException {
        File file2Upload = new File("C:\\Users\\admin\\Desktop\\bkp\\1.rtf");
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        InputStreamReader i = new InputStreamReader(new FileInputStream(file2Upload));
        System.out.println("The length of the file is : "+file2Upload.length());

        return ResponseEntity.ok().headers(headers).contentLength(file2Upload.length())
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .body(i);
        }

Wenn ich versucht habe, die Datei vom Browser herunterzuladen, wird der Download gestartet, schlägt jedoch immer fehl. Stimmt etwas mit dem Dienst nicht, der dazu führt, dass der Download fehlschlägt?

kiran
quelle

Antworten:

148

Option 1 mit einer InputStreamResource

Ressourcenimplementierung für einen bestimmten InputStream .

Sollte nur verwendet werden, wenn keine andere spezifische Ressourcenimplementierung anwendbar ist. Bevorzugen Sie insbesondere ByteArrayResource oder eine der dateibasierten Ressourcenimplementierungen, sofern dies möglich ist.

@RequestMapping(path = "/download", method = RequestMethod.GET)
public ResponseEntity<Resource> download(String param) throws IOException {

    // ...

    InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

    return ResponseEntity.ok()
            .headers(headers)
            .contentLength(file.length())
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(resource);
}

Option2, wie in der Dokumentation der InputStreamResource vorgeschlagen - Verwendung einer ByteArrayResource :

@RequestMapping(path = "/download", method = RequestMethod.GET)
public ResponseEntity<Resource> download(String param) throws IOException {

    // ...

    Path path = Paths.get(file.getAbsolutePath());
    ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));

    return ResponseEntity.ok()
            .headers(headers)
            .contentLength(file.length())
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(resource);
}
Schicksal
quelle
2
Ich versuche es für das Word-Dokument .doc-Format zu tun, aber während des Herunterladens ist das Format weg und die Datei wird ohne Dateierweiterung heruntergeladen und der Dateiname ist eine Antwort beim Herunterladen. Irgendein Vorschlag?
Tulsi Jain
17
@ TulsiJain hinzufügen die Content-Disposition HttpHeader: HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=myDoc.docx");
Fateddy
7
Nur für den Fall, dass Sie das Pech haben, einfach Spring anstelle von Spring Boot zu verwenden, müssen Sie sicherstellen, dass eine Instanz von ResourceHttpMessageConverterzu Ihrer Liste der HttpMessageConverters hinzugefügt wird. Erstellen Sie eine @ConfigurationKlasse, die erweitert WebMvcConfigurerAdapter, implementieren Sie die configureMessageConverters () -Methode und fügen Sie eine converters.add(new ResourceHttpMessageConverter());Zeile hinzu
ashario
4
Fragen: Option 1 scheint den Stream nicht zu schließen. Wo ist die Magie? Option 2 scheint die gesamte Datei vor dem Senden in den Speicher zu laden. Richtig? Alternativen? DANKE!
Eventhorizon
20

Der folgende Beispielcode hat für mich funktioniert und könnte jemandem helfen.

import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
@RequestMapping("/app")
public class ImageResource {

    private static final String EXTENSION = ".jpg";
    private static final String SERVER_LOCATION = "/server/images";

    @RequestMapping(path = "/download", method = RequestMethod.GET)
    public ResponseEntity<Resource> download(@RequestParam("image") String image) throws IOException {
        File file = new File(SERVER_LOCATION + File.separator + image + EXTENSION);

        HttpHeaders header = new HttpHeaders();
        header.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=img.jpg");
        header.add("Cache-Control", "no-cache, no-store, must-revalidate");
        header.add("Pragma", "no-cache");
        header.add("Expires", "0");

        Path path = Paths.get(file.getAbsolutePath());
        ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));

        return ResponseEntity.ok()
                .headers(header)
                .contentLength(file.length())
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .body(resource);
    }

}
Rajesh Samson
quelle
4

Ich würde vorschlagen, einen StreamingResponseBody zu verwenden, da die Anwendung damit direkt in die Antwort (OutputStream) schreiben kann, ohne den Servlet-Container-Thread aufzuhalten. Dies ist ein guter Ansatz, wenn Sie eine sehr große Datei herunterladen.

@GetMapping("download")
public StreamingResponseBody downloadFile(HttpServletResponse response, @PathVariable Long fileId) {

    FileInfo fileInfo = fileService.findFileInfo(fileId);
    response.setContentType(fileInfo.getContentType());
    response.setHeader(
        HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\"" + fileInfo.getFilename() + "\"");

    return outputStream -> {
        int bytesRead;
        byte[] buffer = new byte[BUFFER_SIZE];
        InputStream inputStream = fileInfo.getInputStream();
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
    };
}

Ps.: Bei Verwendung von StreamingResponseBody wird dringend empfohlen, den in Spring MVC verwendeten TaskExecutor für die Ausführung asynchroner Anforderungen zu konfigurieren. TaskExecutor ist eine Schnittstelle, die die Ausführung eines Runnable abstrahiert.

Weitere Informationen: https://medium.com/swlh/streaming-data-with-spring-boot-restful-web-service-87522511c071

Felipe Desiderati
quelle
1
Ich konnte keine der ResponseEntity<Resource>Lösungen zum Laufen bringen. Diese Lösung hat sofort StreamingResponseBodyfunktioniert. DANKE!
t0r0X
2

Ich möchte einen einfachen Ansatz zum Herunterladen von Dateien mit JavaScript (ES6), React und einem Spring Boot- Backend teilen :

  1. Spring Boot Rest Controller

Ressource von org.springframework.core.io.Resource

    @SneakyThrows
    @GetMapping("/files/{filename:.+}/{extraVariable}")
    @ResponseBody
    public ResponseEntity<Resource> serveFile(@PathVariable String filename, @PathVariable String extraVariable) {

        Resource file = storageService.loadAsResource(filename, extraVariable);
        return ResponseEntity.ok()
               .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"")
               .body(file);
    }
  1. Reagieren Sie, API-Aufruf mit AXIOS

Setzen Sie den responseType auf arraybuffer , um den in der Antwort enthaltenen Datentyp anzugeben.

export const DownloadFile = (filename, extraVariable) => {
let url = 'http://localhost:8080/files/' + filename + '/' + extraVariable;
return axios.get(url, { responseType: 'arraybuffer' }).then((response) => {
    return response;
})};

Letzter Schritt> Herunterladen
mit Hilfe von js-file-download Sie können den Browser auslösen, um Daten in einer Datei zu speichern, als ob sie heruntergeladen worden wären.

DownloadFile('filename.extension', 'extraVariable').then(
(response) => {
    fileDownload(response.data, filename);
}
, (error) => {
    // ERROR 
});
Fetahokey
quelle
2

Wenn Sie eine große Datei aus dem Dateisystem des Servers herunterladen müssen, kann ByteArrayResource den gesamten Java-Heap-Speicherplatz belegen . In diesem Fall können Sie FileSystemResource verwenden

Taras Melone
quelle
0
    @GetMapping("/downloadfile/{productId}/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable(value = "productId") String productId,
        @PathVariable String fileName, HttpServletRequest request) {
    // Load file as Resource
    Resource resource;

    String fileBasePath = "C:\\Users\\v_fzhang\\mobileid\\src\\main\\resources\\data\\Filesdown\\" + productId
            + "\\";
    Path path = Paths.get(fileBasePath + fileName);
    try {
        resource = new UrlResource(path.toUri());
    } catch (MalformedURLException e) {
        e.printStackTrace();
        return null;
    }

    // Try to determine file's content type
    String contentType = null;
    try {
        contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
    } catch (IOException ex) {
        System.out.println("Could not determine file type.");
    }

    // Fallback to the default content type if type could not be determined
    if (contentType == null) {
        contentType = "application/octet-stream";
    }

    return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType))
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
            .body(resource);
}

Verwenden Sie zum Testen den Postboten

http: // localhost: 8080 / api / downloadfile / GDD / 1.zip

Feng Zhang
quelle
0

Die Verwendung von Apache IO könnte eine weitere Option zum Kopieren des Streams sein

@RequestMapping(path = "/file/{fileId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> downloadFile(@PathVariable(value="fileId") String fileId,HttpServletResponse response) throws Exception {

    InputStream yourInputStream = ...
    IOUtils.copy(yourInputStream, response.getOutputStream());
    response.flushBuffer();
    return ResponseEntity.ok().build();
}

Maven-Abhängigkeit

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.3.2</version>
    </dependency>
JPG
quelle