web-dev-qa-db-ja.com

RestTemplateで大きなファイルを転送する方法は?

ZipファイルをアップロードできるWebサービスコールがあります。その後、ファイルは保存、解凍などのために別のサービスに転送されます。今のところ、ファイルはファイルシステムに保存され、FileSystemResourceが構築されます。

Resource zipFile = new FileSystemResource(tempFile.getAbsolutePath());

時間を節約するためにByteStreamResourceを使用できます(転送する前にディスク上のファイルを保存する必要はありません)が、そのためにバイト配列を作成する必要があります。大きなファイルの場合、「OutOfMemory:Java heap space」エラーが発生します。

ByteArrayResource r = new ByteArrayResource(inputStream.getBytes());

RestTemplateを使用してOutOfMemoryエラーを取得せずにファイルを転送するソリューションはありますか?

36
Gabi

この種の低レベル操作にはexecuteを使用できます。このスニペットでは、Commons IOのcopyメソッドを使用して入力ストリームをコピーしました。期待する応答の種類に合わせてHttpMessageConverterExtractorをカスタマイズする必要があります。

_final InputStream fis = new FileInputStream(new File("c:\\autoexec.bat")); // or whatever
final RequestCallback requestCallback = new RequestCallback() {
     @Override
    public void doWithRequest(final ClientHttpRequest request) throws IOException {
        request.getHeaders().add("Content-type", "application/octet-stream");
        IOUtils.copy(fis, request.getBody());
     }
};
final RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);     
restTemplate.setRequestFactory(requestFactory);     
final HttpMessageConverterExtractor<String> responseExtractor =
    new HttpMessageConverterExtractor<String>(String.class, restTemplate.getMessageConverters());
restTemplate.execute("http://localhost:4000", HttpMethod.POST, requestCallback, responseExtractor);
_

setBufferRequestBody(false)を呼び出す必要があることを指摘してくれたBazに感謝します。

37
artbristol

上記の答えには不要なコードがあると思います-匿名のRequestCallback内部クラスを作成する必要はなく、ApacheのIOUtilsを使用する必要もありません。

私はあなたに似た解決策を研究するのに少し時間を費やしましたが、これが私が思いついたものです:

Spring Resource Interface およびRestTemplateを使用すると、目標をはるかに簡単に達成できます。

RestTemplate restTemplate = new RestTemplate();

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);

File file = new File("/whatever");

HttpEntity<FileSystemResource> requestEntity = new HttpEntity<>(new FileSystemResource(file));
ResponseEntity e = restTemplate.exchange("http://localhost:4000", HttpMethod.POST, requestEntity, Map.class);

(この例では、POSTする場所からの応答がJSONであると想定しています。ただし、上記のMap.classに設定されている戻り値型クラスを変更することで簡単に変更できます)

15
RuntimeBlairror

@artbristolの answer で本当に必要なのはこれだけです(RestTemplate Spring Beanとして設定できます):

final RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);     
restTemplate.setRequestFactory(requestFactory);     

その後、FileSystemResourceをリクエストボディとして使用するだけで正しいことができると思います。

既にInputStreamResourceとしてデータがあり、それを複数回使用する必要がない場合に、この方法でInputStreamを正常に使用しました。

私の場合、ファイルをgzip圧縮し、GZipInputStreamInputStreamResourceにラップしました。

14
Ed Brannin