Вызов Java HTTP API — ввод и вывод данных потоком

#java #spring-boot #httpclient #spring-resttemplate

#java #весенняя загрузка #httpclient #spring-resttemplate

Вопрос:

У меня есть служба передачи файлов, написанная на Java и Spring boot, где она передает файлы из источника в пункт назначения без какого-либо локального хранилища.

Он получает дескрипторы InputStream из источника и OutputStream для назначения и эффективно выполняет следующие действия для потоковой передачи файла.

 IOUtils.copy(inputStream, outputStream);
  
 Destination InputStream -> IOUtils.copy -> Destination OutputStream
  

Теперь у меня есть другое требование, когда мне нужно вызвать API шифрования, который принимает поток приложений / oct-ect и возвращает то же самое. API шифрует данные по мере того, как клиент передает данные, и одновременно возвращает данные в виде потока. Я протестировал API с помощью curl, используя следующую команду:

 curl -v -X POST -H "Content-Type:application/octet-stream" -H "Accept:application/octet-stream" --data-binary @test-file -o test-file.enc http://127.0.0.1:1234/encrypt/test-file
  

Теперь мне нужно интегрировать этот API шифрования с моей оригинальной службой передачи файлов, где я могу получить дескриптор исходного входного потока, передать его в запрос API, получить входной поток из ответа API и включить это в мой оригинальный вызов IOUtils.copy для передачи данных назначению:

 Destination InputStream -> Encryption API -> Encrypted InputStream -> IOUtils.copy -> Destination OutputStream
  

Для реализации вызова API шифрования изначально я пытаюсь создать клиент и передать FileInputStream и извлечь ответ в FileOutputStream. Я пробовал как Apache HttpClient, так и Spring RestTemplate. В обоих случаях с файлами меньшего размера все работает как ожидалось, однако с большими файлами API шифрования получает только часть данных, и, похоже, они там застряли. Исходный вызывающий абонент (служба передачи файлов) не получает никакого ответа и также зависает. Поведение идентично в обеих реализациях.

Реализация с HttpClient:

 InputStream unencryptedInputStream = new FileInputStream("test-file");
HttpClient httpclient = HttpClients.createDefault();

URIBuilder builder = new URIBuilder("http://127.0.0.1:1234/encrypt/test-file");

HttpPost request = new HttpPost(builder.build());
request.setHeader("Content-Type", "application/octet-stream");

InputStreamEntity inputStreamEntity = new InputStreamEntity(unencryptedInputStream, 1064041488);
inputStreamEntity.setContentType("application/octet-stream");
inputStreamEntity.setChunked(true);

request.setEntity(inputStreamEntity );

HttpResponse response = httpclient.execute(request);
HttpEntity entity = response.getEntity();

InputStream encryptedInputStream = entity.getContent();

OutputStream os = new FileOutputStream("test-file.enc");
IOUtils.copy(encryptedInputStream, os);
  

Реализация с помощью Spring RestTemplate:

 RestTemplate restTemplate = new RestTemplate();

InputStream unencryptedInputStream = new FileInputStream("test-file");

OutputStream encryptedOutputStream = new FileOutputStream("test-file.enc");

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

RequestCallback requestCallback = new RequestCallback() {
    @Override
    public void doWithRequest(ClientHttpRequest clientHttpRequest) throws IOException {
        clientHttpRequest.getHeaders().add("Content-type", "application/octet-stream");
        clientHttpRequest.getHeaders().add("Accept", "application/octet-stream");

        IOUtils.copy(unencryptedInputStream, clientHttpRequest.getBody());
    }
};

ResponseExtractor<InputStream> responseExtractor = new ResponseExtractor<InputStream>() {
    @Override
    public InputStream extractData(ClientHttpResponse clientHttpResponse) throws IOException {
        IOUtils.copy(clientHttpResponse.getBody(), encryptedOutputStream);
        return clientHttpResponse.getBody();
    }
};

restTemplate.execute("http://127.0.0.1:1234/encrypt/test-file", HttpMethod.POST, requestCallback, responseExtractor);
  

Оба случая работают с небольшими файлами, но зависают на больших файлах. Я попытался обернуть запрос IOUtils.copy в поток ‘Runnable’, но все та же проблема.

Комментарии:

1. Сколько памяти разрешено использовать вашему приложению? Фактически вам нужно хранить 2 копии файла в памяти. Можно сделать это более эффективным, но не обязательно легко. Я не удивлюсь, если у вас закончилась оперативная память.

2. должен был упомянуть, что у нас есть собственная версия IOUtils.copy, в которой он очищает буфер. Я протестировал файлы объемом 2 ГБ без шифрования и без проблем с производительностью.