使用FileSystemResource强制下载文件时如何设置 'Content-Disposition' 和 'Filename'?

59

使用Spring 3的FileSystemResource,设置Content-Disposition=attachmentfilename=xyz.zip的最合适和标准的方法是什么?

操作看起来像:

@ResponseBody
@RequestMapping(value = "/action/{abcd}/{efgh}", method = RequestMethod.GET, produces = "application/zip")
@PreAuthorize("@authorizationService.authorizeMethod()")
public FileSystemResource doAction(@PathVariable String abcd, @PathVariable String efgh) {

    File zipFile = service.getFile(abcd, efgh);

    return new FileSystemResource(zipFile);
}

尽管该文件是zip文件,因此浏览器总是下载该文件,但我想明确地将文件作为附件提及,并提供一个与文件实际名称无关的文件名。

可能有解决这个问题的方法,但我想知道正确的Spring和FileSystemResource方法来实现这个目标。

P.S. 这里使用的文件是临时文件,标记在JVM存在时删除。

5个回答

73

除了接受的答案,Spring还有一种专门用于此目的的类ContentDisposition。我认为它处理文件名消毒。

      ContentDisposition contentDisposition = ContentDisposition.builder("inline")
          .filename("Filename")
          .build();

      HttpHeaders headers = new HttpHeaders();
      headers.setContentDisposition(contentDisposition);

有趣的是,直到最近,只有在指定字符集为非US-ASCII时,ContentDisposition类才会对文件名进行编码:https://github.com/spring-projects/spring-framework/issues/24220 - Gareth
10
如果你需要使用HttpServletResponseResponseEntity构建器来构建一个响应,你仍然可以使用ContentDisposition类来为你生成头部字符串:response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString()); 它会处理必要的转义字符。 - neXus
1
从Spring 5.3开始,您可以使用ContentDisposition.inline()ContentDisposition.attachment()代替ContentDisposition.builder("inline") - Milosz Tylenda

57
@RequestMapping(value = "/action/{abcd}/{efgh}", method = RequestMethod.GET)
@PreAuthorize("@authorizationService.authorizeMethod(#id)")
public HttpEntity<byte[]> doAction(@PathVariable ObjectType obj, @PathVariable Date date, HttpServletResponse response) throws IOException {
    ZipFileType zipFile = service.getFile(obj1.getId(), date);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    response.setHeader("Content-Disposition", "attachment; filename=" + zipFile.getFileName());

    return new HttpEntity<byte[]>(zipFile.getByteArray(), headers);
}

5
请注意,如果文件名包含非“token”字符,如空格、非ASCII和某些分隔符,则此方法将会失效。 - Julian Reschke
10
简短回答:response.setHeader("Content-Disposition", "attachment; filename=" + YOUR_FILE_NAME);的作用是设置HTTP响应头,指示浏览器将服务器返回的内容作为附件下载,并指定下载文件的文件名为YOUR_FILE_NAME。 - Matthew.Lothian
5
避免仅提供代码的答案。对代码片段进行解释! - Manu343726
2
在双引号“”中放置文件名。 - nonzaprej
5
注意,如果文件名未经适当清理,这种解决方案可能会导致易受跨站脚本攻击! - Philippe Plantier
显示剩余2条评论

20
 @RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
    @ResponseBody
    public FileSystemResource getFile(@PathVariable("file_name") String fileName,HttpServletResponse response) {
        response.setContentType("application/pdf");      
        response.setHeader("Content-Disposition", "attachment; filename=somefile.pdf"); 
        return new FileSystemResource(new File("file full path")); 
    }

这个方法到底返回什么?myService是什么? - user3809938
@user3809938,FileSystemResource的构造函数可以接受文件路径的FileString类型参数。 - Mattiavelli

17
这里是Spring 4的另一种方法。请注意,这个示例明显没有使用良好的文件系统访问实践,只是为了演示如何以声明方式设置一些属性。
@RequestMapping(value = "/{resourceIdentifier}", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
// @ResponseBody // Needed for @Controller but not for @RestController.
public ResponseEntity<InputStreamResource> download(@PathVariable(name = "resourceIdentifier") final String filename) throws Exception
{
    final String resourceName = filename + ".dat";
    final File iFile = new File("/some/folder", resourceName);
    final long resourceLength = iFile.length();
    final long lastModified = iFile.lastModified();
    final InputStream resource = new FileInputStream(iFile);

    return ResponseEntity.ok()
            .header("Content-Disposition", "attachment; filename=" + resourceName)
            .contentLength(resourceLength)
            .lastModified(lastModified)
            .contentType(MediaType.APPLICATION_OCTET_STREAM_VALUE) // For Spring 5: APPLICATION_OCTET_STREAM
            .body(resource);
}

现在这应该是首选的方式。少一点政府干预总是更好的。 - undefined
1
Niggle: BodyBuilder.contentType,至少在Spring 5中,需要将完整的MediaType作为参数(例如APPLICATION_OCTET_STREAM),而不是一个字符串(例如APPLICATION_OCTET_STREAM_VALUE)。 - undefined
@JoachimLous 谢谢,我已将此添加为代码的注释。 - undefined

1

我对给出的两个答案进行了一些修改,最终在我的项目中得到了最佳解决方案。我的项目需要从数据库中提取一个图像作为Blob,然后将其提供给客户端:

@GetMapping("/images/{imageId:.+}")
@ResponseBody
public ResponseEntity<FileSystemResource>  serveFile(@PathVariable @Valid String imageId,HttpServletResponse response)
{       
    ImageEntity singleImageInfo=db.storage.StorageService.getImage(imageId);
    if(singleImageInfo==null)
    {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
    }
    Blob image=singleImageInfo.getImage();
    try {           
        String filename= UsersExtra.GenerateSession()+"xxyy"+singleImageInfo.getImage1Ext().trim();

    byte [] array = image.getBytes( 1, ( int ) image.length() );
    File file = File.createTempFile(UsersExtra.GenerateSession()+"xxyy", singleImageInfo.getImage1Ext().trim(), new File("."));
    FileOutputStream out = new FileOutputStream( file );
    out.write( array );
    out.close();
    FileSystemResource testing=new FileSystemResource(file);

    String mimeType = "image/"+singleImageInfo.getImage1Ext().trim().toLowerCase().replace(".", "");
      response.setContentType(mimeType);    

        String headerKey = "Content-Disposition";
       String headerValue = String.format("attachment; filename=\"%s\"", filename);
       response.setHeader(headerKey, headerValue);
      // return new FileSystemResource(file); 
       return ResponseEntity.status(HttpStatus.OK).body( new FileSystemResource(file));
    }catch(Exception e)
    {
        System.out.println(e.getMessage());
    }
    return null;
}

在Kumar的代码中使用ResponseEntity可以帮助你以正确的响应代码进行响应。 注意:从blob转换为文件的内容引用自此链接: 从Java blob创建文件内容的片段

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接