Spring RestTemplateを介して大きなファイルをダウンロードする

1. 概要

このチュートリアルでは、_RestTemplate_を使用してlink:/java-download-file [大きなファイルをダウンロード]する方法に関するさまざまなテクニックを紹介します。

2.  RestTemplate

_https://www.baeldung.com/rest-template [RestTemplate] _は、Spring 3で導入されたブロッキングおよび同期HTTPクライアントです。 https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html[Spring documentation]によると、これらは今後廃止される予定です。バージョン5では、リアクティブノンブロッキングHTTPクライアントとして_https://www.baeldung.com/spring-5-webclient [WebClient] _を導入しました。

3. 落とし穴

通常、ファイルをダウンロードするとき、ファイルシステムに保存するか、バイト配列としてメモリにロードします。 ただし、大きなファイルの場合、メモリ内の読み込みにより_https://www.baeldung.com/java-gc-overhead-limit-exceeded [OutOfMemoryError] _が発生する場合があります。 したがって、応答のチャンクを読み取るときにデータをファイルに保存する必要があります。
まず、機能しないいくつかの方法を見てみましょう。
最初に、戻り値の型としてa return_Resource_を返すとどうなりますか:
Resource download() {
    return new ClassPathResource(locationForLargeFile);
}
これが機能しない理由は、__ ResourceHttpMesssageConverter __ **は応答本文全体を_ByteArrayInputStream_ **にロードするため、回避したいメモリプレッシャーが追加されるためです。
第二に、an ___ InputStreamResource __を返し、configure__ResourceHttpMessageConverter#supportsReadStreaming_を返すとどうなりますか? さて、これは機能しません。* _ InputStreamResource.getInputStream()_を呼び出すことができるまでに、「_ socket closed」_ _エラーが発生するからです!*これは、「execute_」が応答を閉じるため終了前の入力ストリーム。
では、問題を解決するために何ができるでしょうか? 実際、ここにも2つのことがあります。
  • *カスタムを書く
    HttpMessageConverter*は、戻り値の型として_File_をサポートします

  • RestTemplate.execute_を* aカスタム_ResponseExtractor *とともに使用して、
    入力ストリームを_File_に保存します

    このチュートリアルでは、2番目のソリューションを使用します。これは、柔軟性が高く、労力も少ないためです。

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にコピーしましたが、他のlink:/convert-input-stream-to-a-file[techniques and libraries]も利用できます。

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_を使用する際のソリューションも紹介しました。 最後に、再開可能なダウンロードを実装する方法を示しました。
いつものように、コードはhttps://github.com/eugenp/tutorials/tree/master/spring-resttemplate [GitH] https://github.com/eugenp/tutorials/tree/master/spring-resttemplate [ ub]。