Skip to content

Commit 05af623

Browse files
committed
#713 - Centralize all Forward header handling
Spring 5.1 is centralizing all Forward header handling. This moves critical bits into one location, making it easy to completely remove once we rebaseline against this version. Also adds a test profile to ensure Spring 5.1 doesn't break anything.
1 parent ef3e3c4 commit 05af623

File tree

6 files changed

+105
-19
lines changed

6 files changed

+105
-19
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ env:
55
matrix:
66
- PROFILE=non-existant
77
- PROFILE=spring5-next
8+
- PROFILE=spring51-next
89
addons:
910
apt:
1011
packages:

pom.xml

+14
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,20 @@
9797
</repositories>
9898
</profile>
9999

100+
<profile>
101+
<id>spring51-next</id>
102+
<properties>
103+
<spring.version>5.1.0.BUILD-SNAPSHOT</spring.version>
104+
<jackson.version>2.9.2</jackson.version>
105+
</properties>
106+
<repositories>
107+
<repository>
108+
<id>spring-libs-snapshot</id>
109+
<url>http://repo.spring.io/libs-snapshot</url>
110+
</repository>
111+
</repositories>
112+
</profile>
113+
100114
<profile>
101115

102116
<!-- Profile to be run on the CI server, JARs JavaDocs -->

src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java

+6-18
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.hateoas.mvc;
1717

18-
import static org.springframework.util.StringUtils.*;
18+
import static org.springframework.hateoas.mvc.ForwardedHeader.handleXForwardedSslHeader;
1919

2020
import lombok.RequiredArgsConstructor;
2121
import lombok.experimental.Delegate;
@@ -306,27 +306,15 @@ public String toString() {
306306
*
307307
* @return
308308
*/
309-
public static UriComponentsBuilder getBuilder() {
309+
public static UriComponentsBuilder getBuilder() {
310310

311311
if (RequestContextHolder.getRequestAttributes() == null) {
312312
return UriComponentsBuilder.fromPath("/");
313313
}
314314

315315
HttpServletRequest request = getCurrentRequest();
316-
UriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request);
317316

318-
// special case handling for X-Forwarded-Ssl:
319-
// apply it, but only if X-Forwarded-Proto is unset.
320-
321-
String forwardedSsl = request.getHeader("X-Forwarded-Ssl");
322-
ForwardedHeader forwarded = ForwardedHeader.of(request.getHeader(ForwardedHeader.NAME));
323-
String proto = hasText(forwarded.getProto()) ? forwarded.getProto() : request.getHeader("X-Forwarded-Proto");
324-
325-
if (!hasText(proto) && hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on")) {
326-
builder.scheme("https");
327-
}
328-
329-
return builder;
317+
return handleXForwardedSslHeader(request, ServletUriComponentsBuilder.fromServletMapping(request));
330318
}
331319

332320
/**
@@ -361,13 +349,13 @@ private static Collection<Affordance> findAffordances(MethodInvocation invocatio
361349
private static class CachingAnnotationMappingDiscoverer implements MappingDiscoverer {
362350

363351
private final @Delegate AnnotationMappingDiscoverer delegate;
364-
private final Map<String, UriTemplate> templates = new ConcurrentReferenceHashMap<>();
352+
private final Map<String, UriTemplate> templates = new ConcurrentReferenceHashMap<>();
365353

366354
public UriTemplate getMappingAsUriTemplate(Class<?> type, Method method) {
367355

368356
String mapping = delegate.getMapping(type, method);
369-
370-
return templates.computeIfAbsent(mapping, UriTemplate::new);
357+
358+
return templates.computeIfAbsent(mapping, UriTemplate::new);
371359
}
372360
}
373361

src/main/java/org/springframework/hateoas/mvc/ForwardedHeader.java

+34
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,27 @@
1515
*/
1616
package org.springframework.hateoas.mvc;
1717

18+
import static org.springframework.util.StringUtils.hasText;
19+
1820
import java.util.Arrays;
1921
import java.util.Collections;
2022
import java.util.Map;
2123
import java.util.stream.Collectors;
2224

25+
import javax.servlet.http.HttpServletRequest;
26+
2327
import org.springframework.util.Assert;
2428
import org.springframework.util.StringUtils;
29+
import org.springframework.web.util.UriComponentsBuilder;
2530

2631
/**
2732
* Value object to partially implement the {@literal Forwarded} header defined in RFC 7239.
2833
*
2934
* @author Oliver Gierke
3035
* @see http://tools.ietf.org/html/rfc7239
36+
* @deprecated In Spring 5.1, all Forwarded headers will by handled by Spring MVC.
3137
*/
38+
@Deprecated
3239
class ForwardedHeader {
3340

3441
public static String NAME = "Forwarded";
@@ -40,6 +47,33 @@ private ForwardedHeader(Map<String, String> elements) {
4047
this.elements = elements;
4148
}
4249

50+
/**
51+
* Utility method to pull handling of {@literal X-Forwarded-Ssl} into a class that will be removed when
52+
* rebaselined against Spring 5.1
53+
*
54+
* @param request
55+
* @param builder
56+
* @return
57+
* @deprecated No longer needed with Spring 5.1
58+
*/
59+
@Deprecated
60+
public static UriComponentsBuilder handleXForwardedSslHeader(HttpServletRequest request, UriComponentsBuilder builder) {
61+
62+
// special case handling for X-Forwarded-Ssl:
63+
// apply it, but only if X-Forwarded-Proto is unset.
64+
65+
String forwardedSsl = request.getHeader("X-Forwarded-Ssl");
66+
ForwardedHeader forwarded = ForwardedHeader.of(request.getHeader(ForwardedHeader.NAME));
67+
String proto = hasText(forwarded.getProto()) ? forwarded.getProto() : request.getHeader("X-Forwarded-Proto");
68+
69+
if (!hasText(proto) && hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on")) {
70+
builder.scheme("https");
71+
}
72+
73+
return builder;
74+
75+
}
76+
4377
/**
4478
* Creates a new {@link ForwardedHeader} from the given source.
4579
*

src/test/java/org/springframework/hateoas/TestUtils.java

+26
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,18 @@
1717

1818
import static org.assertj.core.api.Assertions.*;
1919

20+
import java.io.IOException;
21+
22+
import javax.servlet.ServletException;
23+
import javax.servlet.http.HttpServletRequest;
24+
2025
import org.junit.Before;
26+
import org.springframework.mock.web.MockFilterChain;
2127
import org.springframework.mock.web.MockHttpServletRequest;
28+
import org.springframework.mock.web.MockHttpServletResponse;
2229
import org.springframework.web.context.request.RequestContextHolder;
2330
import org.springframework.web.context.request.ServletRequestAttributes;
31+
import org.springframework.web.filter.ForwardedHeaderFilter;
2432

2533
/**
2634
* Utility class to ease testing.
@@ -43,6 +51,24 @@ protected void assertPointsToMockServer(Link link) {
4351
assertThat(link.getHref()).startsWith("http://localhost");
4452
}
4553

54+
/**
55+
* Provide a mechanism to simulate inserting a {@link ForwardedHeaderFilter} into the servlet
56+
* filter chain, so {@literal Forwarded} headers are properly inserted into the test web request.
57+
*
58+
* @see https://jira.spring.io/browse/SPR-16668
59+
*/
60+
protected void adaptRequestFromForwardedHeaders() {
61+
62+
MockFilterChain chain = new MockFilterChain();
63+
try {
64+
new ForwardedHeaderFilter().doFilter(this.request, new MockHttpServletResponse(), chain);
65+
} catch (ServletException | IOException e) {
66+
throw new RuntimeException(e);
67+
}
68+
HttpServletRequest adaptedRequest = (HttpServletRequest) chain.getRequest();
69+
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(adaptedRequest));
70+
}
71+
4672
public static void assertEqualAndSameHashCode(Object left, Object right) {
4773

4874
assertThat(left).isEqualTo(right);

src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@
2020
import static org.hamcrest.Matchers.*;
2121
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
2222

23+
import java.io.IOException;
2324
import java.lang.reflect.Method;
2425
import java.util.Arrays;
2526
import java.util.List;
2627
import java.util.Optional;
2728

29+
import javax.servlet.ServletException;
30+
2831
import org.junit.Rule;
2932
import org.junit.Test;
3033
import org.junit.rules.ExpectedException;
@@ -162,6 +165,8 @@ public void usesForwardedHostAsHostIfHeaderIsSet() {
162165

163166
request.addHeader("X-Forwarded-Host", "somethingDifferent");
164167

168+
adaptRequestFromForwardedHeaders();
169+
165170
Link link = linkTo(PersonControllerImpl.class).withSelfRel();
166171
assertThat(link.getHref(), startsWith("http://somethingDifferent"));
167172
}
@@ -199,6 +204,8 @@ public void usesForwardedSslAndHostIfHeaderIsSet() {
199204
request.addHeader("X-Forwarded-Host", "somethingDifferent");
200205
request.addHeader("X-Forwarded-Ssl", "on");
201206

207+
adaptRequestFromForwardedHeaders();
208+
202209
Link link = linkTo(PersonControllerImpl.class).withSelfRel();
203210
assertThat(link.getHref(), startsWith("https://somethingDifferent"));
204211
}
@@ -270,6 +277,8 @@ public void usesForwardedHostAndPortFromHeader() {
270277

271278
request.addHeader("X-Forwarded-Host", "foobar:8088");
272279

280+
adaptRequestFromForwardedHeaders();
281+
273282
Link link = linkTo(PersonControllerImpl.class).withSelfRel();
274283
assertThat(link.getHref(), startsWith("http://foobar:8088"));
275284
}
@@ -282,6 +291,8 @@ public void usesFirstHostOfXForwardedHost() {
282291

283292
request.addHeader("X-Forwarded-Host", "barfoo:8888, localhost:8088");
284293

294+
adaptRequestFromForwardedHeaders();
295+
285296
Link link = linkTo(PersonControllerImpl.class).withSelfRel();
286297
assertThat(link.getHref(), startsWith("http://barfoo:8888"));
287298
}
@@ -335,6 +346,8 @@ public void usesForwardedPortFromHeader() {
335346
request.addHeader("X-Forwarded-Port", "9090");
336347
request.setServerPort(8080);
337348

349+
adaptRequestFromForwardedHeaders();
350+
338351
Link link = linkTo(PersonControllerImpl.class).withSelfRel();
339352

340353
assertThat(link.getHref(), startsWith("http://foobarhost:9090/"));
@@ -349,6 +362,8 @@ public void usesForwardedHostFromHeaderWithDefaultPort() {
349362
request.addHeader("X-Forwarded-Host", "foobarhost");
350363
request.setServerPort(8080);
351364

365+
adaptRequestFromForwardedHeaders();
366+
352367
Link link = linkTo(PersonControllerImpl.class).withSelfRel();
353368
assertThat(link.getHref(), startsWith("http://foobarhost/"));
354369
}
@@ -410,12 +425,14 @@ public void usesRootMappingOfTargetClassForMethodsOfParent() throws Exception {
410425
* @see #257, #107
411426
*/
412427
@Test
413-
public void usesXForwardedProtoHeaderAsLinkSchema() {
428+
public void usesXForwardedProtoHeaderAsLinkSchema() throws ServletException, IOException {
414429

415430
for (String proto : Arrays.asList("http", "https")) {
416431

417432
setUp();
418433
request.addHeader("X-Forwarded-Proto", proto);
434+
435+
adaptRequestFromForwardedHeaders();
419436

420437
Link link = linkTo(PersonControllerImpl.class).withSelfRel();
421438
assertThat(link.getHref(), startsWith(proto + "://"));
@@ -433,6 +450,8 @@ public void usesProtoValueFromForwardedHeaderAsLinkSchema() {
433450
setUp();
434451
request.addHeader("Forwarded", new String[] { "proto=" + proto });
435452

453+
adaptRequestFromForwardedHeaders();
454+
436455
Link link = linkTo(PersonControllerImpl.class).withSelfRel();
437456
assertThat(link.getHref(), startsWith(proto.concat("://")));
438457
}
@@ -446,6 +465,8 @@ public void favorsStandardForwardHeaderOverXForwardedProto() {
446465

447466
request.addHeader("X-Forwarded-Proto", "foo");
448467
request.addHeader(ForwardedHeader.NAME, "proto=bar");
468+
469+
adaptRequestFromForwardedHeaders();
449470

450471
Link link = linkTo(PersonControllerImpl.class).withSelfRel();
451472
assertThat(link.getHref(), startsWith("bar://"));
@@ -527,6 +548,8 @@ public void supportsTwoProxiesAddingXForwardedPort() {
527548
request.addHeader("X-Forwarded-Port", "1443,8443");
528549
request.addHeader("X-Forwarded-Host", "proxy1,proxy2");
529550

551+
adaptRequestFromForwardedHeaders();
552+
530553
assertThat(linkTo(PersonControllerImpl.class).withSelfRel().getHref(), startsWith("http://proxy1:1443"));
531554
}
532555

0 commit comments

Comments
 (0)