Downloading a file from spring controllers

前端 未结 14 1080
渐次进展
渐次进展 2020-11-22 01:06

I have a requirement where I need to download a PDF from the website. The PDF needs to be generated within the code, which I thought would be a combination of freemarker and

14条回答
  •  孤城傲影
    2020-11-22 01:42

    TL;DR

    1. Return ResponseEntity from a handler method
    2. Specify Content-Type explicitly
    3. Set Content-Disposition if necessary:
      1. filename
      2. type
        1. inline to force preview in a browser
        2. attachment to force a download
    @Controller
    public class DownloadController {
        @GetMapping("/downloadPdf.pdf")
        // 1.
        public ResponseEntity downloadPdf() {
            FileSystemResource resource = new FileSystemResource("/home/caco3/Downloads/JMC_Tutorial.pdf");
            // 2.
            MediaType mediaType = MediaTypeFactory
                    .getMediaType(resource)
                    .orElse(MediaType.APPLICATION_OCTET_STREAM);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(mediaType);
            // 3
            ContentDisposition disposition = ContentDisposition
                    // 3.2
                    .inline() // or .attachment()
                    // 3.1
                    .filename(resource.getFilename())
                    .build();
            headers.setContentDisposition(disposition);
            return new ResponseEntity<>(resource, headers, HttpStatus.OK);
        }
    }
    

    Explanation

    Return ResponseEntity

    When you return a ResponseEntity, the ResourceHttpMessageConverter kicks in and writes an appropriate response.

    Be aware of possibly wrong Content-Type header set (see FileSystemResource is returned with content type json). That's why this answer suggests setting the Content-Type explicitly.

    Specify Content-Type explicitly:

    Some options are:

    • hardcode the header
    • use the MediaTypeFactory from Spring.
    • or rely on third party library like Apache Tika

    The MediaTypeFactory allows to discover the MediaType appropriate for the Resource (see also /org/springframework/http/mime.types file)

    Set Content-Disposition if necessary:

    Sometimes it is necessary to force a download in a browser or to make the browser open a file as a preview. You can use the Content-Disposition header to satisfy this requirement:

    The first parameter in the HTTP context is either inline (default value, indicating it can be displayed inside the Web page, or as the Web page) or attachment (indicating it should be downloaded; most browsers presenting a 'Save as' dialog, prefilled with the value of the filename parameters if present).

    In the Spring Framework a ContentDisposition can be used.

    To preview a file in a browser:

    ContentDisposition disposition = ContentDisposition
            .builder("inline") // Or .inline() if you're on Spring MVC 5.3+
            .filename(resource.getFilename())
            .build();
    

    To force a download:

    ContentDisposition disposition = ContentDisposition
            .builder("attachment") // Or .attachment() if you're on Spring MVC 5.3+
            .filename(resource.getFilename())
            .build();
    

    Use InputStreamResource carefully:

    Since an InputStream can be read only once, Spring won't write Content-Length header if you return an InputStreamResource (here is a snippet of code from ResourceHttpMessageConverter):

    @Override
    protected Long getContentLength(Resource resource, @Nullable MediaType contentType) throws IOException {
        // Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
        // Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
        if (InputStreamResource.class == resource.getClass()) {
            return null;
        }
        long contentLength = resource.contentLength();
        return (contentLength < 0 ? null : contentLength);
    }
    

    In other cases it works fine:

    ~ $ curl -I localhost:8080/downloadPdf.pdf  | grep "Content-Length"
    Content-Length: 7554270
    

提交回复
热议问题