RestTemplate 下载文件

Last Modified: 2023/04/25

前言

大家平时工作中用的较多可能是使用 RestTemplate 调用 api,api 返回结果一般是 json,RestTemplate 将 json 反序列化为对象。不过今天我们这里要分享的是使用 RestTemplate 下载文件,包括下载大文件。

使用 RestTemplate 下载文件

RestTemplate 提供了 execute 方法,利用该方法我们可以读取响应体,将响应体存放到本地文件中,execute 方法签名如下:

public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException

这里需要比较重要的是 responseExtractor 参数,它的类型为 ResponseExtractor:

@FunctionalInterface
public interface ResponseExtractor<T> {
  @Nullable
  T extractData(ClientHttpResponse response) throws IOException;
}

ResponseExtractor 是一个函数式接口,这使得我们可以通过 lambda 表达式的形式(要求Java8)拿到 response,之后便是读取 response,并将读取到的内容存储到文件中。举个例子:

File file = restTemplate.execute("http://download/urlpath", HttpMethod.GET, null, response -> {
  File ret = File.createTempFile("download", "tmp");
  try (FileOutputStream out = new FileOutputStream(ret)) {
    StreamUtils.copy(response.getBody(), out);
    return ret;
  }
});

以上介绍了一种使用 RestTemplate 下载文件的方式,但需要说明的是,以上方式不仅仅适用于下载文件,事实上响应体本质是一系列 bytes,我们只是将这些 bytes 存放到了文件中。

RestTemplate 下载大文件

当文件很大时,如果由于网络或者其他原因导致下载中断,这个时候最好能从中断的地方再次下载,否则从头开始下载一个大文件是很让人沮丧的。当然这需要服务端能够支持部分请求,单靠 RestTemplate 是无能为力的。

通过 head 请求我们可以测试某个下载地址是否支持部分请求(partial request)。服务器端一般通过 Accept-Ranges 响应头告诉客户端它支持部分请求。

HttpHeaders headers = restTemplate.headForHeaders("http://your/download/path");

如果 headers 中包含 Accept-Ranges 响应头,那么服务端支持部分请求。这样便可以通过 RestTemplate 来实现部分请求:

File file = new File("/path/to/save/file");
restTemplate.execute(
  "http://your/download/path",
  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;
  });

这里有几个问题需要讲解:

Q1: contentLength 从哪里来?

之前我们为了测试服务端是否支持部分请求,发送了一个 head 请求,从这个请求的响应中一般可以读取到 contentLength,使用 headers.get("Content-Length) 即可。如果读取不到怎么办?

好办!将 String.format("bytes=%d-%d", file.length(), contentLength) 改成 String.format("bytes=%d-", file.length())

"Range" 请求头就是为了告诉服务端我们需要哪个区间的文件内容,例如:

  • bytes=100- 表示我们需要下载从 100 字节开始的内容直到文件末尾。
  • bytes=100-1000 表示我们需要下载从 100 到 1000 字节区间范围的文件内容。

Q2: new FileOutputStream(file, true) 第二个参数 true 的作用?

true 表示追加,由于是部分请求,我们可能一开始请求到了 1000 字节处,由于某种原因下载中断了,此时我们可以从 1001 字节处开始再次下载,如果没有 true,再次下载的内容会覆盖之前下载的内容,我们需要的是将本次下载的内容追加到上次已经下载的内容后面,最终完成整个大文件的下载。

有问题吗?点此反馈!

温馨提示:反馈需要登录