如何使用 Spring RestTemplate 恢复下载?
How to resume download with Spring RestTemplate?
我正在使用 RestTemplate 从 Nexus 服务器下载一个文件(大约 350 MB)。 post 中提供的代码非常适合该目的:
RestTemplate restTemplate // = ...;
// Optional Accept header
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
// Streams the response instead of loading it all in memory
ResponseExtractor<Void> responseExtractor = response -> {
// Here I write the response to a file but do what you like
Path path = Paths.get("some/path");
Files.copy(response.getBody(), path);
return null;
};
restTemplate.execute(URI.create("www.something.com"), HttpMethod.GET, requestCallback, responseExtractor);
我想检查文件是否存在,然后尝试继续下载:
...
if(Files.exists(path)) {
log.info("{} exists. Attempting to resume download", path);
Files.write(path, StreamUtils.copyToByteArray(response.getBody()), StandardOpenOption.APPEND);
} else {
Files.copy(response.getBody(), path);
}
但这只会导致 OOM 错误:
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source) ~[na:1.8.0_191]
at java.io.ByteArrayOutputStream.grow(Unknown Source) ~[na:1.8.0_191]
at java.io.ByteArrayOutputStream.ensureCapacity(Unknown Source) ~[na:1.8.0_191]
at java.io.ByteArrayOutputStream.write(Unknown Source) ~[na:1.8.0_191]
...
我已经使用带有 curl 的范围 header 测试了调用,我确定 Nexus 支持它。我是这样设置的:
long bytes = path.toFile().length();
...
request.getHeaders().setRange(Arrays.asList(HttpRange.createByteRange(bytes)));
我猜上面的内存错误是因为 InputStream 阻塞造成的。所以我尝试使用 Channel/Buffer 代替:
...
try {
if(Files.exists(path)) {
log.info("{} exists. Attempting to resume download", path);
ReadableByteChannel channel = Channels.newChannel(response.getBody());
FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.APPEND);
fileChannel.tryLock();
ByteBuffer buffer = ByteBuffer.allocate(4096);
int bytesRead = 0;
while((bytesRead = channel.read(buffer)) != -1) {
fileChannel.write(buffer);
buffer.clear();
}
fileChannel.close();
} else {
Files.copy(response.getBody(), path);
}
...
这至少将 一些 数据写入文件但仍然失败。我对 java.nio 设施没有太多经验,因此不胜感激。
*编辑:感谢任何答案,但我不得不为这个项目使用 JDK 8。
如果您正在处理大文件,则需要以下代码行以确保不会在内存中读取流:
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
我正在使用 RestTemplate 从 Nexus 服务器下载一个文件(大约 350 MB)。
RestTemplate restTemplate // = ...;
// Optional Accept header
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
// Streams the response instead of loading it all in memory
ResponseExtractor<Void> responseExtractor = response -> {
// Here I write the response to a file but do what you like
Path path = Paths.get("some/path");
Files.copy(response.getBody(), path);
return null;
};
restTemplate.execute(URI.create("www.something.com"), HttpMethod.GET, requestCallback, responseExtractor);
我想检查文件是否存在,然后尝试继续下载:
...
if(Files.exists(path)) {
log.info("{} exists. Attempting to resume download", path);
Files.write(path, StreamUtils.copyToByteArray(response.getBody()), StandardOpenOption.APPEND);
} else {
Files.copy(response.getBody(), path);
}
但这只会导致 OOM 错误:
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source) ~[na:1.8.0_191]
at java.io.ByteArrayOutputStream.grow(Unknown Source) ~[na:1.8.0_191]
at java.io.ByteArrayOutputStream.ensureCapacity(Unknown Source) ~[na:1.8.0_191]
at java.io.ByteArrayOutputStream.write(Unknown Source) ~[na:1.8.0_191]
...
我已经使用带有 curl 的范围 header 测试了调用,我确定 Nexus 支持它。我是这样设置的:
long bytes = path.toFile().length();
...
request.getHeaders().setRange(Arrays.asList(HttpRange.createByteRange(bytes)));
我猜上面的内存错误是因为 InputStream 阻塞造成的。所以我尝试使用 Channel/Buffer 代替:
...
try {
if(Files.exists(path)) {
log.info("{} exists. Attempting to resume download", path);
ReadableByteChannel channel = Channels.newChannel(response.getBody());
FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.APPEND);
fileChannel.tryLock();
ByteBuffer buffer = ByteBuffer.allocate(4096);
int bytesRead = 0;
while((bytesRead = channel.read(buffer)) != -1) {
fileChannel.write(buffer);
buffer.clear();
}
fileChannel.close();
} else {
Files.copy(response.getBody(), path);
}
...
这至少将 一些 数据写入文件但仍然失败。我对 java.nio 设施没有太多经验,因此不胜感激。
*编辑:感谢任何答案,但我不得不为这个项目使用 JDK 8。
如果您正在处理大文件,则需要以下代码行以确保不会在内存中读取流:
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);