Skip to content

Commit 38a12ed

Browse files
committed
Expose RequestPath in ServerHttpRequest
The new structured getPath() method replaces the existing getContextPath() + getPathWithinApplication(). Issue: SPR-15648
1 parent 2d17411 commit 38a12ed

26 files changed

+127
-115
lines changed

Diff for: spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java

+1-9
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
5656

5757
private final HttpMethod httpMethod;
5858

59-
private final String contextPath;
60-
6159
private final MultiValueMap<String, HttpCookie> cookies;
6260

6361
private final InetSocketAddress remoteAddress;
@@ -70,9 +68,8 @@ private MockServerHttpRequest(HttpMethod httpMethod, URI uri, String contextPath
7068
InetSocketAddress remoteAddress,
7169
Publisher<? extends DataBuffer> body) {
7270

73-
super(uri, headers);
71+
super(uri, contextPath, headers);
7472
this.httpMethod = httpMethod;
75-
this.contextPath = contextPath;
7673
this.cookies = cookies;
7774
this.remoteAddress = remoteAddress;
7875
this.body = Flux.from(body);
@@ -89,11 +86,6 @@ public String getMethodValue() {
8986
return this.httpMethod.name();
9087
}
9188

92-
@Override
93-
public String getContextPath() {
94-
return this.contextPath;
95-
}
96-
9789
@Override
9890
public InetSocketAddress getRemoteAddress() {
9991
return this.remoteAddress;

Diff for: spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
4141

4242
private final URI uri;
4343

44+
private final RequestPath path;
45+
4446
private final HttpHeaders headers;
4547

4648
private MultiValueMap<String, String> queryParams;
@@ -51,10 +53,12 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest {
5153
/**
5254
* Constructor with the URI and headers for the request.
5355
* @param uri the URI for the request
56+
* @param contextPath the context path for the request
5457
* @param headers the headers for the request
5558
*/
56-
public AbstractServerHttpRequest(URI uri, HttpHeaders headers) {
59+
public AbstractServerHttpRequest(URI uri, String contextPath, HttpHeaders headers) {
5760
this.uri = uri;
61+
this.path = new DefaultRequestPath(uri, contextPath, StandardCharsets.UTF_8);
5862
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
5963
}
6064

@@ -64,6 +68,11 @@ public URI getURI() {
6468
return this.uri;
6569
}
6670

71+
@Override
72+
public RequestPath getPath() {
73+
return this.path;
74+
}
75+
6776
@Override
6877
public HttpHeaders getHeaders() {
6978
return this.headers;

Diff for: spring-web/src/main/java/org/springframework/http/server/reactive/ContextPathCompositeHandler.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* <p>This is intended as a coarse-grained mechanism for delegating requests to
1717
* one of several applications -- each represented by an {@code HttpHandler}, with
1818
* the application "context path" (the prefix-based mapping) exposed via
19-
* {@link ServerHttpRequest#getContextPath()}.
19+
* {@link ServerHttpRequest#getPath()}.
2020
*
2121
* @author Rossen Stoyanchev
2222
* @since 5.0
@@ -49,12 +49,12 @@ private static void assertValidContextPath(String contextPath) {
4949
@Override
5050
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
5151
// Remove underlying context path first (e.g. Servlet container)
52-
String path = request.getPathWithinApplication();
52+
String path = request.getPath().pathWithinApplication().value();
5353
return this.handlerMap.entrySet().stream()
5454
.filter(entry -> path.startsWith(entry.getKey()))
5555
.findFirst()
5656
.map(entry -> {
57-
String contextPath = request.getContextPath() + entry.getKey();
57+
String contextPath = request.getPath().contextPath().value() + entry.getKey();
5858
ServerHttpRequest newRequest = request.mutate().contextPath(contextPath).build();
5959
return entry.getValue().handle(newRequest, response);
6060
})

Diff for: spring-web/src/main/java/org/springframework/http/server/reactive/DefaultRequestPath.java

+5
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ class DefaultRequestPath implements RequestPath {
5858
this.pathWithinApplication = initPathWithinApplication(this.fullPath, this.contextPath);
5959
}
6060

61+
DefaultRequestPath(RequestPath requestPath, String contextPath, Charset charset) {
62+
this.fullPath = new DefaultPathSegmentContainer(requestPath.value(), requestPath.pathSegments());
63+
this.contextPath = initContextPath(this.fullPath, contextPath);
64+
this.pathWithinApplication = initPathWithinApplication(this.fullPath, this.contextPath);
65+
}
6166

6267
private static PathSegmentContainer parsePath(String path, Charset charset) {
6368
path = StringUtils.hasText(path) ? path : "";

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

+55-26
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.net.URI;
2020
import java.net.URISyntaxException;
21+
import java.nio.charset.StandardCharsets;
2122

2223
import org.springframework.http.HttpHeaders;
2324
import org.springframework.http.HttpMethod;
@@ -79,18 +80,51 @@ public ServerHttpRequest.Builder header(String key, String value) {
7980

8081
@Override
8182
public ServerHttpRequest build() {
82-
URI uri = null;
83-
if (this.path != null) {
84-
uri = this.delegate.getURI();
85-
try {
86-
uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
87-
this.path, uri.getQuery(), uri.getFragment());
88-
}
89-
catch (URISyntaxException ex) {
90-
throw new IllegalStateException("Invalid URI path: \"" + this.path + "\"");
91-
}
83+
URI uriToUse = getUriToUse();
84+
RequestPath path = getRequestPathToUse(uriToUse);
85+
HttpHeaders headers = getHeadersToUse();
86+
return new MutativeDecorator(this.delegate, this.httpMethod, uriToUse, path, headers);
87+
}
88+
89+
@Nullable
90+
private URI getUriToUse() {
91+
if (this.path == null) {
92+
return null;
93+
}
94+
URI uri = this.delegate.getURI();
95+
try {
96+
return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
97+
this.path, uri.getQuery(), uri.getFragment());
98+
}
99+
catch (URISyntaxException ex) {
100+
throw new IllegalStateException("Invalid URI path: \"" + this.path + "\"");
101+
}
102+
}
103+
104+
@Nullable
105+
private RequestPath getRequestPathToUse(@Nullable URI uriToUse) {
106+
if (uriToUse == null && this.contextPath == null) {
107+
return null;
108+
}
109+
else if (uriToUse == null) {
110+
return new DefaultRequestPath(this.delegate.getPath(), this.contextPath, StandardCharsets.UTF_8);
111+
}
112+
else {
113+
return new DefaultRequestPath(uriToUse, this.contextPath, StandardCharsets.UTF_8);
114+
}
115+
}
116+
117+
@Nullable
118+
private HttpHeaders getHeadersToUse() {
119+
if (this.httpHeaders != null) {
120+
HttpHeaders headers = new HttpHeaders();
121+
headers.putAll(this.delegate.getHeaders());
122+
headers.putAll(this.httpHeaders);
123+
return headers;
124+
}
125+
else {
126+
return null;
92127
}
93-
return new MutativeDecorator(this.delegate, this.httpMethod, uri, this.contextPath, this.httpHeaders);
94128
}
95129

96130

@@ -104,25 +138,20 @@ private static class MutativeDecorator extends ServerHttpRequestDecorator {
104138

105139
private final URI uri;
106140

107-
private final String contextPath;
141+
private final RequestPath requestPath;
108142

109143
private final HttpHeaders httpHeaders;
110144

111-
public MutativeDecorator(ServerHttpRequest delegate, HttpMethod httpMethod,
112-
@Nullable URI uri, String contextPath, @Nullable HttpHeaders httpHeaders) {
145+
146+
public MutativeDecorator(ServerHttpRequest delegate, HttpMethod method,
147+
@Nullable URI uri, @Nullable RequestPath requestPath,
148+
@Nullable HttpHeaders httpHeaders) {
113149

114150
super(delegate);
115-
this.httpMethod = httpMethod;
151+
this.httpMethod = method;
116152
this.uri = uri;
117-
this.contextPath = contextPath;
118-
if (httpHeaders != null) {
119-
this.httpHeaders = new HttpHeaders();
120-
this.httpHeaders.putAll(super.getHeaders());
121-
this.httpHeaders.putAll(httpHeaders);
122-
}
123-
else {
124-
this.httpHeaders = null;
125-
}
153+
this.requestPath = requestPath;
154+
this.httpHeaders = httpHeaders;
126155
}
127156

128157
@Override
@@ -136,8 +165,8 @@ public URI getURI() {
136165
}
137166

138167
@Override
139-
public String getContextPath() {
140-
return (this.contextPath != null ? this.contextPath : super.getContextPath());
168+
public RequestPath getPath() {
169+
return (this.requestPath != null ? this.requestPath : super.getPath());
141170
}
142171

143172
@Override

Diff for: spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public class ReactorServerHttpRequest extends AbstractServerHttpRequest {
4949
public ReactorServerHttpRequest(HttpServerRequest request, NettyDataBufferFactory bufferFactory)
5050
throws URISyntaxException {
5151

52-
super(initUri(request), initHeaders(request));
52+
super(initUri(request), "", initHeaders(request));
5353
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
5454
this.request = request;
5555
this.bufferFactory = bufferFactory;

Diff for: spring-web/src/main/java/org/springframework/http/server/reactive/RequestPath.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424
public interface RequestPath extends PathSegmentContainer {
2525

2626
/**
27-
* The contextPath portion of the request if any.
27+
* Returns the portion of the URL path that represents the application.
28+
* The context path is always at the beginning of the path and starts but
29+
* does not end with "/". It is shared for URLs of the same application.
30+
* <p>The context path may come from the underlying runtime API such as
31+
* when deploying as a WAR to a Servlet container or it may also be assigned
32+
* through the use of {@link ContextPathCompositeHandler} or both.
2833
*/
2934
PathSegmentContainer contextPath();
3035

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

+7-31
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.springframework.http.ReactiveHttpInputMessage;
2525
import org.springframework.lang.Nullable;
2626
import org.springframework.util.MultiValueMap;
27-
import org.springframework.util.StringUtils;
2827

2928
/**
3029
* Represents a reactive server-side HTTP request
@@ -36,35 +35,11 @@
3635
public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage {
3736

3837
/**
39-
* Returns the portion of the URL path that represents the application.
40-
* The context path is always at the beginning of the path and starts but
41-
* does not end with "/". It is shared for URLs of the same application.
42-
* <p>The context path may come from the underlying runtime API such as
43-
* when deploying as a WAR to a Servlet container or it may also be assigned
44-
* through the use of {@link ContextPathCompositeHandler} or both.
45-
* <p>The context path is not decoded.
46-
* @return the context path (not decoded) or an empty string
38+
* Returns a structured representation of the request path including the
39+
* context path + path within application portions, path segments with
40+
* encoded and decoded values, and path parameters.
4741
*/
48-
default String getContextPath() {
49-
return "";
50-
}
51-
52-
/**
53-
* Returns the portion of the URL path after the {@link #getContextPath()
54-
* contextPath}. The returned path is not decoded.
55-
* @return the path under the contextPath
56-
*/
57-
default String getPathWithinApplication() {
58-
String path = getURI().getRawPath();
59-
String contextPath = getContextPath();
60-
if (StringUtils.hasText(contextPath)) {
61-
int length = contextPath.length();
62-
return (path.length() > length ? path.substring(length) : "");
63-
}
64-
else {
65-
return path;
66-
}
67-
}
42+
RequestPath getPath();
6843

6944
/**
7045
* Return a read-only map with parsed and decoded query parameter values.
@@ -104,12 +79,13 @@ interface Builder {
10479
Builder method(HttpMethod httpMethod);
10580

10681
/**
107-
* Set the request URI to return.
82+
* Set the path to use instead of the {@code "rawPath"} of
83+
* {@link ServerHttpRequest#getURI()}.
10884
*/
10985
Builder path(String path);
11086

11187
/**
112-
* Set the contextPath to return.
88+
* Set the contextPath to use.
11389
*/
11490
Builder contextPath(String contextPath);
11591

Diff for: spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java

+5
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ public URI getURI() {
6868
return getDelegate().getURI();
6969
}
7070

71+
@Override
72+
public RequestPath getPath() {
73+
return getDelegate().getPath();
74+
}
75+
7176
@Override
7277
public MultiValueMap<String, String> getQueryParams() {
7378
return getDelegate().getQueryParams();

Diff for: spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java

+1-6
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public class ServletServerHttpRequest extends AbstractServerHttpRequest {
7272
public ServletServerHttpRequest(HttpServletRequest request, AsyncContext asyncContext,
7373
DataBufferFactory bufferFactory, int bufferSize) throws IOException {
7474

75-
super(initUri(request), initHeaders(request));
75+
super(initUri(request), request.getContextPath(), initHeaders(request));
7676

7777
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
7878
Assert.isTrue(bufferSize > 0, "'bufferSize' must be higher than 0");
@@ -154,11 +154,6 @@ public String getMethodValue() {
154154
return getServletRequest().getMethod();
155155
}
156156

157-
@Override
158-
public String getContextPath() {
159-
return getServletRequest().getContextPath();
160-
}
161-
162157
@Override
163158
protected MultiValueMap<String, HttpCookie> initCookies() {
164159
MultiValueMap<String, HttpCookie> httpCookies = new LinkedMultiValueMap<>();

Diff for: spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public class UndertowServerHttpRequest extends AbstractServerHttpRequest {
5353

5454

5555
public UndertowServerHttpRequest(HttpServerExchange exchange, DataBufferFactory bufferFactory) {
56-
super(initUri(exchange), initHeaders(exchange));
56+
super(initUri(exchange), "", initHeaders(exchange));
5757
this.exchange = exchange;
5858
this.body = new RequestBodyPublisher(exchange, bufferFactory);
5959
this.body.registerListeners(exchange);

Diff for: spring-web/src/main/java/org/springframework/web/cors/reactive/UrlBasedCorsConfigurationSource.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void registerCorsConfiguration(String path, CorsConfiguration config) {
8080

8181
@Override
8282
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
83-
String lookupPath = exchange.getRequest().getPathWithinApplication();
83+
String lookupPath = exchange.getRequest().getPath().pathWithinApplication().value();
8484
for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
8585
if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
8686
return entry.getValue();

Diff for: spring-web/src/test/java/org/springframework/http/server/reactive/ContextPathCompositeHandlerTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public void matchWithNativeContextPath() {
105105
new ContextPathCompositeHandler(map).handle(request, new MockServerHttpResponse());
106106

107107
assertTrue(handler.wasInvoked());
108-
assertEquals("/yet/another/path", handler.getRequest().getContextPath());
108+
assertEquals("/yet/another/path", handler.getRequest().getPath().contextPath().value());
109109
}
110110

111111
@Test
@@ -133,7 +133,7 @@ private ServerHttpResponse testHandle(String pathToHandle, Map<String, HttpHandl
133133

134134
private void assertInvoked(TestHttpHandler handler, String contextPath) {
135135
assertTrue(handler.wasInvoked());
136-
assertEquals(contextPath, handler.getRequest().getContextPath());
136+
assertEquals(contextPath, handler.getRequest().getPath().contextPath().value());
137137
}
138138

139139
private void assertNotInvoked(TestHttpHandler... handlers) {

Diff for: spring-web/src/test/java/org/springframework/http/server/reactive/RxNettyServerHttpRequest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public RxNettyServerHttpRequest(HttpServerRequest<ByteBuf> request,
5656
NettyDataBufferFactory dataBufferFactory, InetSocketAddress remoteAddress)
5757
throws URISyntaxException {
5858

59-
super(initUri(request, remoteAddress), initHeaders(request));
59+
super(initUri(request, remoteAddress), "", initHeaders(request));
6060
this.request = request;
6161

6262
Assert.notNull(dataBufferFactory, "NettyDataBufferFactory must not be null");

0 commit comments

Comments
 (0)