知識庫 / HTTP Client-Side RSS 訂閱

通過 Spring RestTemplate 下載大型文件

HTTP Client-Side,Spring
HongKong
9
01:22 PM · Dec 06 ,2025

1. 概述

在本教程中,我們將演示如何使用 RestTemplate 技術下載大型文件。

2. RestTemplate

RestTemplate 是 Spring 3.0 中引入的阻塞式同步 HTTP 客户端。根據 Spring 文檔,由於 Spring 5.0 引入了 WebClient 作為響應式非阻塞 HTTP 客户端,因此它將在未來被棄用。

3. 常見問題

通常,當我們下載文件時,我們會將其存儲在文件系統中,或將其加載為字節數組。但是,對於大型文件,將數據加載到內存中可能會導致 OutOfMemoryError。因此,我們必須將數據存儲在文件中,並以塊的形式讀取響應。

讓我們首先看一下一些不生效的方法:

首先,如果我們將返回類型設置為 Resource 會發生什麼:

Resource download() {
    return new ClassPathResource(locationForLargeFile);
}

這段代碼無法正常工作的原因是,ResourceHttpMesssageConverter 會將整個響應體加載到 ByteArrayInputStream 中,仍然會增加我們想要避免的內存壓力。

其次,如果我們返回一個 InputStreamResource 並配置 ResourceHttpMessageConverter#supportsReadStreaming,結果也是不行,因為當我們能夠調用 InputStreamResource.getInputStream() 時,會收到 “socket closed” 錯誤! 這是因為 “execute” 在退出之前關閉了響應輸入流。

所以,我們該如何解決這個問題呢? 實際上,這裏也有兩點:

  • 編寫一個自定義的 HttpMessageConverter,支持 File 作為返回值
  • 使用 RestTemplate.execute 搭配一個自定義的 ResponseExtractor,將輸入流存儲到 File

在本文教程中,我們將使用第二種解決方案,因為它更靈活,而且也需要更少的努力。

4. 不中斷下載

讓我們實現一個 ResponseExtractor,將內容寫入臨時文件 :

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來複制響應輸入流到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);

如果我們不知道確切的內容長度,可以使用 Range 標頭的值,通過 String.format 進行設置:

String.format("bytes=%d-", file.length())

6. 結論

我們討論了在下載大型文件時可能出現的各種問題。我們還通過使用 RestTemplate 提出瞭解決方案。最後,我們展示瞭如何實現可中斷下載。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.