Как пересылать большие файлы с помощью RestTemplate?

У меня есть вызов веб-службы, через который можно загружать zip-файлы. Затем файлы перенаправляются в другую службу для хранения, распаковки и т. д. Сейчас файл хранится в файловой системе, затем создается FileSystemResource.

Resource zipFile = new FileSystemResource(tempFile.getAbsolutePath());

Я мог бы использовать ByteStreamResource, чтобы сэкономить время (сохранение файла на диске перед пересылкой не требуется), но для этого мне нужно построить массив байтов. В случае больших файлов я получу ошибку «OutOfMemory: java heap space».

ByteArrayResource r = new ByteArrayResource(inputStream.getBytes());

Любые решения для пересылки файлов без получения ошибки OutOfMemory с использованием RestTemplate?


person Gabi    schedule 03.04.2013    source источник
comment
Разве вы не можете передать входной поток другому сервису? Или вам придется записать входной поток в файл, а затем передать дескриптор файла службе. Кроме того, не знаете, как это относится к Groovy?   -  person tim_yates    schedule 03.04.2013
comment
Я не нашел способа просто передать входной поток. Я использовал тег Groovy, потому что код в groovy (java InputStream не имеет метода getBytes)   -  person Gabi    schedule 03.04.2013
comment
Аааа, меня бросило, когда ты пишешь это в стиле Java ;-) Так что же тогда принимает этот другой сервис?   -  person tim_yates    schedule 03.04.2013
comment
Я не предоставляю это как ответ, потому что он немного больше по объему, чем ответ на ваш вопрос, но рассматривали ли вы Spring Integration для этой проблемы? В основном вы смотрите на шаблон проверки претензий с веб-службой и адаптерами REST. Вы можете сделать большую часть работы за вас с помощью структуры СИ. static.springsource.org/spring-integration/reference/htmlsingle/< /а>   -  person Emerson Farrugia    schedule 26.06.2013


Ответы (3)


Изменить: другие ответы лучше (используйте Resource) https://stackoverflow.com/a/36226006/116509

Мой оригинальный ответ:

Вы можете использовать execute для такого рода низкоуровневых операций. В этом фрагменте я использовал метод Commons IO copy для копирования входного потока. Вам нужно будет настроить HttpMessageConverterExtractor для ожидаемого ответа.

final InputStream fis = new FileInputStream(new File("c:\\autoexec.bat")); // or whatever
final RequestCallback requestCallback = new RequestCallback() {
     @Override
    public void doWithRequest(final ClientHttpRequest request) throws IOException {
        request.getHeaders().add("Content-type", "application/octet-stream");
        IOUtils.copy(fis, request.getBody());
     }
};
final RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);     
restTemplate.setRequestFactory(requestFactory);     
final HttpMessageConverterExtractor<String> responseExtractor =
    new HttpMessageConverterExtractor<String>(String.class, restTemplate.getMessageConverters());
restTemplate.execute("http://localhost:4000", HttpMethod.POST, requestCallback, responseExtractor);

(Спасибо Базу за то, что он указал, что вам нужно позвонить setBufferRequestBody(false), иначе это не поможет)

person artbristol    schedule 03.04.2013
comment
Разве входной поток не загружается в память таким образом? - person Bax; 15.01.2014
comment
@Bax Нет, он будет загружаться по частям во время копирования. Должно быть достаточно эффективным с точки зрения памяти. - person artbristol; 16.01.2014
comment
doWithRequest(request) вызывается перед request.execute() и не вернется, пока весь поток не будет скопирован в тело запроса. Центр копируется кусками в тело, но из него никто не потребляет, он не работает как труба, я что-то упускаю? - person Bax; 16.01.2014
comment
@Bax doWithRequest находится в анонимном классе; он определен ранее, но не будет вызываться до выполнения (это обратный вызов) - person artbristol; 16.01.2014
comment
Я знаю о семантике Java :) Я говорю о классе RestTemplate, он выполняет переданный вами экземпляр обратного вызова, а затем вызывает метод выполнения - person Bax; 16.01.2014
comment
@Bax, возможно, вы правы ... Я проверил это, и запрос устанавливает заголовок Content-length, что он мог сделать только путем буферизации всего содержимого. Обновлю ответ, когда разберусь. - person artbristol; 16.01.2014
comment
нашел решение, используя *ClientHttpRequestFactory#setBufferRequestBody(false), в свою очередь, генерирует *StreamingClientHttpRequest, который открывает соединение при вызове getBody(). - person Bax; 16.01.2014
comment
мы не можем просто использовать новый ByteArrayResource(inputStream.getBytes()) с ClientHttpRequestFactory#setBufferRequestBody(false), установленным в RestTemplate? а затем просто вызвать RestTemplate#exchange()? - person Max Ch; 23.07.2014
comment
@MaxCh нет метода getBytes для InputStream - person artbristol; 23.07.2014
comment
Это верно, исходный вопрос был написан на Groovy, поэтому я просто следовал ему. Но я хочу сказать, что использование execute может не понадобиться при вызове setBufferRequestBody(false). Таким образом, код может быть проще. Верно? - person Max Ch; 24.07.2014
comment
@artbristol У меня также есть аналогичный вопрос здесь на RestTemplate, в котором у меня есть сомнения относительно ClientHttpRequestFactory реализации. Посмотрим, сможешь ли ты мне помочь, если это возможно. Я застрял на этом некоторое время. Любая помощь будет оценена. - person AKIWEB; 07.09.2014
comment
ах... autoexec.bat. Спасибо за воспоминания - person IcedDante; 17.02.2015

Я думаю, что в приведенном выше ответе есть ненужный код - вам не нужно создавать анонимный внутренний класс RequestCallback, и вам не нужно использовать IOUtils из apache.

Я потратил немного времени на изучение решения, похожего на ваше, и вот что я придумал:

Вы можете достичь своей цели намного проще, используя интерфейс ресурсов Spring и RestTemplate.

RestTemplate restTemplate = new RestTemplate();

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);

File file = new File("/whatever");

HttpEntity<FileSystemResource> requestEntity = new HttpEntity<>(new FileSystemResource(file));
ResponseEntity e = restTemplate.exchange("http://localhost:4000", HttpMethod.POST, requestEntity, Map.class);

(В этом примере предполагается, что ответом, откуда вы отправляете POST, является JSON. Но это можно легко изменить, изменив класс возвращаемого типа... установленный выше на Map.class)

person RuntimeBlairror    schedule 25.03.2016

Единственная часть ответа @artbristol, которая вам действительно нужна, это (которую вы можете настроить как RestTemplate Spring bean):

final RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);     
restTemplate.setRequestFactory(requestFactory);     

После этого я думаю, что просто использовать FileSystemResource в качестве тела запроса будет правильно.

Я также успешно использовал InputStreamResource таким образом для случаев, когда у вас уже есть данные в виде InputStream и вам не нужно использовать их несколько раз.

В моем случае мы заархивировали наши файлы и обернули GZipInputStream в InputStreamResource.

person Ed Brannin    schedule 21.09.2015