-
Notifications
You must be signed in to change notification settings - Fork 38.4k
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
RestClient
calls interceptors from RestTemplate
twice
#32038
Comments
RestClient
calls interceptors from RestTemplate
twice
Hi @tilm4nn, Congratulations on reporting your first issue for the Spring Framework! 👍 Thanks for the detailed explanation and the test that reproduces the behavior. We'll look into it. |
@Test
void restclient_should_calls_interceptors_from_resttemplate_only_once() {
AtomicInteger calls = new AtomicInteger();
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(List.of((request, body, execution) -> {
calls.incrementAndGet();
return execution.execute(request, body);
}));
RestClient restClient = RestClient.create(restTemplate);
try {
restClient.get().uri("https://localhost:8080/foobar").retrieve();
} catch (ResourceAccessException e) { }
Assertions.assertEquals(1, calls.get()); // ❌ assertion failed, expected: <1> but was: <2>
// It means, interceptors are called twice
} Thanks @tilm4nn for the detailed explanation! your reproduces works well with me too. Root cause:
|
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors); |
RestTemplate.getRequestFactory()
returnInterceptingClientHttpRequestFactory(interceptors)
. it contains interceptors
Lines 180 to 185 in e1c8a84
this.requestFactory = restTemplate.getRequestFactory(); | |
this.messageConverters = new ArrayList<>(restTemplate.getMessageConverters()); | |
if (!CollectionUtils.isEmpty(restTemplate.getInterceptors())) { | |
this.interceptors = new ArrayList<>(restTemplate.getInterceptors()); | |
} |
- But on
RestClientBuilder(RestTemplate)
, we copyinterceptors
again that already contained inInterceptingClientHttpRequestFactory
fromrestTemplate.getRequestFactory()
spring-framework/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java
Line 521 in e1c8a84
factory = new InterceptingClientHttpRequestFactory(DefaultRestClient.this.clientRequestFactory, DefaultRestClient.this.interceptors); |
- Finally,
RestClient.createRequest()
usesinterceptors
andInterceptingClientHttpRequestFactory
fromrestTemplate.getRequestFactory()
that already containsinterceptors
, so sameinterceptors
fromRestTemplate
called twice!
My simple proposal
Lines 183 to 185 in e1c8a84
if (!CollectionUtils.isEmpty(restTemplate.getInterceptors())) { | |
this.interceptors = new ArrayList<>(restTemplate.getInterceptors()); | |
} |
if (!CollectionUtils.isEmpty(restTemplate.getInterceptors())) {
// 👇👇 If RestTemplate's requestFactory already contains interceptors, don't copy it
if (!(this.requestFactory instanceof InterceptingClientHttpRequestFactory)) {
this.interceptors = new ArrayList<>(restTemplate.getInterceptors());
}
}
With above code, we don't copy RestTemplate's interceptors
when restTemplate.requestFactory
already contains that interceptors
.
And I checked test also passed with above code.
So simply check my investigation and share your opinion~! I'm also ready to open PR. thanks a lot! cc. @sbrannen 🙇
Hi @injae-kim, thank you very much for your investigation and proposal. I think the idea of the proposed solution is clear and it might be achieved in a simpler way. I can imagine only two possible paths:
So as the outcome is always "we do not copy the interceptors list" wouldn't it be possible to just skip the copy of the interceptors list alltogether? Or am I missing something here? One additional case comes to my mind: var builder = RestClient.builder(restTemplateWithInterceptors);
builder.requestFactory(someSpecialRequestFactory)
// Here we have lost the interceptors of RestTemplate's InterceptingClientHttpRequestFactory and have to add them manually
builder.requestInterceptors(interceptors -> interceptors.addAll(restTemplateWithInterceptors.getInterceptors())) But I think one can argue that the manual step is justified in this case. |
And here is another workaround that also works independent of how the RestTemplate is used otherwise: RestClient.builder(restTemplate).requestInterceptors(List::clear).build(); |
Hi @tilm4nn ! as you suggested above, we can fix this issue by "just skip the copy of the interceptors list from RestTemplate".
But I worry about this case. in the future I think we can change So let's wait maintainer's opinion about this~ thanks! 😃 |
@tilm4nn Thanks for reporting this, it should now be fixed. @injae-kim Changing what |
aha~ I understood. thanks for the kind explanation! I agree with your fix. it looks more explicit way! 👍 |
This has been observed using spring-boot 3.2.1 with spring-web 6.1.2
When using
RestClient.create(RestTemplate)
and providing aRestTemplate
instance that holds anyHttpClientRequestInerceptors
,RestClient
copies the interceptor list and creates an ownInterceptingClientHttpRequestFactory
with the copied list.But it also uses the
InterceptingClientHttpRequestFactory
obtained fromRestTemplate.getRequestFactory()
containing the (same) interceptors from theRestTemplate
to create the requests.Now when creating a request, a request chain containing two different instances of
InterceptingClientHttpRequest
each with an equal interceptor list are created and thus each interceptor is called twice during request execution.There is a workaround, when you do not need to reuse the
RestTemplate
:Complete code to reproduce:
The text was updated successfully, but these errors were encountered: