Baixar um arquivo grande por meio de um RestTemplate de primavera
1. Visão geral
Neste tutorial, vamos mostrar diferentes técnicas sobre comodownload large files comRestTemplate.
2. RestTemplate
RestTemplate é um cliente HTTP bloqueador e síncrono introduzido no Spring 3. De acordo comSpring documentation, ele será descontinuado no futuro, pois eles introduziramWebClient como um cliente HTTP reativo sem bloqueio na versão 5.
3. Armadilhas
Geralmente, quando baixamos um arquivo, o armazenamos em nosso sistema de arquivos ou o carregamos na memória como uma matriz de bytes. Mas quando é um arquivo grande, o carregamento na memória pode levar a umOutOfMemoryError. Portanto, temos que armazenar dados em um arquivo enquanto lemos os blocos de resposta.
Vejamos primeiro algumas maneiras que não funcionam:
Primeiro, o que acontece se retornarmos umResource como nosso tipo de retorno:
Resource download() {
return new ClassPathResource(locationForLargeFile);
}
O motivo pelo qual isso não funciona é queResourceHttpMesssageConverter will load the entire response body into a ByteArrayInputStream ainda está adicionando a pressão de memória que queríamos evitar.
Em segundo lugar, e se retornarmos umInputStreamResource and configureResourceHttpMessageConverter#supportsReadStreaming? Bem, isso também não funciona desdeby the time we can call InputStreamResource.getInputStream(), we get a “socket closed” error!. Isso ocorre porque o“execute ”fecha o fluxo de entrada de resposta antes da saída.
Então, o que podemos fazer para resolver o problema? Na verdade, há duas coisas aqui também:
-
Write a custom HttpMessageConverter que suportaFile como um tipo de retorno
-
UseRestTemplate.execute coma custom ResponseExtractor para armazenar o fluxo de entrada em umFile
Neste tutorial, usaremos a segunda solução porque é mais flexível e também requer menos esforço.
4. Download Sem Currículo
Vamos implementar umResponseExtractor para gravar o corpo em um arquivo temporário:
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);
Aqui, usamosStreamUtils.copy para copiar o fluxo de entrada de resposta em umFileOutputStream, , mas outrostechniques and libraries também estão disponíveis.
5. Download com Pausa e Continuar
Como faremos o download de um arquivo grande, é razoável considerar o download depois de termos pausado por algum motivo.
Portanto, primeiro vamos verificar se o URL de download suporta a retomada:
HttpHeaders headers = restTemplate.headForHeaders(FILE_URL);
Assertions
.assertThat(headers.get("Accept-Ranges"))
.contains("bytes");
Assertions
.assertThat(headers.getContentLength())
.isGreaterThan(0);
Então, podemos implementar umRequestCallback para definir o cabeçalho “Range” e retomar o download:
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);
Se não soubermos o comprimento exato do conteúdo, podemos definir o valor do cabeçalhoRange usandoString.format:
String.format("bytes=%d-", file.length())
6. Conclusão
Discutimos os problemas que podem surgir durante o download de um arquivo grande. Também apresentamos uma solução usandoRestTemplate. Finalmente, mostramos como podemos implementar um download recuperável.
Como sempre, o código está disponível em nossoGitHhttps: //github.com/eugenp/tutorials/tree/master/spring-resttemplate [ub].