Skip to content

Commit eb91a92

Browse files
committed
Merge pull request #35914 from fcappi
* pr/35914: Polish 'Apply SslConfigurer in addition to configured mappers' Apply SslConfigurer in addition to configured mappers Closes gh-35914
2 parents b770ffc + 3c7fbf3 commit eb91a92

File tree

4 files changed

+154
-10
lines changed

4 files changed

+154
-10
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorClientHttpConnectorFactory.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package org.springframework.boot.autoconfigure.web.reactive.function.client;
1818

19+
import java.util.ArrayList;
20+
import java.util.List;
1921
import java.util.function.Supplier;
22+
import java.util.stream.Collectors;
2023
import java.util.stream.Stream;
2124

2225
import javax.net.ssl.SSLException;
@@ -36,6 +39,7 @@
3639
* {@link ClientHttpConnectorFactory} for {@link ReactorClientHttpConnector}.
3740
*
3841
* @author Phillip Webb
42+
* @author Fernando Cappi
3943
*/
4044
class ReactorClientHttpConnectorFactory implements ClientHttpConnectorFactory<ReactorClientHttpConnector> {
4145

@@ -55,28 +59,28 @@ class ReactorClientHttpConnectorFactory implements ClientHttpConnectorFactory<Re
5559

5660
@Override
5761
public ReactorClientHttpConnector createClientHttpConnector(SslBundle sslBundle) {
58-
ReactorNettyHttpClientMapper mapper = this.mappers.get()
59-
.reduce((before, after) -> (client) -> after.configure(before.configure(client)))
60-
.orElse((client) -> client);
62+
List<ReactorNettyHttpClientMapper> mappers = this.mappers.get()
63+
.collect(Collectors.toCollection(ArrayList::new));
6164
if (sslBundle != null) {
62-
mapper = new SslConfigurer(sslBundle)::configure;
65+
mappers.add(new SslConfigurer(sslBundle));
6366
}
64-
return new ReactorClientHttpConnector(this.reactorResourceFactory, mapper::configure);
65-
67+
return new ReactorClientHttpConnector(this.reactorResourceFactory,
68+
ReactorNettyHttpClientMapper.of(mappers)::configure);
6669
}
6770

6871
/**
6972
* Configures the Netty {@link HttpClient} with SSL.
7073
*/
71-
private static class SslConfigurer {
74+
private static class SslConfigurer implements ReactorNettyHttpClientMapper {
7275

7376
private final SslBundle sslBundle;
7477

7578
SslConfigurer(SslBundle sslBundle) {
7679
this.sslBundle = sslBundle;
7780
}
7881

79-
HttpClient configure(HttpClient httpClient) {
82+
@Override
83+
public HttpClient configure(HttpClient httpClient) {
8084
return httpClient.secure(ThrowingConsumer.of(this::customizeSsl).throwing(IllegalStateException::new));
8185
}
8286

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ReactorNettyHttpClientMapper.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2023 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.
@@ -16,15 +16,19 @@
1616

1717
package org.springframework.boot.autoconfigure.web.reactive.function.client;
1818

19+
import java.util.Collection;
20+
1921
import reactor.netty.http.client.HttpClient;
2022

2123
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
24+
import org.springframework.util.Assert;
2225

2326
/**
2427
* Mapper that allows for custom modification of a {@link HttpClient} before it is used as
2528
* the basis for a {@link ReactorClientHttpConnector}.
2629
*
2730
* @author Brian Clozel
31+
* @author Phillip Webb
2832
* @since 2.3.0
2933
*/
3034
@FunctionalInterface
@@ -37,4 +41,31 @@ public interface ReactorNettyHttpClientMapper {
3741
*/
3842
HttpClient configure(HttpClient httpClient);
3943

44+
/**
45+
* Return a new {@link ReactorNettyHttpClientMapper} composed of the given mappers.
46+
* @param mappers the mappers to compose
47+
* @return a composed {@link ReactorNettyHttpClientMapper} instance
48+
* @since 3.1.1
49+
*/
50+
static ReactorNettyHttpClientMapper of(Collection<ReactorNettyHttpClientMapper> mappers) {
51+
Assert.notNull(mappers, "Mappers must not be null");
52+
return of(mappers.toArray(ReactorNettyHttpClientMapper[]::new));
53+
}
54+
55+
/**
56+
* Return a new {@link ReactorNettyHttpClientMapper} composed of the given mappers.
57+
* @param mappers the mappers to compose
58+
* @return a composed {@link ReactorNettyHttpClientMapper} instance
59+
* @since 3.1.1
60+
*/
61+
static ReactorNettyHttpClientMapper of(ReactorNettyHttpClientMapper... mappers) {
62+
Assert.notNull(mappers, "Mappers must not be null");
63+
return (httpClient) -> {
64+
for (ReactorNettyHttpClientMapper mapper : mappers) {
65+
httpClient = mapper.configure(httpClient);
66+
}
67+
return httpClient;
68+
};
69+
}
70+
4071
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorFactoryConfigurationTests.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
import org.junit.jupiter.api.Test;
2525

2626
import org.springframework.boot.autoconfigure.AutoConfigurations;
27+
import org.springframework.boot.ssl.SslBundle;
28+
import org.springframework.boot.ssl.SslBundleKey;
29+
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
30+
import org.springframework.boot.ssl.jks.JksSslStoreDetails;
2731
import org.springframework.boot.test.context.FilteredClassLoader;
2832
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
2933
import org.springframework.context.annotation.Bean;
@@ -34,6 +38,8 @@
3438

3539
import static org.assertj.core.api.Assertions.assertThat;
3640
import static org.mockito.Mockito.mock;
41+
import static org.mockito.Mockito.spy;
42+
import static org.mockito.Mockito.verify;
3743

3844
/**
3945
* Tests for {@link ClientHttpConnectorFactoryConfiguration}.
@@ -80,12 +86,16 @@ private JettyClientHttpConnectorFactory getJettyClientHttpConnectorFactory(
8086

8187
@Test
8288
void shouldApplyHttpClientMapper() {
89+
JksSslStoreDetails storeDetails = JksSslStoreDetails.forLocation("classpath:test.jks");
90+
JksSslStoreBundle stores = new JksSslStoreBundle(storeDetails, storeDetails);
91+
SslBundle sslBundle = spy(SslBundle.of(stores, SslBundleKey.of("password")));
8392
new ReactiveWebApplicationContextRunner()
8493
.withConfiguration(AutoConfigurations.of(ClientHttpConnectorFactoryConfiguration.ReactorNetty.class))
8594
.withUserConfiguration(CustomHttpClientMapper.class)
8695
.run((context) -> {
87-
context.getBean(ReactorClientHttpConnectorFactory.class).createClientHttpConnector();
96+
context.getBean(ReactorClientHttpConnectorFactory.class).createClientHttpConnector(sslBundle);
8897
assertThat(CustomHttpClientMapper.called).isTrue();
98+
verify(sslBundle).getManagers();
8999
});
90100
}
91101

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.web.reactive.function.client;
18+
19+
import java.util.Collection;
20+
import java.util.List;
21+
22+
import org.junit.jupiter.api.Test;
23+
import reactor.netty.http.client.HttpClient;
24+
import reactor.netty.http.client.HttpClientConfig;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
28+
29+
/**
30+
* Tests for {@link ReactorNettyHttpClientMapper}.
31+
*
32+
* @author Phillip Webb
33+
*/
34+
class ReactorNettyHttpClientMapperTests {
35+
36+
@Test
37+
void ofWithCollectionCreatesComposite() {
38+
ReactorNettyHttpClientMapper one = (httpClient) -> new TestHttpClient(httpClient, "1");
39+
ReactorNettyHttpClientMapper two = (httpClient) -> new TestHttpClient(httpClient, "2");
40+
ReactorNettyHttpClientMapper three = (httpClient) -> new TestHttpClient(httpClient, "3");
41+
ReactorNettyHttpClientMapper compose = ReactorNettyHttpClientMapper.of(List.of(one, two, three));
42+
TestHttpClient httpClient = (TestHttpClient) compose.configure(new TestHttpClient());
43+
assertThat(httpClient.getContent()).isEqualTo("123");
44+
}
45+
46+
@Test
47+
void ofWhenCollectionIsNullThrowsException() {
48+
Collection<ReactorNettyHttpClientMapper> mappers = null;
49+
assertThatIllegalArgumentException().isThrownBy(() -> ReactorNettyHttpClientMapper.of(mappers))
50+
.withMessage("Mappers must not be null");
51+
}
52+
53+
@Test
54+
void ofWithArrayCreatesComposite() {
55+
ReactorNettyHttpClientMapper one = (httpClient) -> new TestHttpClient(httpClient, "1");
56+
ReactorNettyHttpClientMapper two = (httpClient) -> new TestHttpClient(httpClient, "2");
57+
ReactorNettyHttpClientMapper three = (httpClient) -> new TestHttpClient(httpClient, "3");
58+
ReactorNettyHttpClientMapper compose = ReactorNettyHttpClientMapper.of(one, two, three);
59+
TestHttpClient httpClient = (TestHttpClient) compose.configure(new TestHttpClient());
60+
assertThat(httpClient.getContent()).isEqualTo("123");
61+
}
62+
63+
@Test
64+
void ofWhenArrayIsNullThrowsException() {
65+
ReactorNettyHttpClientMapper[] mappers = null;
66+
assertThatIllegalArgumentException().isThrownBy(() -> ReactorNettyHttpClientMapper.of(mappers))
67+
.withMessage("Mappers must not be null");
68+
}
69+
70+
private static class TestHttpClient extends HttpClient {
71+
72+
private final String content;
73+
74+
TestHttpClient() {
75+
this.content = "";
76+
}
77+
78+
TestHttpClient(HttpClient httpClient, String content) {
79+
this.content = (httpClient instanceof TestHttpClient testHttpClient) ? testHttpClient.content + content
80+
: content;
81+
}
82+
83+
@Override
84+
public HttpClientConfig configuration() {
85+
throw new UnsupportedOperationException("Auto-generated method stub");
86+
}
87+
88+
@Override
89+
protected HttpClient duplicate() {
90+
throw new UnsupportedOperationException("Auto-generated method stub");
91+
}
92+
93+
String getContent() {
94+
return this.content;
95+
}
96+
97+
}
98+
99+
}

0 commit comments

Comments
 (0)