Skip to content

Commit 1fb04cb

Browse files
committed
Add support for configuring SslInfo in WebTestClient
Prior to this commit, there was no easy way to configure an SslInfo instance for use with WebTestClient. To address that, this commit introduces a new sslInfo(SslInfo) method in WebTestClient.MockServerSpec, which can be used as follows. var client = WebTestClient.bindToApplicationContext(context) .sslInfo(new MockSslInfo("mock ID")) // ... .build(); Closes gh-35042
1 parent 4375e59 commit 1fb04cb

File tree

5 files changed

+94
-10
lines changed

5 files changed

+94
-10
lines changed

spring-test/src/main/java/org/springframework/test/web/reactive/server/AbstractMockServerSpec.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.jspecify.annotations.Nullable;
2424

25+
import org.springframework.http.server.reactive.SslInfo;
2526
import org.springframework.util.CollectionUtils;
2627
import org.springframework.web.server.WebFilter;
2728
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
@@ -32,6 +33,7 @@
3233
* Base class for implementations of {@link WebTestClient.MockServerSpec}.
3334
*
3435
* @author Rossen Stoyanchev
36+
* @author Sam Brannen
3537
* @since 5.0
3638
* @param <B> a self reference to the builder type
3739
*/
@@ -42,6 +44,8 @@ abstract class AbstractMockServerSpec<B extends WebTestClient.MockServerSpec<B>>
4244

4345
private @Nullable WebSessionManager sessionManager;
4446

47+
private @Nullable SslInfo sslInfo;
48+
4549
private @Nullable List<MockServerConfigurer> configurers;
4650

4751

@@ -66,6 +70,12 @@ public <T extends B> T webSessionManager(WebSessionManager sessionManager) {
6670
return self();
6771
}
6872

73+
@Override
74+
public <T extends B> T sslInfo(SslInfo sslInfo) {
75+
this.sslInfo = sslInfo;
76+
return self();
77+
}
78+
6979
@Override
7080
public <T extends B> T apply(MockServerConfigurer configurer) {
7181
configurer.afterConfigureAdded(this);
@@ -91,7 +101,7 @@ public WebTestClient.Builder configureClient() {
91101
if (!CollectionUtils.isEmpty(this.configurers)) {
92102
this.configurers.forEach(configurer -> configurer.beforeServerCreated(builder));
93103
}
94-
return new DefaultWebTestClientBuilder(builder);
104+
return new DefaultWebTestClientBuilder(builder, this.sslInfo);
95105
}
96106

97107
/**

spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.http.client.reactive.JettyClientHttpConnector;
3333
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
3434
import org.springframework.http.codec.ClientCodecConfigurer;
35+
import org.springframework.http.server.reactive.SslInfo;
3536
import org.springframework.util.Assert;
3637
import org.springframework.util.ClassUtils;
3738
import org.springframework.util.CollectionUtils;
@@ -50,6 +51,7 @@
5051
* Default implementation of {@link WebTestClient.Builder}.
5152
*
5253
* @author Rossen Stoyanchev
54+
* @author Sam Brannen
5355
* @since 5.0
5456
*/
5557
class DefaultWebTestClientBuilder implements WebTestClient.Builder {
@@ -78,6 +80,8 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
7880

7981
private @Nullable ClientHttpConnector connector;
8082

83+
private @Nullable SslInfo sslInfo;
84+
8185
private @Nullable String baseUrl;
8286

8387
private @Nullable UriBuilderFactory uriBuilderFactory;
@@ -103,21 +107,21 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
103107

104108
/** Determine connector via classpath detection. */
105109
DefaultWebTestClientBuilder() {
106-
this(null, null);
110+
this(null, null, null);
107111
}
108112

109113
/** Use HttpHandlerConnector with mock server. */
110-
DefaultWebTestClientBuilder(WebHttpHandlerBuilder httpHandlerBuilder) {
111-
this(httpHandlerBuilder, null);
114+
DefaultWebTestClientBuilder(WebHttpHandlerBuilder httpHandlerBuilder, @Nullable SslInfo sslInfo) {
115+
this(httpHandlerBuilder, null, sslInfo);
112116
}
113117

114118
/** Use given connector. */
115119
DefaultWebTestClientBuilder(ClientHttpConnector connector) {
116-
this(null, connector);
120+
this(null, connector, null);
117121
}
118122

119-
DefaultWebTestClientBuilder(
120-
@Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector) {
123+
private DefaultWebTestClientBuilder(@Nullable WebHttpHandlerBuilder httpHandlerBuilder,
124+
@Nullable ClientHttpConnector connector, @Nullable SslInfo sslInfo) {
121125

122126
Assert.isTrue(httpHandlerBuilder == null || connector == null,
123127
"Expected WebHttpHandlerBuilder or ClientHttpConnector but not both.");
@@ -127,13 +131,15 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
127131
"To use WebTestClient, please add spring-webflux to the test classpath.");
128132

129133
this.connector = connector;
134+
this.sslInfo = sslInfo;
130135
this.httpHandlerBuilder = (httpHandlerBuilder != null ? httpHandlerBuilder.clone() : null);
131136
}
132137

133138
/** Copy constructor. */
134139
DefaultWebTestClientBuilder(DefaultWebTestClientBuilder other) {
135140
this.httpHandlerBuilder = (other.httpHandlerBuilder != null ? other.httpHandlerBuilder.clone() : null);
136141
this.connector = other.connector;
142+
this.sslInfo = other.sslInfo;
137143
this.responseTimeout = other.responseTimeout;
138144

139145
this.baseUrl = other.baseUrl;
@@ -284,7 +290,7 @@ public WebTestClient build() {
284290
ClientHttpConnector connectorToUse = this.connector;
285291
if (connectorToUse == null) {
286292
if (this.httpHandlerBuilder != null) {
287-
connectorToUse = new HttpHandlerConnector(this.httpHandlerBuilder.build());
293+
connectorToUse = new HttpHandlerConnector(this.httpHandlerBuilder.build(), this.sslInfo);
288294
}
289295
}
290296
if (connectorToUse == null) {

spring-test/src/main/java/org/springframework/test/web/reactive/server/HttpHandlerConnector.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
24+
import org.jspecify.annotations.Nullable;
2425
import org.reactivestreams.Publisher;
2526
import reactor.core.publisher.Flux;
2627
import reactor.core.publisher.Mono;
@@ -40,9 +41,11 @@
4041
import org.springframework.http.server.reactive.HttpHeadResponseDecorator;
4142
import org.springframework.http.server.reactive.ServerHttpRequest;
4243
import org.springframework.http.server.reactive.ServerHttpResponse;
44+
import org.springframework.http.server.reactive.SslInfo;
4345
import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
4446
import org.springframework.mock.http.client.reactive.MockClientHttpResponse;
4547
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
48+
import org.springframework.mock.http.server.reactive.MockServerHttpRequest.BodyBuilder;
4649
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
4750
import org.springframework.util.Assert;
4851
import org.springframework.util.MultiValueMap;
@@ -56,6 +59,7 @@
5659
* {@link MockServerHttpRequest} and {@link MockServerHttpResponse}.
5760
*
5861
* @author Rossen Stoyanchev
62+
* @author Sam Brannen
5963
* @since 5.0
6064
*/
6165
public class HttpHandlerConnector implements ClientHttpConnector {
@@ -64,13 +68,26 @@ public class HttpHandlerConnector implements ClientHttpConnector {
6468

6569
private final HttpHandler handler;
6670

71+
private final @Nullable SslInfo sslInfo;
72+
6773

6874
/**
69-
* Constructor with the {@link HttpHandler} to handle requests with.
75+
* Construct an {@code HttpHandlerConnector} with the supplied {@link HttpHandler}
76+
* to handle requests with.
7077
*/
7178
public HttpHandlerConnector(HttpHandler handler) {
79+
this(handler, null);
80+
}
81+
82+
/**
83+
* Construct an {@code HttpHandlerConnector} with the supplied {@link SslInfo}
84+
* and {@link HttpHandler} to handle requests with.
85+
* @since 7.0
86+
*/
87+
public HttpHandlerConnector(HttpHandler handler, @Nullable SslInfo sslInfo) {
7288
Assert.notNull(handler, "HttpHandler is required");
7389
this.handler = handler;
90+
this.sslInfo = sslInfo;
7491
}
7592

7693

@@ -136,7 +153,11 @@ private ServerHttpRequest adaptRequest(MockClientHttpRequest request, Publisher<
136153
URI uri = request.getURI();
137154
HttpHeaders headers = request.getHeaders();
138155
MultiValueMap<String, HttpCookie> cookies = request.getCookies();
139-
return MockServerHttpRequest.method(method, uri).headers(headers).cookies(cookies).body(body);
156+
BodyBuilder builder = MockServerHttpRequest.method(method, uri).headers(headers).cookies(cookies);
157+
if (this.sslInfo != null) {
158+
builder.sslInfo(this.sslInfo);
159+
}
160+
return builder.body(body);
140161
}
141162

142163
private ServerHttpResponse prepareResponse(ServerHttpResponse response, ServerHttpRequest request) {

spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.http.client.reactive.ClientHttpRequest;
4141
import org.springframework.http.codec.ClientCodecConfigurer;
4242
import org.springframework.http.codec.ServerCodecConfigurer;
43+
import org.springframework.http.server.reactive.SslInfo;
4344
import org.springframework.test.json.JsonComparator;
4445
import org.springframework.test.json.JsonCompareMode;
4546
import org.springframework.test.json.JsonComparison;
@@ -275,6 +276,14 @@ interface MockServerSpec<B extends MockServerSpec<B>> {
275276
*/
276277
<T extends B> T webSessionManager(WebSessionManager sessionManager);
277278

279+
/**
280+
* Provide SSL session information and certificates for the mock server.
281+
* @param sslInfo the {@link SslInfo} to use
282+
* @since 7.0
283+
* @see org.springframework.mock.http.server.reactive.MockSslInfo
284+
*/
285+
<T extends B> T sslInfo(SslInfo sslInfo);
286+
278287
/**
279288
* Shortcut for pre-packaged customizations to the mock server setup.
280289
* @param configurer the configurer to apply

spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ApplicationContextTests.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,37 @@
1717
package org.springframework.test.web.reactive.server.samples.bind;
1818

1919
import org.junit.jupiter.api.Test;
20+
import reactor.core.publisher.Mono;
2021

2122
import org.springframework.beans.factory.annotation.Autowired;
2223
import org.springframework.context.ApplicationContext;
2324
import org.springframework.context.annotation.Configuration;
2425
import org.springframework.context.annotation.Import;
26+
import org.springframework.http.server.reactive.ServerHttpRequest;
27+
import org.springframework.mock.http.server.reactive.MockSslInfo;
2528
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
2629
import org.springframework.test.web.reactive.server.WebTestClient;
2730
import org.springframework.web.bind.annotation.GetMapping;
2831
import org.springframework.web.bind.annotation.RestController;
2932
import org.springframework.web.reactive.config.EnableWebFlux;
33+
import org.springframework.web.server.ServerWebExchange;
34+
import org.springframework.web.server.WebFilter;
35+
import org.springframework.web.server.WebFilterChain;
3036

3137
/**
3238
* Sample tests demonstrating "mock" server tests binding to server infrastructure
3339
* declared in a Spring ApplicationContext.
3440
*
3541
* @author Rossen Stoyanchev
42+
* @author Sam Brannen
3643
* @since 5.0
3744
*/
3845
@SpringJUnitConfig
3946
class ApplicationContextTests {
4047

48+
private static final String SSL_SESSION_ID = "sslSessionId";
49+
50+
4151
@Autowired
4252
ApplicationContext context;
4353

@@ -52,6 +62,19 @@ void buildWithDefaults() {
5262
.expectBody(String.class).isEqualTo("It works!");
5363
}
5464

65+
@Test // gh-35042
66+
void buildWithSslInfo() {
67+
var client = WebTestClient.bindToApplicationContext(context)
68+
.sslInfo(new MockSslInfo("test123"))
69+
.webFilter(new SslSessionIdFilter())
70+
.build();
71+
72+
client.get().uri("/sslInfo")
73+
.exchange()
74+
.expectStatus().isOk()
75+
.expectBody(String.class).isEqualTo("Session ID: test123");
76+
}
77+
5578

5679
@Configuration
5780
@EnableWebFlux
@@ -66,6 +89,21 @@ static class TestController {
6689
String test() {
6790
return "It works!";
6891
}
92+
93+
@GetMapping("/sslInfo")
94+
String sslInfo(ServerHttpRequest request) {
95+
return "Session ID: " + request.getAttributes().get(SSL_SESSION_ID);
96+
}
97+
}
98+
99+
private static class SslSessionIdFilter implements WebFilter {
100+
101+
@Override
102+
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
103+
var request = exchange.getRequest();
104+
request.getAttributes().put(SSL_SESSION_ID, request.getSslInfo().getSessionId());
105+
return chain.filter(exchange);
106+
}
69107
}
70108

71109
}

0 commit comments

Comments
 (0)