通过@Bean 提供的 RestTemplateBuilder 流式上传缓冲完整文件
Posted
技术标签:
【中文标题】通过@Bean 提供的 RestTemplateBuilder 流式上传缓冲完整文件【英文标题】:Streaming upload via @Bean-provided RestTemplateBuilder buffers full file 【发布时间】:2018-12-28 05:17:48 【问题描述】:我正在构建一个用于上传大文件(数 GB)的反向代理,因此希望使用不缓冲整个文件的流模型。大缓冲区会引入延迟,更重要的是,它们可能会导致内存不足错误。
我的客户端类包含
@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;
和
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);
这是可行的,但请注意我的 REST_TEMPLATE_MODE
值控制它是否满足我的流式传输要求。
问题:为什么REST_TEMPLATE_MODE == 3
会导致全文件缓冲?
参考资料:
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 -- 建立 InputStream How to autowire RestTemplate using annotations Design notes and usage caveats, also:restTemplate
does not support streaming downloads
【问题讨论】:
顺便说一句:通过观察以下症状检测到全文件缓冲:延迟和服务器内存配置文件。 相关?:Shouldn't RestTemplate be instantiated per @Service class ? 关于“最适合ClientHttpRequestFactory
”的有趣描述:Class RestTemplateBuilder
我觉得很有趣setBufferRequestBody(false)
不保证这个结果。
可能有用:Spring RestTemplate (setBufferRequestBody as false) does not like MultiValueMap request body, which contains an InputStream object
【参考方案1】:
简而言之,Spring Boot 作为@Bean
提供的RestTemplateBuilder
实例包括一个与执行器/指标相关联的拦截器(过滤器)——并且拦截器接口需要将请求主体缓冲为一个简单的byte[]
.
如果您从头开始实例化自己的 RestTemplateBuilder
或 RestTemplate
,则默认情况下不会包含此内容。
我似乎是唯一访问此帖子的人,但为了防止在我发布完整解决方案之前它对某人有所帮助,我发现了一个重要线索:
restTemplate.getInterceptors().forEach(item->System.out.println(item));
显示...
org.SF.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor
如果我通过setInterceptors
清除拦截器列表,问题就解决了。此外,我发现任何拦截器,即使它只执行 NOP,也会引入全文件缓冲。
公共类 SimpleClientHttpRequestFactory ...
我已明确设置bufferRequestBody = false
,但如果使用拦截器,显然此代码将被绕过。早点知道就好了……
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody)
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
else
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
公共抽象类 InterceptingHttpAccessor 扩展 HttpAccessor ...
这表明如果interceptors
的列表不为空,则使用InterceptingClientHttpRequestFactory
。
/**
* Overridden to expose an @link InterceptingClientHttpRequestFactory
* if necessary.
* @see #getInterceptors()
*/
@Override
public ClientHttpRequestFactory getRequestFactory()
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors))
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null)
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
return factory;
else
return super.getRequestFactory();
类 InterceptingClientHttpRequest 扩展 AbstractBufferingClientHttpRequest ...
接口清楚地表明使用InterceptingClientHttpRequest
需要将body
缓冲到byte[]
。没有使用流接口的选项。
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException
【讨论】:
以上是关于通过@Bean 提供的 RestTemplateBuilder 流式上传缓冲完整文件的主要内容,如果未能解决你的问题,请参考以下文章