1. 概述
本教程中,我们将展示使用 RestTemplate 下载大文件的不同技术。
2. RestTemplate
RestTemplate 是 Spring 3 中引入的同步阻塞式 HTTP 客户端。根据 Spring 官方文档 介绍,在将来的版本中它可能会被弃用,因为他们已在 Spring 5 中引入了 WebClient 作为非阻塞式 Reactive HTTP 客户端。
3. 陷阱
通常,当我们下载文件时,我们会将其保存在本地文件系统中,或者作为字节流加载到内存中。但是,当遇到大文件时,内存加载可能会造成 OutOfMemoryError。因此,当我们读取 response 块时,必须将其保存到文件中。
我们先来看这两种不起作用的方法:
第一个,当我们将 Resource 作为我们的返回值类型时会发生什么?
-
Resource download() { -
return new ClassPathResource(locationForLargeFile); -
}
ResourceHttpMesssageConverter 会将整个 response 块加载到 ByteArrayInputStream 中,依旧增加了我们本想避免的内存压力。
第二个,当我们配置 ResourceHttpMessageConverter#supportsReadStreaming 方法,并返回 InputStreamResource,会发生什么?好吧,它也不会起作用,**当我们调用 InputStreamResource.getInputStream() 时,将得到一个 socket closed 错误!**这是由于 execute 方法在它退出前会关闭 response 输入流所造成的。
所以我们可以怎样解决这个问题呢?实际上,也有两种办法:
基于它们的灵活性和实现难度,本教程对第二种解决方案来展开介绍。
4. 无需恢复的下载
让我们来实现一个 ResponseExtractor,用以将 body 写入到临时文件中。
-
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 将 response 输入流复制到 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);
如果我们不确定具体的 Content 长度,可以使用如下的 String.format 形式来设置 Range 请求头
String.format("bytes=%d-", file.length())
本文探讨了使用Spring的RestTemplate下载大文件时可能遇到的问题,包括内存溢出和流关闭错误。介绍了如何通过实现ResponseExtractor和RequestCallback来解决这些问题,以支持暂停和恢复下载。
2212

被折叠的 条评论
为什么被折叠?



