Рубрики
Без рубрики

Загрузите большой файл Через Spring RestTemplate

Изучите различные методы загрузки больших файлов с помощью RestTemplate.

Автор оригинала: baeldung.

1. Обзор

В этом уроке мы покажем различные методы, как загружать большие файлы с помощью RestTemplate .

2. RestTemplate

RestTemplate – это блокирующий и асинхронный HTTP-клиент, представленный в Spring 3. Согласно документации Spring , в будущем он будет устарел, так как они ввели WebClient | в качестве реактивного неблокирующего HTTP-клиента в версии 5.

3. Подводные камни

Обычно, когда мы загружаем файл, мы сохраняем его в нашей файловой системе или загружаем в память в виде массива байтов. Но когда это большой файл, загрузка в память может привести к OutOfMemoryError . Следовательно, мы должны хранить данные в файле, когда читаем фрагменты ответа.

Давайте сначала рассмотрим несколько способов, которые не работают:

Во-первых, что произойдет, если мы вернем Resource в качестве типа возвращаемого значения:

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

Причина, по которой это не работает, заключается в том, что Resourcehttpmessageconverter загрузит все тело ответа в ByteArrayInputStream | все еще добавляя давление памяти, которого мы хотели избежать.

Во-вторых, что делать, если мы вернем InputStreamResource и настроим ResourceHttpMessageConverter#supportsReadStreaming ? Ну, это тоже не работает, так как к тому времени, когда мы сможем позвонить InputStreamResource.getInputStream() , мы получаем “ сокет закрыт” ошибка! Это происходит потому, что “execute ” закрывает входной поток ответа перед выходом.

Так что же мы можем сделать, чтобы решить эту проблему? На самом деле, здесь тоже есть две вещи:

  • Напишите обычай HttpMessageConverter который поддерживает File в качестве возвращаемого типа
  • Используйте RestTemplate.execute with a custom ResponseExtractor для хранения входного потока в файле

В этом уроке мы будем использовать второе решение, потому что оно более гибкое и требует меньше усилий.

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);

Затем мы можем реализовать Запрос обратного вызова для установки заголовка “Диапазон” и возобновления загрузки:

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 . Наконец, мы показали, как мы можем реализовать возобновляемую загрузку.

Как всегда, код доступен в нашем GitHub .