Skip to content

Commit be5542a

Browse files
committed
Add buffering predicate to RestTemplate
See gh-33785
1 parent ec48c47 commit be5542a

File tree

4 files changed

+76
-6
lines changed

4 files changed

+76
-6
lines changed

Diff for: spring-web/src/main/java/org/springframework/http/client/support/HttpAccessor.java

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,12 +20,15 @@
2020
import java.net.URI;
2121
import java.util.ArrayList;
2222
import java.util.List;
23+
import java.util.function.BiPredicate;
2324

2425
import org.apache.commons.logging.Log;
26+
import org.jspecify.annotations.Nullable;
2527

2628
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
2729
import org.springframework.http.HttpLogging;
2830
import org.springframework.http.HttpMethod;
31+
import org.springframework.http.client.BufferingClientHttpRequestFactory;
2932
import org.springframework.http.client.ClientHttpRequest;
3033
import org.springframework.http.client.ClientHttpRequestFactory;
3134
import org.springframework.http.client.ClientHttpRequestInitializer;
@@ -57,6 +60,8 @@ public abstract class HttpAccessor {
5760

5861
private final List<ClientHttpRequestInitializer> clientHttpRequestInitializers = new ArrayList<>();
5962

63+
private @Nullable BiPredicate<URI, HttpMethod> bufferingPredicate;
64+
6065

6166
/**
6267
* Set the request factory that this accessor uses for obtaining client request handles.
@@ -78,10 +83,11 @@ public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
7883
* Return the request factory that this accessor uses for obtaining client request handles.
7984
*/
8085
public ClientHttpRequestFactory getRequestFactory() {
81-
return this.requestFactory;
86+
return (this.bufferingPredicate != null ?
87+
new BufferingClientHttpRequestFactory(this.requestFactory, this.bufferingPredicate) :
88+
this.requestFactory);
8289
}
8390

84-
8591
/**
8692
* Set the request initializers that this accessor should use.
8793
* <p>The initializers will get immediately sorted according to their
@@ -111,6 +117,25 @@ public List<ClientHttpRequestInitializer> getClientHttpRequestInitializers() {
111117
return this.clientHttpRequestInitializers;
112118
}
113119

120+
/**
121+
* Enable buffering of request and response, aggregating all content before
122+
* it is sent, and making it possible to read the response body repeatedly.
123+
* @param predicate to determine whether to buffer for the given request
124+
* @since 7.0
125+
*/
126+
public void setBufferingPredicate(@Nullable BiPredicate<URI, HttpMethod> predicate) {
127+
this.bufferingPredicate = predicate;
128+
}
129+
130+
/**
131+
* Return the {@link #setBufferingPredicate(BiPredicate) configured} predicate
132+
* to determine whether to buffer request and response content.
133+
* @since 7.0
134+
*/
135+
public @Nullable BiPredicate<URI, HttpMethod> getBufferingPredicate() {
136+
return this.bufferingPredicate;
137+
}
138+
114139
/**
115140
* Create a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}.
116141
* @param url the URL to connect to

Diff for: spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -99,7 +99,8 @@ public ClientHttpRequestFactory getRequestFactory() {
9999
if (!CollectionUtils.isEmpty(interceptors)) {
100100
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
101101
if (factory == null) {
102-
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
102+
factory = new InterceptingClientHttpRequestFactory(
103+
super.getRequestFactory(), interceptors, getBufferingPredicate());
103104
this.interceptingRequestFactory = factory;
104105
}
105106
return factory;

Diff for: spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java

+1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ public DefaultRestClientBuilder(RestTemplate restTemplate) {
185185
if (!CollectionUtils.isEmpty(restTemplate.getInterceptors())) {
186186
this.interceptors = new ArrayList<>(restTemplate.getInterceptors());
187187
}
188+
this.bufferingPredicate = restTemplate.getBufferingPredicate();
188189
if (!CollectionUtils.isEmpty(restTemplate.getClientHttpRequestInitializers())) {
189190
this.initializers = new ArrayList<>(restTemplate.getClientHttpRequestInitializers());
190191
}

Diff for: spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -52,10 +52,13 @@
5252
import org.springframework.http.converter.GenericHttpMessageConverter;
5353
import org.springframework.http.converter.HttpMessageConverter;
5454
import org.springframework.http.converter.SmartHttpMessageConverter;
55+
import org.springframework.http.converter.StringHttpMessageConverter;
5556
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
5657
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
58+
import org.springframework.util.FileCopyUtils;
5759
import org.springframework.web.util.DefaultUriBuilderFactory;
5860

61+
import static java.nio.charset.StandardCharsets.UTF_8;
5962
import static org.assertj.core.api.Assertions.assertThat;
6063
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
6164
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -765,6 +768,46 @@ void requestInterceptorCanAddExistingHeaderValueWithBody() throws Exception {
765768
verify(response).close();
766769
}
767770

771+
@Test
772+
void requestInterceptorWithBuffering() throws Exception {
773+
try (MockWebServer server = new MockWebServer()) {
774+
server.enqueue(new MockResponse().setResponseCode(200).setBody("Hello Spring!"));
775+
server.start();
776+
template.setRequestFactory(new SimpleClientHttpRequestFactory());
777+
template.setInterceptors(List.of((request, body, execution) -> {
778+
ClientHttpResponse response = execution.execute(request, body);
779+
byte[] result = FileCopyUtils.copyToByteArray(response.getBody());
780+
assertThat(result).isEqualTo("Hello Spring!".getBytes(UTF_8));
781+
return response;
782+
}));
783+
template.setBufferingPredicate((uri, httpMethod) -> true);
784+
template.setMessageConverters(List.of(new StringHttpMessageConverter()));
785+
String result = template.getForObject(server.url("/").uri(), String.class);
786+
assertThat(server.getRequestCount()).isEqualTo(1);
787+
assertThat(result).isEqualTo("Hello Spring!");
788+
}
789+
}
790+
791+
@Test
792+
void buffering() throws Exception {
793+
try (MockWebServer server = new MockWebServer()) {
794+
server.enqueue(new MockResponse().setResponseCode(200).setBody("Hello Spring!"));
795+
server.start();
796+
template.setRequestFactory(new SimpleClientHttpRequestFactory());
797+
template.setBufferingPredicate((uri, httpMethod) -> true);
798+
template.setMessageConverters(List.of(new StringHttpMessageConverter()));
799+
String result = template.execute(server.url("/").uri(), HttpMethod.GET, req -> {}, response -> {
800+
byte[] bytes = FileCopyUtils.copyToByteArray(response.getBody());
801+
assertThat(bytes).isEqualTo("Hello Spring!".getBytes(UTF_8));
802+
bytes = FileCopyUtils.copyToByteArray(response.getBody());
803+
assertThat(bytes).isEqualTo("Hello Spring!".getBytes(UTF_8));
804+
return new String(bytes, UTF_8);
805+
});
806+
assertThat(server.getRequestCount()).isEqualTo(1);
807+
assertThat(result).isEqualTo("Hello Spring!");
808+
}
809+
}
810+
768811
@Test
769812
void clientHttpRequestInitializerAndRequestInterceptorAreBothApplied() throws Exception {
770813
ClientHttpRequestInitializer initializer = request ->

0 commit comments

Comments
 (0)