Browse Source

Add buffering predicate to RestTemplate

See gh-33785
pull/34242/head
rstoyanchev 1 year ago
parent
commit
be5542a7a7
  1. 31
      spring-web/src/main/java/org/springframework/http/client/support/HttpAccessor.java
  2. 5
      spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java
  3. 1
      spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java
  4. 45
      spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java

31
spring-web/src/main/java/org/springframework/http/client/support/HttpAccessor.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,12 +20,15 @@ import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.BiPredicate;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.jspecify.annotations.Nullable;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.HttpLogging; import org.springframework.http.HttpLogging;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInitializer; import org.springframework.http.client.ClientHttpRequestInitializer;
@ -57,6 +60,8 @@ public abstract class HttpAccessor {
private final List<ClientHttpRequestInitializer> clientHttpRequestInitializers = new ArrayList<>(); private final List<ClientHttpRequestInitializer> clientHttpRequestInitializers = new ArrayList<>();
private @Nullable BiPredicate<URI, HttpMethod> bufferingPredicate;
/** /**
* Set the request factory that this accessor uses for obtaining client request handles. * Set the request factory that this accessor uses for obtaining client request handles.
@ -78,10 +83,11 @@ public abstract class HttpAccessor {
* Return the request factory that this accessor uses for obtaining client request handles. * Return the request factory that this accessor uses for obtaining client request handles.
*/ */
public ClientHttpRequestFactory getRequestFactory() { public ClientHttpRequestFactory getRequestFactory() {
return this.requestFactory; return (this.bufferingPredicate != null ?
new BufferingClientHttpRequestFactory(this.requestFactory, this.bufferingPredicate) :
this.requestFactory);
} }
/** /**
* Set the request initializers that this accessor should use. * Set the request initializers that this accessor should use.
* <p>The initializers will get immediately sorted according to their * <p>The initializers will get immediately sorted according to their
@ -111,6 +117,25 @@ public abstract class HttpAccessor {
return this.clientHttpRequestInitializers; return this.clientHttpRequestInitializers;
} }
/**
* Enable buffering of request and response, aggregating all content before
* it is sent, and making it possible to read the response body repeatedly.
* @param predicate to determine whether to buffer for the given request
* @since 7.0
*/
public void setBufferingPredicate(@Nullable BiPredicate<URI, HttpMethod> predicate) {
this.bufferingPredicate = predicate;
}
/**
* Return the {@link #setBufferingPredicate(BiPredicate) configured} predicate
* to determine whether to buffer request and response content.
* @since 7.0
*/
public @Nullable BiPredicate<URI, HttpMethod> getBufferingPredicate() {
return this.bufferingPredicate;
}
/** /**
* Create a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}. * Create a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}.
* @param url the URL to connect to * @param url the URL to connect to

5
spring-web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -99,7 +99,8 @@ public abstract class InterceptingHttpAccessor extends HttpAccessor {
if (!CollectionUtils.isEmpty(interceptors)) { if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory; ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) { if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors); factory = new InterceptingClientHttpRequestFactory(
super.getRequestFactory(), interceptors, getBufferingPredicate());
this.interceptingRequestFactory = factory; this.interceptingRequestFactory = factory;
} }
return factory; return factory;

1
spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java

@ -185,6 +185,7 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
if (!CollectionUtils.isEmpty(restTemplate.getInterceptors())) { if (!CollectionUtils.isEmpty(restTemplate.getInterceptors())) {
this.interceptors = new ArrayList<>(restTemplate.getInterceptors()); this.interceptors = new ArrayList<>(restTemplate.getInterceptors());
} }
this.bufferingPredicate = restTemplate.getBufferingPredicate();
if (!CollectionUtils.isEmpty(restTemplate.getClientHttpRequestInitializers())) { if (!CollectionUtils.isEmpty(restTemplate.getClientHttpRequestInitializers())) {
this.initializers = new ArrayList<>(restTemplate.getClientHttpRequestInitializers()); this.initializers = new ArrayList<>(restTemplate.getClientHttpRequestInitializers());
} }

45
spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -52,10 +52,13 @@ import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.http.converter.SmartHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter; import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.util.DefaultUriBuilderFactory; import org.springframework.web.util.DefaultUriBuilderFactory;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@ -765,6 +768,46 @@ class RestTemplateTests {
verify(response).close(); verify(response).close();
} }
@Test
void requestInterceptorWithBuffering() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.enqueue(new MockResponse().setResponseCode(200).setBody("Hello Spring!"));
server.start();
template.setRequestFactory(new SimpleClientHttpRequestFactory());
template.setInterceptors(List.of((request, body, execution) -> {
ClientHttpResponse response = execution.execute(request, body);
byte[] result = FileCopyUtils.copyToByteArray(response.getBody());
assertThat(result).isEqualTo("Hello Spring!".getBytes(UTF_8));
return response;
}));
template.setBufferingPredicate((uri, httpMethod) -> true);
template.setMessageConverters(List.of(new StringHttpMessageConverter()));
String result = template.getForObject(server.url("/").uri(), String.class);
assertThat(server.getRequestCount()).isEqualTo(1);
assertThat(result).isEqualTo("Hello Spring!");
}
}
@Test
void buffering() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.enqueue(new MockResponse().setResponseCode(200).setBody("Hello Spring!"));
server.start();
template.setRequestFactory(new SimpleClientHttpRequestFactory());
template.setBufferingPredicate((uri, httpMethod) -> true);
template.setMessageConverters(List.of(new StringHttpMessageConverter()));
String result = template.execute(server.url("/").uri(), HttpMethod.GET, req -> {}, response -> {
byte[] bytes = FileCopyUtils.copyToByteArray(response.getBody());
assertThat(bytes).isEqualTo("Hello Spring!".getBytes(UTF_8));
bytes = FileCopyUtils.copyToByteArray(response.getBody());
assertThat(bytes).isEqualTo("Hello Spring!".getBytes(UTF_8));
return new String(bytes, UTF_8);
});
assertThat(server.getRequestCount()).isEqualTo(1);
assertThat(result).isEqualTo("Hello Spring!");
}
}
@Test @Test
void clientHttpRequestInitializerAndRequestInterceptorAreBothApplied() throws Exception { void clientHttpRequestInitializerAndRequestInterceptorAreBothApplied() throws Exception {
ClientHttpRequestInitializer initializer = request -> ClientHttpRequestInitializer initializer = request ->

Loading…
Cancel
Save