【问题标题】:download a file from Spring boot rest service从 Spring Boot Rest 服务下载文件
【发布时间】:2016-06-11 09:26:01
【问题描述】:

我正在尝试从 Spring Boot Rest 服务下载文件。

@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);
        }

当我尝试从浏览器下载文件时,它会开始下载,但总是失败。导致下载失败的服务有什么问题吗?

【问题讨论】:

    标签: java spring rest


    【解决方案1】:

    选项 1 使用 InputStreamResource

    Resource 实现给定的InputStream

    仅应在没有其他特定资源实现适用的情况下使用。特别是,尽可能首选 ByteArrayResource 或任何基于文件的 Resource 实现。

    @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 正如 InputStreamResource 的文档所建议的那样 - 使用 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);
    }
    

    【讨论】:

    • 我正在尝试为 word 文档 .doc 格式执行此操作,但是在下载格式时已消失,并且下载的文件没有文件扩展名,并且下载时文件名是响应。有什么建议吗?
    • @TulsiJain 添加 Content-Disposition HttpHeader:HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=myDoc.docx");
    • 以防万一您不幸使用普通 Spring 而不是 Spring Boot,您需要确保将 ResourceHttpMessageConverter 的实例添加到您的 HttpMessageConverters 列表中。创建一个扩展WebMvcConfigurerAdapter@Configuration 类,实现configureMessageConverters() 方法并添加converters.add(new ResourceHttpMessageConverter());
    • 问题:选项 1 似乎没有关闭流。魔法在哪里?选项 2 似乎在发送之前将完整的文件加载到内存中。正确的?备择方案?谢了!
    • @eventhorizo​​n 在选项 1 中,Spring Boot 将关闭流。看到这个:stackoverflow.com/a/48660203/7363182
    【解决方案2】:

    下面的示例代码对我有用,可能会对某人有所帮助。

    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);
        }
    
    }
    

    【讨论】:

    • 非常适合我。
    【解决方案3】:

    我想分享一个使用 JavaScript (ES6)、ReactSpring Boot 后端下载文件的简单方法:

    1. Spring boot 休息控制器

    来自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. React,API 调用使用AXIOS

    将 responseType 设置为 arraybuffer 以指定响应中包含的数据类型。

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

    最后一步 > 下载
    js-file-download 的帮助下,您可以触发浏览器将数据保存到文件中,就像下载数据一样。

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

    【讨论】:

    • 我遇到了这个问题,并对 CONTENT_DISPOSITION 标头的文件名用双引号括起来感到好奇。事实证明,如果您有一个名称中包含空格的文件,那么如果没有双引号,您将无法在响应中获得整个文件名。好电话,@fetahokey
    【解决方案4】:
        @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);
    }
    

    要测试它,请使用邮递员

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

    【讨论】:

      【解决方案5】:

      我建议使用 StreamingResponseBody,因为有了它,应用程序可以直接写入响应 (OutputStream),而不会阻塞 Servlet 容器线程。如果您要下载非常大的文件,这是一个很好的方法。

      @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.:在使用StreamingResponseBody时,强烈建议配置Spring MVC中使用的TaskExecutor来执行异步请求。 TaskExecutor 是一个抽象 Runnable 执行的接口。

      更多信息:https://medium.com/swlh/streaming-data-with-spring-boot-restful-web-service-87522511c071

      【讨论】:

        【解决方案6】:

        如果您需要从服务器的文件系统下载一个巨大的文件,那么 ByteArrayResource 可以占用所有 Java 堆空间。在这种情况下,您可以使用FileSystemResource

        【讨论】:

          【解决方案7】:

          使用 Apache IO 可能是复制 Stream 的另一种选择

          @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 依赖

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

          【讨论】:

          • 直接用inputStream发回InputStreamResource。你不需要复制流。
          猜你喜欢
          • 1970-01-01
          • 2015-04-09
          • 2018-02-09
          • 2018-12-22
          • 2020-01-02
          • 1970-01-01
          • 2022-10-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多