Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove buffering in ClientHttpRequestFactory implementations #30557

Closed
poutsma opened this issue May 30, 2023 · 1 comment
Closed

Remove buffering in ClientHttpRequestFactory implementations #30557

poutsma opened this issue May 30, 2023 · 1 comment
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@poutsma
Copy link
Contributor

poutsma commented May 30, 2023

The ClientHttpRequestFactory hierarchy provides an HTTP client libraries abstraction to the RestTemplate. This abstraction was introduced in Spring Framework 3.0, when memory constraints are different than they are today. In particular, most ClientHttpRequestFactory implementations buffer the entire request body in memory before sending it on the wire. Because the ClientHttpRequest exposes the request body through a OutputStream, and most HTTP client libraries do not expose such a stream, a ByteArrayOuputStream is used, converted to a byte array that is sent as request body.

Instead of exposing an output stream, which necessitates buffering the request body, we can store a callback that writes to the HTTP output stream, and use that callback when the HTTP connection has been made. In Spring Framework 4 we introduced the StreamingHttpOutputMessage to support this alternative, but this interface was only implemented by the Apache HttpClient request factory.

We should review request buffering in the ClientHttpRequestFactory hierarchy, and make sure that implementations can support the StreamingHttpOutputMessage do so.

@zaenk
Copy link

zaenk commented Jun 6, 2024

This change breaks implementations where RestTemplate is initialised with HttpClients.createMinimal(HttpClientConnectionManager).

These implementations would fail with this excpetion

Exception in thread "DefaultDispatcher-worker-1" java.lang.NullPointerException: Target host                                                                                                                                                         
    at java.base/java.util.Objects.requireNonNull(Unknown Source)                                                                                                                                                                                    
    at org.apache.hc.core5.util.Args.notNull(Args.java:169)                                                                                                                                                                                          
    at org.apache.hc.client5.http.impl.classic.MinimalHttpClient.doExecute(MinimalHttpClient.java:115)                                                                                                                                               
    at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:87)                                                                                                                                              
    at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:55)                                                                                                                                              
    at org.apache.hc.client5.http.classic.HttpClient.executeOpen(HttpClient.java:183)                                                                                                                                                                
    at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:95)                                                                                                                      
    at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:70)                                                                                                                
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)                                                                                                                                          
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:889)                                                                                                                                                                  
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:790)                                                                                                                                                                    
    at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:507)                                                                                                                                                              

The issue arised when updating from Spring Boot 3.0.x to 3.2.x.

The cause of this is that the MiminalHttpClient does not fall back to inspecting the request object for the target host as InternalHttpClient does.

The workaround is simple - instead fo MinimalHttpClient, use HttpClientBuilder:

HttpClientBuilder.create()
            .setConnectionManager(httpClientManager)
            .build()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants