1. 개요

이 예제에서, 우리는 방법에 대한 서로 다른 기술을 보여줄거야  큰 파일을 다운로드RestTemplate을 .

2.  RestTemplate

RestTemplate 은 Spring 3에서 도입 된 차단 및 동기 HTTP 클라이언트입니다. Spring 문서 에 따르면버전 5에서 반응 형 비 차단 HTTP 클라이언트로 WebClient 도입했기 때문에 향후 사용되지 않을 예정입니다.

3. 함정

일반적으로 파일을 다운로드 할 때 파일 시스템에 저장하거나 바이트 배열로 메모리에로드합니다. 그러나 대용량 파일 인 경우 메모리 내로드로 인해 OutOfMemoryError 가 발생할 수 있습니다 . 따라서 응답 청크를 읽을 때 파일에 데이터를 저장해야합니다.

먼저 작동하지 않는 몇 가지 방법을 살펴 보겠습니다.

먼저 Resource 를 반환 유형으로 반환하면 어떻게 되나요  ?

Resource download() {
    return new ClassPathResource(locationForLargeFile);
}

이것이 작동하지 않는 이유는 ResourceHttpMesssageConverter  가 전체 Response body을 ByteArrayInputStream에  로드하여 여전히 피하고 싶은 메모리 압력을 추가하기 때문입니다.

둘째, InputStreamResource를  반환하고 ResourceHttpMessageConverter # supportsReadStreaming을 구성  하면  어떻게됩니까? 음, 이것은 InputStreamResource.getInputStream ()을 호출   할 때까지 " 소켓 폐쇄" 오류가 발생하기 때문에 작동하지 않습니다 ! 이는 “execute ”가 종료 전에 응답 입력 스트림을 닫기 때문 입니다.

그렇다면 문제를 해결하기 위해 무엇을 할 수 있습니까? 실제로 여기에도 두 가지가 있습니다.

  • 반환 형식으로 File 을 지원 하는 사용자 지정 HttpMessageConverter 작성
  • 사용자 지정 ResponseExtractor 와 함께 RestTemplate.execute사용 하여 입력 스트림을 파일 에 저장합니다.

이 예제에서는 두 번째 솔루션이 더 유연하고 더 적은 노력이 필요하기 때문에 사용합니다.

4. 이력서없이 다운로드

임시 파일에 본문을 작성 하는 ResponseExtractor구현해 보겠습니다 .

File file = restTemplate.execute(FILE_URL, HttpMethod.GET, null, clientHttpResponse -> {
    File ret = File.createTempFile("download", "tmp");
    StreamUtils.copy(clientHttpResponse.getBody(), new FileOutputStream(ret));
    return ret;
});

Assert.assertNotNull(file);
Assertions
  .assertThat(file.length())
  .isEqualTo(contentLength);

여기서는 StreamUtils.copy사용하여  FileOutputStream  의 응답 입력 스트림을 복사 했지만 다른  기술과 라이브러리 도 사용할 수 있습니다.

5. 일시 중지 및 재개로 다운로드

대용량 파일을 다운로드 할 예정이므로 어떤 이유로 든 일시 중지 한 후에 다운로드를 고려하는 것이 좋습니다.

먼저 다운로드 URL이 이력서를 지원하는지 확인하겠습니다.

HttpHeaders headers = restTemplate.headForHeaders(FILE_URL);

Assertions
  .assertThat(headers.get("Accept-Ranges"))
  .contains("bytes");
Assertions
  .assertThat(headers.getContentLength())
  .isGreaterThan(0);

그런 다음 RequestCallback구현하여 "Range"헤더를 설정하고 다운로드를 재개 할 수 있습니다.

restTemplate.execute(
  FILE_URL,
  HttpMethod.GET,
  clientHttpRequest -> clientHttpRequest.getHeaders().set(
    "Range",
    String.format("bytes=%d-%d", file.length(), contentLength)),
    clientHttpResponse -> {
        StreamUtils.copy(clientHttpResponse.getBody(), new FileOutputStream(file, true));
    return file;
});

Assertions
  .assertThat(file.length())
  .isLessThanOrEqualTo(contentLength);

정확한 콘텐츠 길이를 모르는 경우 String.format을 사용하여 Range 헤더 값을 설정할 수 있습니다 .

String.format("bytes=%d-", file.length())

6. 결론

큰 파일을 다운로드 할 때 발생할 수있는 문제에 대해 논의했습니다. 또한 RestTemplate 을 사용하는 동안 솔루션을 제시했습니다 . 마지막으로 재개 가능한 다운로드를 구현하는 방법을 보여주었습니다.

항상 그렇듯이 코드는 GitHub 에서 사용할 수 있습니다 .