스프링 컨트롤러에서 파일 다운로드


387

웹 사이트에서 PDF를 다운로드해야하는 요구 사항이 있습니다. PDF는 코드 내에서 생성되어야하는데, 프리 마커와 iText와 같은 PDF 생성 프레임 워크의 조합이라고 생각했습니다. 더 좋은 방법?

그러나 내 주요 문제는 사용자가 Spring Controller를 통해 파일을 다운로드하도록 허용하는 방법입니다.


2
당신은뿐만 아니라 반응성 방법으로 그것을 할 수 있도록 스프링 프레임 워크는 2011 년 이후 많이 바뀌 것을 그것은 언급 할만큼 가치 - 여기가 예입니다
르지 Skrzynecki

답변:


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

}

일반적으로 말할 때 response.getOutputStream()거기에 무엇이든 쓸 수 있습니다. 생성 된 PDF를 생성기에 넣을 장소로이 출력 스트림을 전달할 수 있습니다. 또한 어떤 파일 형식을 보내고 있는지 알고 있다면 설정할 수 있습니다.

response.setContentType("application/pdf");

4
이것은 내가 말하려는 내용이지만 응답 유형 헤더를 파일에 적절한 것으로 설정해야합니다.
GaryF

2
예, 게시물을 편집했습니다. 다양한 파일 형식이 생성되었으므로 파일 확장자를 기준으로 파일의 콘텐츠 형식을 확인하기 위해 브라우저에 남겨 두었습니다.
Infeligo

귀하의 게시물 덕분에 flushBuffer를 잊어 버렸습니다. 왜 내 것이 작동하지 않는지 보았습니다 :-)
Jan Vladimir Mostert

35
IOUtilsSpring 대신 Apache를 사용해야하는 특별한 이유는 FileCopyUtils무엇입니까?
Powerlord

3
더 나은 해결책은 다음과 같습니다. stackoverflow.com/questions/16652760/…
Dmytro Plekhotkin

290

Spring의 내장 지원을 ResourceHttpMessageConverter와 함께 사용하여 이것을 스트리밍 할 수있었습니다. MIME 유형을 결정할 수있는 경우 내용 길이와 내용 유형을 설정합니다

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

10
작동합니다. 그러나 파일 (.csv 파일)이 브라우저에 표시되고 다운로드되지 않습니다. 브라우저를 강제로 다운로드하려면 어떻게해야합니까?
chzbrgla

41
@RequestMapping에 produce = MediaType.APPLICATION_OCTET_STREAM_VALUE를 추가하여 강제로 다운로드 할 수 있습니다.
David Kago

8
또한 <bean class = "org.springframework.http.converter.ResourceHttpMessageConverter"/> 를 messageConverters 목록에 추가해야 합니다 (<mvc : annotation-driven> <mvc : message-converters>)
Sllouyssgort

4
Content-Disposition이 방법으로 헤더 를 설정하는 방법이 있습니까?
랄프

8
그럴 필요는 없지만 HttpResponse를 메서드에 매개 변수로 추가 한 다음 "response.setHeader ("Content-Disposition ","attachment; filename = somefile.pdf ");"
Scott Carlson

82

응답에 파일을 직접 쓸 수 있어야합니다. 같은 것

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

파일을에 이진 스트림으로 씁니다 response.getOutputStream(). 이렇게하는 것을 잊지 response.flush()말에 그 그것을해야한다.


8
콘텐츠 유형을 다음과 같이 설정하는 '봄'방법이 아닙니까? @RequestMapping(value = "/foo/bar", produces = "application/pdf")
Black

4
@Francis 응용 프로그램이 다른 파일 형식을 다운로드하면 어떻게됩니까? Lobster1234의 답변을 통해 컨텐츠 처리를 동적으로 설정할 수 있습니다.
Rose

2
사실 @Rose이지만 형식에 따라 다른 종점을 정의하는 것이 좋습니다.
Black

3
확장 성이 없기 때문에 그렇지 않습니다. 현재 12 가지 유형의 리소스를 지원하고 있습니다. 이 경우 사용자가 업로드하려는 항목을 기반으로 더 많은 파일 형식을 지원할 수 있으며 본질적으로 동일한 작업을 수행하는 많은 엔드 포인트가 생길 수 있습니다. IMHO에는 다운로드 엔드 포인트가 하나만 있어야하며 다양한 파일 형식을 처리합니다. @Francis
Rose

3
그것은 절대적으로 "확장 가능"하지만 우리는 그것이 최선의 관례인지에 동의하지 않을 수 있습니다
Black

74

Spring 3.0에서는 HttpEntityreturn 객체를 사용할 수 있습니다 . 이것을 사용하면 컨트롤러에 HttpServletResponse객체 가 필요하지 않으므로 테스트하기가 더 쉽습니다. 이 점을 제외하면이 답변은 Infeligo의 답변과 상대적으로 같습니다 .

pdf 프레임 워크의 반환 값이 바이트 배열 인 경우 (다른 반환 값에 대해서는 내 답변의 두 번째 부분을 읽으십시오) :

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

당신의 PDF 프레임 워크 (의 반환 형식이있는 경우 documentBbody) 이미 바이트 배열이 아닌 (더도하고 ByteArrayInputStream) 다음은 현명하지 않을 것이다 NOT 먼저 바이트 배열 할 수 있습니다. 대신 다음을 사용하는 것이 좋습니다.

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

11
-1 불필요하게 전체 파일을 메모리에로드하여 OutOfMemoryErrors를 쉽게 만들 수 있습니다.
파이살 페 로즈

1
@FaisalFeroz : 그렇습니다. 그러나 파일 문서는 메모리에 생성됩니다 (질문 : "코드 내에서 PDF를 생성해야합니다"참조). 어쨌든-이 문제를 극복하는 솔루션은 무엇입니까?
Ralph

1
응답 http 상태 코드를 지정할 수있는 HttpEntity의 수퍼 인 ResponseEntity를 사용할 수도 있습니다. 예 :return new ResponseEntity<byte[]>(documentBody, headers, HttpStatus.CREATED)
Amr Mostafa

@Amr Mostafa : 반면 ResponseEntityHttpEntity(그러나 나는 그것을 얻는) 서브 클래스는 201 CREATED는 데이터에 대한 뷰를 반환 할 때 사용할 것이 아닙니다. ( 201 CREATED에 대해서는 w3.org/Protocols/rfc2616/rfc2616-sec10.html 참조 )
Ralph

1
파일 이름에서 공백을 밑줄로 바꾸는 이유가 있습니까? 실제 이름을 보내기 위해 따옴표로 묶을 수 있습니다.
Alexandru Severin

63

만약 너라면:

  • byte[]응답으로 보내기 전에 전체 파일을로드하기를 원하지 않습니다 .
  • 를 통해 전송 / 다운로드를 원하거나 필요로합니다 InputStream.
  • 전송 된 MIME 유형 및 파일 이름을 완전히 제어하고 싶습니다.
  • 다른 @ControllerAdvice픽업 예외가 있거나 없습니다.

아래 코드는 필요한 것입니다.

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

파일 길이 부분에 관해서 : File#length()일반적인 경우에는 충분해야하지만 , 느릴 수 있기 때문에이 관찰을 할 것이라고 생각했습니다. 경우 이전에 저장해야합니다 (예 : DB). 파일이 큰 경우, 특히 파일이 원격 시스템에 있거나 그와 같이 좀 더 정교한 경우 (데이터베이스 등)에는 느릴 수 있습니다.



InputStreamResource

리소스가 파일이 아닌 경우 (예 : DB에서 데이터를 선택하는 경우)를 사용해야합니다 InputStreamResource. 예:

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

FileSystemResource 클래스 사용을 권장하지 않습니까?
Stephane

실제로, 나는 FileSystemResource거기 에서 사용하는 것이 좋다고 생각합니다 . 리소스가 파일 인 경우 에도 권장 됩니다 . 이 샘플에서는 FileSystemResource어디에서나 사용할 수 있습니다 InputStreamResource.
acdcjunior

파일 길이 계산 부분에 관하여 : 걱정하지 마십시오. File#length()일반적인 경우에 충분해야합니다. 파일이 원격 시스템에 있거나 데이터베이스와 같은 정교한 파일 인 경우 특히 느릴 수 있기 때문에 방금 언급했습니다. 그러나 이전이 아니라 문제가되는 경우 (또는 확실한 증거가있는 경우)에만 걱정하십시오. 요점은 : 파일을 스트리밍하기 위해 노력하고 있습니다. 파일을 모두 미리로드해야한다면 스트리밍에 아무런 차이가 없습니다.
acdcjunior

위의 코드가 왜 작동하지 않습니까? 0 바이트 파일을 다운로드합니다. ByteArray & ResourceMessage 변환기가 있는지 확인하고 확인했습니다. 뭔가 빠졌습니까?
coding_idiot

왜 ByteArray & ResourceMessage 변환기에 대해 걱정하고 있습니까?
acdcjunior

20

이 코드는 jsp의 링크를 클릭 할 때 스프링 컨트롤러에서 자동으로 파일을 다운로드하는 데 효과적입니다.

@RequestMapping(value="/downloadLogFile")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
    try {
        String filePathToBeServed = //complete file name with path;
        File fileToDownload = new File(filePathToBeServed);
        InputStream inputStream = new FileInputStream(fileToDownload);
        response.setContentType("application/force-download");
        response.setHeader("Content-Disposition", "attachment; filename="+fileName+".txt"); 
        IOUtils.copy(inputStream, response.getOutputStream());
        response.flushBuffer();
        inputStream.close();
    } catch (Exception e){
        LOGGER.debug("Request could not be completed at this moment. Please try again.");
        e.printStackTrace();
    }

}

14

아래 코드는 텍스트 파일을 생성하고 다운로드하는 데 효과적이었습니다.

@RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity<byte[]> getDownloadData() throws Exception {

    String regData = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
    byte[] output = regData.getBytes();

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("charset", "utf-8");
    responseHeaders.setContentType(MediaType.valueOf("text/html"));
    responseHeaders.setContentLength(output.length);
    responseHeaders.set("Content-disposition", "attachment; filename=filename.txt");

    return new ResponseEntity<byte[]>(output, responseHeaders, HttpStatus.OK);
}

5

내가 빨리 생각할 수있는 것은 pdf를 생성하고 코드에서 webapp / downloads / <RANDOM-FILENAME> .pdf에 저장하고 HttpServletRequest를 사용 하여이 파일로 전달하는 것입니다.

request.getRequestDispatcher("/downloads/<RANDOM-FILENAME>.pdf").forward(request, response);

뷰 리졸버를 다음과 같이 구성 할 수 있다면

  <bean id="pdfViewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"
              value="org.springframework.web.servlet.view.JstlView" />
    <property name="order" value=”2″/>
    <property name="prefix" value="/downloads/" />
    <property name="suffix" value=".pdf" />
  </bean>

그리고 그냥 돌아와

return "RANDOM-FILENAME";

1
두 개의 뷰 리졸버가 필요한 경우 어떻게 리졸버 이름을 반환하거나 컨트롤러에서 선택할 수 있습니까?
azerafati

3

다음 솔루션이 나를 위해 일합니다.

    @RequestMapping(value="/download")
    public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
        try {

            String fileName="archivo demo.pdf";
            String filePathToBeServed = "C:\\software\\Tomcat 7.0\\tmpFiles\\";
            File fileToDownload = new File(filePathToBeServed+fileName);

            InputStream inputStream = new FileInputStream(fileToDownload);
            response.setContentType("application/force-download");
            response.setHeader("Content-Disposition", "attachment; filename="+fileName); 
            IOUtils.copy(inputStream, response.getOutputStream());
            response.flushBuffer();
            inputStream.close();
        } catch (Exception exception){
            System.out.println(exception.getMessage());
        }

    }

2

아래와 같은 것

@RequestMapping(value = "/download", method = RequestMethod.GET)
public void getFile(HttpServletResponse response) {
    try {
        DefaultResourceLoader loader = new DefaultResourceLoader();
        InputStream is = loader.getResource("classpath:META-INF/resources/Accepted.pdf").getInputStream();
        IOUtils.copy(is, response.getOutputStream());
        response.setHeader("Content-Disposition", "attachment; filename=Accepted.pdf");
        response.flushBuffer();
    } catch (IOException ex) {
        throw new RuntimeException("IOError writing file to output stream");
    }
}

여기에서 PDF를 표시하거나 예제를 다운로드 할 수 있습니다


1

누군가에게 도움이된다면. Infeligo가 받아 들인 대답을 제안 할 수 있지만 강제 다운로드를 위해 코드 에이 여분의 비트를 넣으십시오.

response.setContentType("application/force-download");


0

필자의 경우 요청에 따라 파일을 생성하므로 URL도 생성해야합니다.

나를 위해 다음과 같이 작동합니다.

@RequestMapping(value = "/files/{filename:.+}", method = RequestMethod.GET, produces = "text/csv")
@ResponseBody
public FileSystemResource getFile(@PathVariable String filename) {
    String path = dataProvider.getFullPath(filename);
    return new FileSystemResource(new File(path));
}

MIME 형식이 매우 중요 produces하며 파일 이름이 링크의 일부이므로를 사용해야 @PathVariable합니다.

HTML 코드는 다음과 같습니다.

<a th:href="@{|/dbreport/files/${file_name}|}">Download</a>

여기서 ${file_name}컨트롤러의 Thymeleaf에 의해 생성되고 result_20200225.csv이므로 전체 URL behing 링크는 다음과 같습니다 example.com/aplication/dbreport/files/result_20200225.csv.

링크 브라우저를 클릭하면 파일로 무엇을 할 것인지 묻습니다-저장 또는 열기.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.