1. 概要
このチュートリアルでは、RestTemplateを使用して大きなファイルをダウンロードする方法に関するさまざまな手法を紹介します。
2. RestTemplate
RestTemplate は、Spring3で導入されたブロッキングおよび同期HTTPクライアントです。 Springのドキュメントによると、バージョン5でリアクティブなノンブロッキングHTTPクライアントとして WebClient が導入されたため、将来的に非推奨になります。
3. 落とし穴
通常、ファイルをダウンロードするときは、ファイルシステムに保存するか、バイト配列としてメモリにロードします。 ただし、ファイルが大きい場合、メモリ内のロードによりOutOfMemoryErrorが発生する可能性があります。 したがって、応答のチャンクを読み取るときに、データをファイルに保存する必要があります。
まず、機能しないいくつかの方法を見てみましょう。
まず、リターンタイプとしてResourceを返すとどうなりますか。
Resource download() {
return new ClassPathResource(locationForLargeFile);
}
これが機能しない理由は、 ResourceHttpMesssageConverter が応答本文全体をByteArrayInputStreamにロードし、回避したいメモリプレッシャーを追加するためです。
次に、 InputStreamResource を返し、 ResourceHttpMessageConverter#supportsReadStreamingを構成するとどうなりますか? InputStreamResource.getInputStream()を呼び出すことができるようになるまでに、「ソケットが閉じられました」エラーが発生するため、これも機能しません!これは、「execute」が閉じるためです。出口の前の応答入力ストリーム。
では、問題を解決するために何ができるでしょうか。 実際、ここにも2つのことがあります。
- リターンタイプとしてファイルをサポートするカスタムHttpMessageConverterを記述します
- RestTemplate.execute とカスタムResponseExtractorを使用して、入力ストリームをファイルに保存します。
このチュートリアルでは、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、にコピーしましたが、他のテクニックとライブラリも利用できます。
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で入手できます。