spring - 通过@Bean 提供的 RestTemplateBuilder 流式上传缓冲完整文件

标签 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 value 控制它是否满足我的流媒体要求。

问题 : 为什么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
  • 最佳答案

    简而言之,RestTemplateBuilder 的实例作为 @Bean 提供Spring Boot 包含一个与执行器/指标相关联的拦截器(过滤器)——并且拦截器接口(interface)需要将请求主体缓冲为一个简单的 byte[] .

    如果你实例化你自己的 RestTemplateBuilderRestTemplate从头开始,默认情况下它不会包含此内容。

    我似乎是唯一一个访问这篇文章的人,但以防万一它在我发布完整的解决方案之前对某人有所帮助,我发现了一个很大的线索:

    restTemplate.getInterceptors().forEach(item->System.out.println(item));
    

    显示...
    org.SF.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor
    

    如果我通过 setInterceptors 清除拦截器列表,它解决了问题。此外,我发现任何拦截器,即使它只执行 NOP,也会引入全文件缓冲。

    公共(public)类 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);
        }
    }
    

    公共(public)抽象类 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 { ...

    接口(interface)清楚地表明使用 InterceptingClientHttpRequest需要缓冲 bodybyte[] .没有使用流接口(interface)的选项。
        @Override
        public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
    

    关于spring - 通过@Bean 提供的 RestTemplateBuilder 流式上传缓冲完整文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51433306/

    相关文章:

    Java:如何获取上传和下载速度

    spring - 如何通过 ServletContextListener 的上下文查找访问 EJB bean

    java - Spring Boot 测试数据库初始化运行两次

    JAVA:如何根据属性值合并 arrayList 中的对象?

    spring - Maven/Spring 未正确运行 jmeter 测试

    ios - iTunes 如何解析 m3u 文件

    java - springboot java oracle过程调用游标

    java - 从 YAML 属性文件实例化 POJO (LocalDateTime)(Java 和 Spring Boot)

    java - 用于 AsyncHttpClient 的 RestTemplate

    c++ - QT中如何播放rtsp流