Skip to content

Commit e3e975d

Browse files
committed
Support for SslInfo in ServerHttpRequest#mutate
Issue: SPR-16830
1 parent b385ff1 commit e3e975d

File tree

3 files changed

+113
-31
lines changed

3 files changed

+113
-31
lines changed

spring-web/src/main/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilder.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
5757
@Nullable
5858
private String contextPath;
5959

60+
@Nullable
61+
private SslInfo sslInfo;
62+
6063
private Flux<DataBuffer> body;
6164

6265
private final ServerHttpRequest originalRequest;
@@ -97,6 +100,7 @@ public ServerHttpRequest.Builder uri(URI uri) {
97100

98101
@Override
99102
public ServerHttpRequest.Builder path(String path) {
103+
Assert.isTrue(path.startsWith("/"), "The path does not have a leading slash.");
100104
this.uriPath = path;
101105
return this;
102106
}
@@ -120,10 +124,16 @@ public ServerHttpRequest.Builder headers(Consumer<HttpHeaders> headersConsumer)
120124
return this;
121125
}
122126

127+
@Override
128+
public ServerHttpRequest.Builder sslInfo(SslInfo sslInfo) {
129+
this.sslInfo = sslInfo;
130+
return this;
131+
}
132+
123133
@Override
124134
public ServerHttpRequest build() {
125-
return new DefaultServerHttpRequest(getUriToUse(), this.contextPath, this.httpHeaders,
126-
this.httpMethodValue, this.cookies, this.body, this.originalRequest);
135+
return new MutatedServerHttpRequest(getUriToUse(), this.contextPath, this.httpHeaders,
136+
this.httpMethodValue, this.cookies, this.sslInfo, this.body, this.originalRequest);
127137
}
128138

129139
private URI getUriToUse() {
@@ -165,7 +175,7 @@ private URI getUriToUse() {
165175
}
166176

167177

168-
private static class DefaultServerHttpRequest extends AbstractServerHttpRequest {
178+
private static class MutatedServerHttpRequest extends AbstractServerHttpRequest {
169179

170180
private final String methodValue;
171181

@@ -181,15 +191,16 @@ private static class DefaultServerHttpRequest extends AbstractServerHttpRequest
181191

182192
private final ServerHttpRequest originalRequest;
183193

184-
public DefaultServerHttpRequest(URI uri, @Nullable String contextPath,
194+
195+
public MutatedServerHttpRequest(URI uri, @Nullable String contextPath,
185196
HttpHeaders headers, String methodValue, MultiValueMap<String, HttpCookie> cookies,
186-
Flux<DataBuffer> body, ServerHttpRequest originalRequest) {
197+
@Nullable SslInfo sslInfo, Flux<DataBuffer> body, ServerHttpRequest originalRequest) {
187198

188199
super(uri, contextPath, headers);
189200
this.methodValue = methodValue;
190201
this.cookies = cookies;
191202
this.remoteAddress = originalRequest.getRemoteAddress();
192-
this.sslInfo = originalRequest.getSslInfo();
203+
this.sslInfo = sslInfo != null ? sslInfo : originalRequest.getSslInfo();
193204
this.body = body;
194205
this.originalRequest = originalRequest;
195206
}

spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,36 @@ interface Builder {
9595
Builder method(HttpMethod httpMethod);
9696

9797
/**
98-
* Set the URI to return.
98+
* Set the URI to use with the following conditions:
99+
* <ul>
100+
* <li>If {@link #path(String) path} is also set, it overrides the path
101+
* of the URI provided here.
102+
* <li>If {@link #contextPath(String) contextPath} is also set, or
103+
* already present, it must match the start of the path of the URI
104+
* provided here.
105+
* </ul>
99106
*/
100107
Builder uri(URI uri);
101108

102109
/**
103-
* Set the path to use instead of the {@code "rawPath"} of
104-
* {@link ServerHttpRequest#getURI()}.
110+
* Set the path to use instead of the {@code "rawPath"} of the URI of
111+
* the request with the following conditions:
112+
* <ul>
113+
* <li>If {@link #uri(URI) uri} is also set, the path given here
114+
* overrides the path of the given URI.
115+
* <li>If {@link #contextPath(String) contextPath} is also set, or
116+
* already present, it must match the start of the path given here.
117+
* <li>The given value must begin with a slash.
118+
* </ul>
105119
*/
106120
Builder path(String path);
107121

108122
/**
109123
* Set the contextPath to use.
124+
* <p>The given value must be a valid {@link RequestPath#contextPath()
125+
* contextPath} and it must match the start of the path of the URI of
126+
* the request. That means changing the contextPath, implies also
127+
* changing the path via {@link #path(String)}.
110128
*/
111129
Builder contextPath(String contextPath);
112130

@@ -116,16 +134,22 @@ interface Builder {
116134
Builder header(String key, String value);
117135

118136
/**
119-
* Manipulate this request's headers with the given consumer. The
120-
* headers provided to the consumer are "live", so that the consumer can be used to
121-
* {@linkplain HttpHeaders#set(String, String) overwrite} existing header values,
122-
* {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other
123-
* {@link HttpHeaders} methods.
124-
* @param headersConsumer a function that consumes the {@code HttpHeaders}
125-
* @return this builder
137+
* Manipulate request headers. The provided {@code HttpHeaders} contains
138+
* current request headers, so that the {@code Consumer} can
139+
* {@linkplain HttpHeaders#set(String, String) overwrite} or
140+
* {@linkplain HttpHeaders#remove(Object) remove} existing values, or
141+
* use any other {@link HttpHeaders} methods.
126142
*/
127143
Builder headers(Consumer<HttpHeaders> headersConsumer);
128144

145+
/**
146+
* Set the SSL session information. This may be useful in environments
147+
* where TLS termination is done at the router, but SSL information is
148+
* made available in some other way such as through a header.
149+
* @since 5.0.7
150+
*/
151+
Builder sslInfo(SslInfo sslInfo);
152+
129153
/**
130154
* Build a {@link ServerHttpRequest} decorator with the mutated properties.
131155
*/

spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,25 @@
1717
package org.springframework.http.server.reactive;
1818

1919
import java.io.ByteArrayInputStream;
20+
import java.net.URI;
2021
import java.util.Arrays;
2122
import java.util.Collections;
2223
import javax.servlet.AsyncContext;
2324
import javax.servlet.ReadListener;
2425
import javax.servlet.ServletInputStream;
25-
import javax.servlet.http.HttpServletRequest;
2626

2727
import org.junit.Test;
2828

2929
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
30+
import org.springframework.http.HttpMethod;
3031
import org.springframework.mock.web.test.DelegatingServletInputStream;
3132
import org.springframework.mock.web.test.MockAsyncContext;
3233
import org.springframework.mock.web.test.MockHttpServletRequest;
3334
import org.springframework.mock.web.test.MockHttpServletResponse;
3435
import org.springframework.util.MultiValueMap;
3536

3637
import static org.junit.Assert.*;
38+
import static org.mockito.Mockito.*;
3739

3840
/**
3941
* Unit tests for {@link AbstractServerHttpRequest}.
@@ -84,33 +86,78 @@ public void queryParamsWithNoValue() throws Exception {
8486
assertEquals(Collections.singletonList(null), params.get("a"));
8587
}
8688

89+
@Test
90+
public void mutateRequest() throws Exception {
91+
92+
SslInfo sslInfo = mock(SslInfo.class);
93+
ServerHttpRequest request = createHttpRequest("/").mutate().sslInfo(sslInfo).build();
94+
assertSame(sslInfo, request.getSslInfo());
95+
96+
request = createHttpRequest("/").mutate().method(HttpMethod.DELETE).build();
97+
assertEquals(HttpMethod.DELETE, request.getMethod());
98+
99+
String baseUri = "http://aaa.org:8080/a";
100+
101+
request = createHttpRequest(baseUri).mutate().uri(URI.create("http://bbb.org:9090/b")).build();
102+
assertEquals("http://bbb.org:9090/b", request.getURI().toString());
103+
104+
request = createHttpRequest(baseUri).mutate().path("/b/c/d").build();
105+
assertEquals("http://aaa.org:8080/b/c/d", request.getURI().toString());
106+
107+
request = createHttpRequest(baseUri).mutate().path("/app/b/c/d").contextPath("/app").build();
108+
assertEquals("http://aaa.org:8080/app/b/c/d", request.getURI().toString());
109+
assertEquals("/app", request.getPath().contextPath().value());
110+
}
111+
112+
@Test(expected = IllegalArgumentException.class)
113+
public void mutateWithInvalidPath() throws Exception {
114+
createHttpRequest("/").mutate().path("foo-bar");
115+
}
116+
87117
@Test // SPR-16434
88118
public void mutatePathWithEncodedQueryParams() throws Exception {
89-
ServerHttpRequest request = createHttpRequest("/path?name=%E6%89%8E%E6%A0%B9")
90-
.mutate().path("/mutatedPath").build();
119+
ServerHttpRequest request = createHttpRequest("/path?name=%E6%89%8E%E6%A0%B9");
120+
request = request.mutate().path("/mutatedPath").build();
121+
91122
assertEquals("/mutatedPath", request.getURI().getRawPath());
92123
assertEquals("name=%E6%89%8E%E6%A0%B9", request.getURI().getRawQuery());
93124
}
94125

95-
96-
private ServerHttpRequest createHttpRequest(String path) throws Exception {
97-
HttpServletRequest request = createEmptyBodyHttpServletRequest(path);
126+
private ServerHttpRequest createHttpRequest(String uriString) throws Exception {
127+
URI uri = URI.create(uriString);
128+
MockHttpServletRequest request = new TestHttpServletRequest(uri);
98129
AsyncContext asyncContext = new MockAsyncContext(request, new MockHttpServletResponse());
99130
return new ServletServerHttpRequest(request, asyncContext, "", new DefaultDataBufferFactory(), 1024);
100131
}
101132

102-
private HttpServletRequest createEmptyBodyHttpServletRequest(String path) {
103-
return new MockHttpServletRequest("GET", path) {
133+
134+
private static class TestHttpServletRequest extends MockHttpServletRequest {
135+
136+
TestHttpServletRequest(URI uri) {
137+
super("GET", uri.getRawPath());
138+
if (uri.getScheme() != null) {
139+
setScheme(uri.getScheme());
140+
}
141+
if (uri.getHost() != null) {
142+
setServerName(uri.getHost());
143+
}
144+
if (uri.getPort() != -1) {
145+
setServerPort(uri.getPort());
146+
}
147+
if (uri.getRawQuery() != null) {
148+
setQueryString(uri.getRawQuery());
149+
}
150+
}
151+
152+
@Override
153+
public ServletInputStream getInputStream() {
154+
return new DelegatingServletInputStream(new ByteArrayInputStream(new byte[0])) {
104155
@Override
105-
public ServletInputStream getInputStream() {
106-
return new DelegatingServletInputStream(new ByteArrayInputStream(new byte[0])) {
107-
@Override
108-
public void setReadListener(ReadListener readListener) {
109-
// Ignore
110-
}
111-
};
156+
public void setReadListener(ReadListener readListener) {
157+
// Ignore
112158
}
113159
};
160+
}
114161
}
115162

116163
}

0 commit comments

Comments
 (0)