1. はじめに
今回はSpringFramework
のRestTemplate
を利用して巨大ファイルをダウンロードする方法について説明します。ポイントはResponseExtractor
を利用することです。
なお、巨大ファイルダウンロードのサーバ側の実装については「TERASOLUNA5.x(=SpringMVC)で巨大ファイルダウンロードを実現する方法」を参照ください。
2. ソースコード
package todo.app.largefile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.client.ResponseExtractor;
/**
* ★ポイント1
* Implements ResponseExtractor class for large file
*/
public class LargeFileResponseExtractor implements
ResponseExtractor<ResponseEntity<File>> {
/**
* ファイルパス
*/
private String filePath;
/**
* 空のコンストラクタ
*/
public LargeFileResponseExtractor() {
}
/**
* コンストラクタ
* @param filePath ファイルパス
*/
public LargeFileResponseExtractor(String filePath) {
this.filePath = filePath;
}
/**
* ★ポイント2
* @see org.springframework.web.client.ResponseExtractor#extractData(org.springframework.http.client.ClientHttpResponse)
*/
@Override
public ResponseEntity<File> extractData(
ClientHttpResponse response) throws IOException {
// 1. create File object on instructed file path or temporary
File downloadFile = null;
if (filePath == null) {
downloadFile = File.createTempFile("download", null);
} else {
downloadFile = new File(filePath);
}
// 2. copy contents with buffering
FileCopyUtils.copy(response.getBody(),
new FileOutputStream(downloadFile));
// ★ポイント3
// 3. create ResponseEntity object with status, headers, body
return ResponseEntity.status(response.getStatusCode())
.headers(response.getHeaders()).body(downloadFile);
}
}
★ポイント1
org.springframework.web.client.ResponseExtractor
インターフェースを実装したクラスを定義します。
<>
にはResponseExtractor
の戻り値となるデータ型を定義しますが、ResponseEntity<File>
とするのがポイントです。
今回は他でも利用できるように別クラスとして定義しましたが、利用する箇所に①アロー演算子(ラムダ式)、②インナークラス(匿名クラス)として書くことも可能です。
★ポイント2
HTTPレスポンスのBODYからデータをバッファリングしつつ、ファイルとして保存する処理をextractData
メソッドに実装します。この処理により巨大なファイルでもOutOfMemory
にならずにファイルをダウンロードすることが可能になります。
★ポイント3
戻り値となるResponseEntity
クラスのオブジェクトを生成します。この際、HTTPレスポンスのステータスコード、レスポンスヘッダも含めるようにします。
package todo.app.largefile;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
import java.io.File;
import java.util.List;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;
public class FileDownloadControllerTest {
@Test
public void test01() {
String targetUrl = "http://localhost:8090/todo-rest/download/chunked";
RestTemplate restTemplate = new RestTemplate();
// ★ポイント4
ResponseExtractor<ResponseEntity<File>> responseExtractor = new LargeFileResponseExtractor();
// ★ポイント5
ResponseEntity<File> responseEntity = restTemplate.execute(targetUrl,
HttpMethod.GET, null, responseExtractor);
// ★ポイント6
File downloadFile = responseEntity.getBody();
// assert
HttpHeaders httpHeaders = responseEntity.getHeaders();
long contentLength = httpHeaders.getContentLength();
MediaType contentType = httpHeaders.getContentType();
List<String> checkSum = httpHeaders.get("X-SHA1-CheckSum");
// ★ポイント7
assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
assertThat(contentLength, is(downloadFile.length()));
// omitted
}
}
★ポイント4
LargeFileResponseExtractor
クラスのオブジェクトを生成します。
★ポイント5
RestTemplate
のexecute
メソッドでHTTPリクエストを発行します。この際、第4引数のResponseExtractor
に★ポイント4で生成したLargeFileResponseExtractor
のオブジェクトを指定します。
★ポイント6
ResponseEntity
のgetBody
メソッドを実行するとLargeFileResponseExtractor
で保存したダウンロードファイルが取得できます。
★ポイント7
今回のサンプルではContent-Length
ヘッダのデータサイズとダウンロードファイルのデータサイズが等しいことを確認しています。
「TERASOLUNA5.x(=SpringMVC)で巨大ファイルダウンロードを実現する方法」のサーバ処理ではダウンロードファイルのチェックサム(ハッシュ値)をX-SHA1-CheckSum
ヘッダに設定しています。今回は省略しましたが、このチェックサムを比較して正常にダウンロードできたか確認する処理を追加してもいいでしょう。
3. さいごに
今回はRestTemplate
とResponseExtractor
を利用して巨大ファイルをダウンロードする方法について説明しました。
今回の方法はJdbcTemplate
でResultSetExtractor
を利用する方法とパターンが似ているかと思います。一般的にフレームワーク内ではアーキテクチャを揃えるため、APIの使い方は似たような感じになります。一つのAPIの使い方を覚えたら他も似たような感じになると思って調べてみるとよいかと思います。