通过@Bean提供的RestTemplateBuilder进行流上传会缓冲整个文件

时间:2018-07-20 00:00:54

标签: spring spring-boot streaming reverse-proxy resttemplate

我正在构建用于上载大文件(多个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会导致全文件缓冲?


参考文献:

1 个答案:

答案 0 :(得分:0)

简而言之,Spring Boot作为RestTemplateBuilder提供的@Bean实例包括与执行器/度量相关的拦截器(过滤器)-拦截器接口需要将请求主体缓冲到一个简单的byte[]

如果您从头实例化自己的RestTemplateBuilderRestTemplate,则默认情况下将不包括该变量。


我似乎是唯一访问此帖子的人,但是以防万一在我提出完整解决方案之前可以帮助某人,我发现了一个重要线索:

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 {...

这表明如果InterceptingClientHttpRequestFactory的列表不为空,则使用interceptors

/**
 * 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 {