SpringのRestTemplateで巨大ファイルダウンロードを実現する方法

More than 1 year has passed since last update.


1. はじめに

今回はSpringFrameworkRestTemplateを利用して巨大ファイルをダウンロードする方法について説明します。ポイントはResponseExtractorを利用することです。

なお、巨大ファイルダウンロードのサーバ側の実装については「TERASOLUNA5.x(=SpringMVC)で巨大ファイルダウンロードを実現する方法」を参照ください。


2. ソースコード


LargeFileResponseExtractor.java

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レスポンスのステータスコード、レスポンスヘッダも含めるようにします。


FileDownloadControllerTest.java

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

RestTemplateexecuteメソッドでHTTPリクエストを発行します。この際、第4引数のResponseExtractorに★ポイント4で生成したLargeFileResponseExtractorのオブジェクトを指定します。

★ポイント6

ResponseEntitygetBodyメソッドを実行するとLargeFileResponseExtractorで保存したダウンロードファイルが取得できます。

★ポイント7

今回のサンプルではContent-Lengthヘッダのデータサイズとダウンロードファイルのデータサイズが等しいことを確認しています。

TERASOLUNA5.x(=SpringMVC)で巨大ファイルダウンロードを実現する方法」のサーバ処理ではダウンロードファイルのチェックサム(ハッシュ値)をX-SHA1-CheckSumヘッダに設定しています。今回は省略しましたが、このチェックサムを比較して正常にダウンロードできたか確認する処理を追加してもいいでしょう。


3. さいごに

今回はRestTemplateResponseExtractorを利用して巨大ファイルをダウンロードする方法について説明しました。

今回の方法はJdbcTemplateResultSetExtractorを利用する方法とパターンが似ているかと思います。一般的にフレームワーク内ではアーキテクチャを揃えるため、APIの使い方は似たような感じになります。一つのAPIの使い方を覚えたら他も似たような感じになると思って調べてみるとよいかと思います。