Skip to content

Commit e70210a

Browse files
committed
Introduce ForwardedHeaderFilter for WebFlux
This commit introduces a ForwardedHeaderFilter for WebFlux, similar to the existing Servlet version. As part of this the DefaultServerHttpRequestBuilder had to be changed to no longer use delegation, but instead use a deep copy at the point of mutate(). Otherwise, headers could not be removed. Issue: SPR-15954
1 parent 69af698 commit e70210a

File tree

5 files changed

+385
-84
lines changed

5 files changed

+385
-84
lines changed

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

Lines changed: 91 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,24 @@
1616

1717
package org.springframework.http.server.reactive;
1818

19+
import java.net.InetSocketAddress;
1920
import java.net.URI;
2021
import java.net.URISyntaxException;
22+
import java.util.LinkedList;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.function.Consumer;
2126

27+
import reactor.core.publisher.Flux;
28+
29+
import org.springframework.core.io.buffer.DataBuffer;
30+
import org.springframework.http.HttpCookie;
2231
import org.springframework.http.HttpHeaders;
2332
import org.springframework.http.HttpMethod;
24-
import org.springframework.http.server.RequestPath;
2533
import org.springframework.lang.Nullable;
2634
import org.springframework.util.Assert;
35+
import org.springframework.util.LinkedMultiValueMap;
36+
import org.springframework.util.MultiValueMap;
2737

2838
/**
2939
* Package-private default implementation of {@link ServerHttpRequest.Builder}.
@@ -34,36 +44,66 @@
3444
*/
3545
class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
3646

37-
private final ServerHttpRequest delegate;
47+
private URI uri;
48+
49+
private HttpHeaders httpHeaders;
50+
51+
private String httpMethodValue;
52+
53+
private final MultiValueMap<String, HttpCookie> cookies;
3854

3955
@Nullable
40-
private HttpMethod httpMethod;
56+
private final InetSocketAddress remoteAddress;
4157

4258
@Nullable
43-
private String path;
59+
private String uriPath;
4460

4561
@Nullable
4662
private String contextPath;
4763

48-
@Nullable
49-
private HttpHeaders httpHeaders;
64+
private Flux<DataBuffer> body;
65+
66+
public DefaultServerHttpRequestBuilder(ServerHttpRequest original) {
67+
Assert.notNull(original, "ServerHttpRequest is required");
68+
69+
this.uri = original.getURI();
70+
this.httpMethodValue = original.getMethodValue();
71+
this.remoteAddress = original.getRemoteAddress();
72+
this.body = original.getBody();
5073

74+
this.httpHeaders = new HttpHeaders();
75+
copyMultiValueMap(original.getHeaders(), this.httpHeaders);
5176

52-
public DefaultServerHttpRequestBuilder(ServerHttpRequest delegate) {
53-
Assert.notNull(delegate, "ServerHttpRequest delegate is required");
54-
this.delegate = delegate;
77+
this.cookies = new LinkedMultiValueMap<>(original.getCookies().size());
78+
copyMultiValueMap(original.getCookies(), this.cookies);
79+
}
80+
81+
private static <K, V> void copyMultiValueMap(MultiValueMap<K,V> source,
82+
MultiValueMap<K,V> destination) {
83+
84+
for (Map.Entry<K, List<V>> entry : source.entrySet()) {
85+
K key = entry.getKey();
86+
List<V> values = new LinkedList<>(entry.getValue());
87+
destination.put(key, values);
88+
}
5589
}
5690

5791

5892
@Override
5993
public ServerHttpRequest.Builder method(HttpMethod httpMethod) {
60-
this.httpMethod = httpMethod;
94+
this.httpMethodValue = httpMethod.name();
95+
return this;
96+
}
97+
98+
@Override
99+
public ServerHttpRequest.Builder uri(URI uri) {
100+
this.uri = uri;
61101
return this;
62102
}
63103

64104
@Override
65105
public ServerHttpRequest.Builder path(String path) {
66-
this.path = path;
106+
this.uriPath = path;
67107
return this;
68108
}
69109

@@ -75,111 +115,79 @@ public ServerHttpRequest.Builder contextPath(String contextPath) {
75115

76116
@Override
77117
public ServerHttpRequest.Builder header(String key, String value) {
78-
if (this.httpHeaders == null) {
79-
this.httpHeaders = new HttpHeaders();
80-
}
81118
this.httpHeaders.add(key, value);
82119
return this;
83120
}
84121

122+
@Override
123+
public ServerHttpRequest.Builder headers(Consumer<HttpHeaders> headersConsumer) {
124+
Assert.notNull(headersConsumer, "'headersConsumer' must not be null");
125+
headersConsumer.accept(this.httpHeaders);
126+
return this;
127+
}
128+
85129
@Override
86130
public ServerHttpRequest build() {
87131
URI uriToUse = getUriToUse();
88-
RequestPath path = getRequestPathToUse(uriToUse);
89-
HttpHeaders headers = getHeadersToUse();
90-
return new MutativeDecorator(this.delegate, this.httpMethod, uriToUse, path, headers);
132+
return new DefaultServerHttpRequest(uriToUse, this.contextPath, this.httpHeaders,
133+
this.httpMethodValue, this.cookies, this.remoteAddress, this.body);
134+
91135
}
92136

93-
@Nullable
94137
private URI getUriToUse() {
95-
if (this.path == null) {
96-
return null;
138+
if (this.uriPath == null) {
139+
return this.uri;
97140
}
98-
URI uri = this.delegate.getURI();
99141
try {
100-
return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
101-
this.path, uri.getQuery(), uri.getFragment());
142+
return new URI(this.uri.getScheme(), this.uri.getUserInfo(), uri.getHost(), uri.getPort(),
143+
uriPath, uri.getQuery(), uri.getFragment());
102144
}
103145
catch (URISyntaxException ex) {
104-
throw new IllegalStateException("Invalid URI path: \"" + this.path + "\"");
105-
}
106-
}
107-
108-
@Nullable
109-
private RequestPath getRequestPathToUse(@Nullable URI uriToUse) {
110-
if (uriToUse == null && this.contextPath == null) {
111-
return null;
112-
}
113-
else if (uriToUse == null) {
114-
return this.delegate.getPath().modifyContextPath(this.contextPath);
115-
}
116-
else {
117-
return RequestPath.parse(uriToUse, this.contextPath);
118-
}
119-
}
120-
121-
@Nullable
122-
private HttpHeaders getHeadersToUse() {
123-
if (this.httpHeaders != null) {
124-
HttpHeaders headers = new HttpHeaders();
125-
headers.putAll(this.delegate.getHeaders());
126-
headers.putAll(this.httpHeaders);
127-
return headers;
128-
}
129-
else {
130-
return null;
146+
throw new IllegalStateException("Invalid URI path: \"" + this.uriPath + "\"");
131147
}
132148
}
133149

150+
private static class DefaultServerHttpRequest extends AbstractServerHttpRequest {
134151

135-
/**
136-
* An immutable wrapper of a request returning property overrides -- given
137-
* to the constructor -- or original values otherwise.
138-
*/
139-
private static class MutativeDecorator extends ServerHttpRequestDecorator {
140-
141-
@Nullable
142-
private final HttpMethod httpMethod;
143-
144-
@Nullable
145-
private final URI uri;
152+
private final String methodValue;
146153

147-
@Nullable
148-
private final RequestPath requestPath;
154+
private final MultiValueMap<String, HttpCookie> cookies;
149155

150156
@Nullable
151-
private final HttpHeaders httpHeaders;
152-
153-
154-
public MutativeDecorator(ServerHttpRequest delegate, @Nullable HttpMethod method,
155-
@Nullable URI uri, @Nullable RequestPath requestPath, @Nullable HttpHeaders httpHeaders) {
156-
157-
super(delegate);
158-
this.httpMethod = method;
159-
this.uri = uri;
160-
this.requestPath = requestPath;
161-
this.httpHeaders = httpHeaders;
157+
private final InetSocketAddress remoteAddress;
158+
159+
private final Flux<DataBuffer> body;
160+
161+
public DefaultServerHttpRequest(URI uri, @Nullable String contextPath,
162+
HttpHeaders headers, String methodValue,
163+
MultiValueMap<String, HttpCookie> cookies, @Nullable InetSocketAddress remoteAddress,
164+
Flux<DataBuffer> body) {
165+
super(uri, contextPath, headers);
166+
this.methodValue = methodValue;
167+
this.cookies = cookies;
168+
this.remoteAddress = remoteAddress;
169+
this.body = body;
162170
}
163171

164172
@Override
165-
@Nullable
166-
public HttpMethod getMethod() {
167-
return (this.httpMethod != null ? this.httpMethod : super.getMethod());
173+
public String getMethodValue() {
174+
return this.methodValue;
168175
}
169176

170177
@Override
171-
public URI getURI() {
172-
return (this.uri != null ? this.uri : super.getURI());
178+
protected MultiValueMap<String, HttpCookie> initCookies() {
179+
return this.cookies;
173180
}
174181

182+
@Nullable
175183
@Override
176-
public RequestPath getPath() {
177-
return (this.requestPath != null ? this.requestPath : super.getPath());
184+
public InetSocketAddress getRemoteAddress() {
185+
return this.remoteAddress;
178186
}
179187

180188
@Override
181-
public HttpHeaders getHeaders() {
182-
return (this.httpHeaders != null ? this.httpHeaders : super.getHeaders());
189+
public Flux<DataBuffer> getBody() {
190+
return this.body;
183191
}
184192
}
185193

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package org.springframework.http.server.reactive;
1818

1919
import java.net.InetSocketAddress;
20+
import java.net.URI;
21+
import java.util.function.Consumer;
2022

2123
import org.springframework.http.HttpCookie;
24+
import org.springframework.http.HttpHeaders;
2225
import org.springframework.http.HttpMethod;
2326
import org.springframework.http.HttpRequest;
2427
import org.springframework.http.ReactiveHttpInputMessage;
@@ -79,6 +82,11 @@ interface Builder {
7982
*/
8083
Builder method(HttpMethod httpMethod);
8184

85+
/**
86+
* Set the URI to return.
87+
*/
88+
Builder uri(URI uri);
89+
8290
/**
8391
* Set the path to use instead of the {@code "rawPath"} of
8492
* {@link ServerHttpRequest#getURI()}.
@@ -95,6 +103,17 @@ interface Builder {
95103
*/
96104
Builder header(String key, String value);
97105

106+
/**
107+
* Manipulate this request's headers with the given consumer. The
108+
* headers provided to the consumer are "live", so that the consumer can be used to
109+
* {@linkplain HttpHeaders#set(String, String) overwrite} existing header values,
110+
* {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other
111+
* {@link HttpHeaders} methods.
112+
* @param headersConsumer a function that consumes the {@code HttpHeaders}
113+
* @return this builder
114+
*/
115+
Builder headers(Consumer<HttpHeaders> headersConsumer);
116+
98117
/**
99118
* Build a {@link ServerHttpRequest} decorator with the mutated properties.
100119
*/

0 commit comments

Comments
 (0)