I'm building a reverse-proxy for uploading large files (multiple gigabytes), and therefore want to use a streaming model that does not buffer entire files. Large buffers would introduce latency and, more importantly, they could result in out-of-memory errors.
My client class contains
@Autowired private RestTemplate restTemplate;
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
int REST_TEMPLATE_MODE = 1; // 1=streams, 2=streams, 3=buffers
return
REST_TEMPLATE_MODE == 1 ? new RestTemplate() :
REST_TEMPLATE_MODE == 2 ? (new RestTemplateBuilder()).build() :
REST_TEMPLATE_MODE == 3 ? restTemplateBuilder.build() : null;
}
and
public void upload_via_streaming(InputStream inputStream, String originalname) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
InputStreamResource inputStreamResource = new InputStreamResource(inputStream) {
@Override public String getFilename() { return originalname; }
@Override public long contentLength() { return -1; }
};
MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>();
body.add("myfile", inputStreamResource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,headers);
String response = restTemplate.postForObject(UPLOAD_URL, requestEntity, String.class);
System.out.println("response: "+response);
}
This is working, but notice my REST_TEMPLATE_MODE
value controls whether or not it meets my streaming requirement.
Question: Why does REST_TEMPLATE_MODE == 3
result in full-file buffering?
References:
- How to forward large files with RestTemplate?
- How to send Multipart form data with restTemplate Spring-mvc
- Spring - How to stream large multipart file uploads to database without storing on local file system -- establishing the InputStream
- How to autowire RestTemplate using annotations
- Design notes and usage caveats, also:
restTemplate
does not support streaming downloads
In short, the instance of
RestTemplateBuilder
provided as an@Bean
by Spring Boot includes an interceptor (filter) associated with actuator/metrics -- and the interceptor interface requires buffering of the request body into a simplebyte[]
.If you instantiate your own
RestTemplateBuilder
orRestTemplate
from scratch, it won't include this by default.I seem to be the only person visiting this post, but just in case it helps someone before I get around to posting a complete solution, I've found a big clue:
displays...
If I clear the interceptor list via
setInterceptors
, it solves the problem. Furthermore, I found that any interceptor, even if it only performs a NOP, will introduce full-file buffering.public class SimpleClientHttpRequestFactory { ...
I have explicitly set
bufferRequestBody = false
, but apparently this code is bypassed if interceptors are used. This would have been nice to know earlier...public abstract class InterceptingHttpAccessor extends HttpAccessor { ...
This shows that the
InterceptingClientHttpRequestFactory
is used if the list ofinterceptors
is not empty.class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { ...
The interfaces make it clear that using
InterceptingClientHttpRequest
requires bufferingbody
to abyte[]
. There is not an option to use a streaming interface.