Permalink
Browse files

#33 - ControllerLinkBuilder now regards X-Forwarded-Host header.

When setting up the builder we now set the host to the value held in the X-Forwarded-Host [0] header if present. This allows to render appropriate URIs in reverse proxy scenarios. This is essentially an extension of ServletUriComponentsBuilder that will be fixed in there eventually. See SPR-10110 [1] for details.

[0] http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10
[1] https://jira.springsource.org/browse/SPR-10110
  • Loading branch information...
olivergierke committed Dec 19, 2012
1 parent 25f68fa commit 3f8b90e2b522f6d597804b3dcc43527c9a981f67
@@ -21,6 +21,8 @@
import java.util.Iterator;
import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.core.AnnotationAttribute;
@@ -30,8 +32,12 @@
import org.springframework.hateoas.core.LinkBuilderSupport;
import org.springframework.hateoas.core.MappingDiscoverer;
import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriTemplate;
@@ -79,7 +85,7 @@ public static ControllerLinkBuilder linkTo(Class<?> controller, Object... parame
Assert.notNull(controller);
- ControllerLinkBuilder builder = new ControllerLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping());
+ ControllerLinkBuilder builder = new ControllerLinkBuilder(getBuilder());
String mapping = DISCOVERER.getMapping(controller);
if (mapping == null) {
@@ -95,7 +101,7 @@ public static ControllerLinkBuilder linkTo(Method method, Object... parameters)
UriTemplate template = new UriTemplate(DISCOVERER.getMapping(method));
URI uri = template.expand(parameters);
- return new ControllerLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping()).slash(uri);
+ return new ControllerLinkBuilder(getBuilder()).slash(uri);
}
/**
@@ -141,7 +147,7 @@ public static ControllerLinkBuilder linkTo(Object invocationValue) {
values.putAll(accessor.getBoundParameters(invocation));
URI uri = template.expand(values);
- return new ControllerLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping()).slash(uri);
+ return new ControllerLinkBuilder(getBuilder()).slash(uri);
}
/**
@@ -173,4 +179,38 @@ protected ControllerLinkBuilder getThis() {
protected ControllerLinkBuilder createNewInstance(UriComponentsBuilder builder) {
return new ControllerLinkBuilder(builder);
}
+
+ /**
+ * Returns a {@link UriComponentsBuilder} obtained from the current servlet mapping with the host tweaked in case the
+ * request contains an {@code X-Forwarded-Host} header.
+ *
+ * @return
+ */
+ private static UriComponentsBuilder getBuilder() {
+
+ HttpServletRequest request = getCurrentRequest();
+ ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request);
+
+ String header = request.getHeader("X-Forwarded-Host");
+ if (StringUtils.hasText(header)) {
+ builder.host(header);
+ }
+
+ return builder;
+ }
+
+ /**
+ * Copy of {@link ServletUriComponentsBuilder#getCurrentRequest()} until SPR-10110 gets fixed.
+ *
+ * @return
+ */
+ private static HttpServletRequest getCurrentRequest() {
+
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ Assert.state(requestAttributes != null, "Could not find current request via RequestContextHolder");
+ Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);
+ HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
+ Assert.state(servletRequest != null, "Could not find current HttpServletRequest");
+ return servletRequest;
+ }
}
@@ -18,8 +18,6 @@
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
-import javax.servlet.http.HttpServletRequest;
-
import org.junit.Before;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
@@ -32,10 +30,12 @@
*/
public class TestUtils {
+ protected MockHttpServletRequest request;
+
@Before
public void setUp() {
- HttpServletRequest request = new MockHttpServletRequest();
+ request = new MockHttpServletRequest();
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes);
}
@@ -17,11 +17,11 @@
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import org.hamcrest.Matchers;
import org.junit.Test;
+import org.mockito.Mockito;
import org.springframework.hateoas.Identifiable;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.TestUtils;
@@ -48,23 +48,23 @@ public void createsLinkToParameterizedControllerRoot() {
Link link = linkTo(PersonsAddressesController.class, 15).withSelfRel();
assertThat(link.getRel(), is(Link.REL_SELF));
- assertThat(link.getHref(), Matchers.endsWith("/people/15/addresses"));
+ assertThat(link.getHref(), endsWith("/people/15/addresses"));
}
@Test
public void createsLinkToSubResource() {
Link link = linkTo(PersonControllerImpl.class).slash("something").withSelfRel();
assertThat(link.getRel(), is(Link.REL_SELF));
- assertThat(link.getHref(), Matchers.endsWith("/people/something"));
+ assertThat(link.getHref(), endsWith("/people/something"));
}
@Test
public void createsLinkWithCustomRel() {
Link link = linkTo(PersonControllerImpl.class).withRel(Link.REL_NEXT);
assertThat(link.getRel(), is(Link.REL_NEXT));
- assertThat(link.getHref(), Matchers.endsWith("/people"));
+ assertThat(link.getHref(), endsWith("/people"));
}
@Test(expected = IllegalStateException.class)
@@ -81,35 +81,47 @@ public void createsLinkToUnmappedController() {
@SuppressWarnings("unchecked")
public void usesIdOfIdentifyableForPathSegment() {
- Identifiable<Long> identifyable = mock(Identifiable.class);
- when(identifyable.getId()).thenReturn(10L);
+ Identifiable<Long> identifyable = Mockito.mock(Identifiable.class);
+ Mockito.when(identifyable.getId()).thenReturn(10L);
Link link = linkTo(PersonControllerImpl.class).slash(identifyable).withSelfRel();
- assertThat(link.getHref(), Matchers.endsWith("/people/10"));
+ assertThat(link.getHref(), endsWith("/people/10"));
}
@Test
public void appendingNullIsANoOp() {
Link link = linkTo(PersonControllerImpl.class).slash(null).withSelfRel();
- assertThat(link.getHref(), Matchers.endsWith("/people"));
+ assertThat(link.getHref(), endsWith("/people"));
link = linkTo(PersonControllerImpl.class).slash((Object) null).withSelfRel();
- assertThat(link.getHref(), Matchers.endsWith("/people"));
+ assertThat(link.getHref(), endsWith("/people"));
}
@Test
public void linksToMethod() {
Link link = linkTo(methodOn(ControllerWithMethods.class).myMethod(null)).withSelfRel();
- assertThat(link.getHref(), Matchers.endsWith("/something/else"));
+ assertThat(link.getHref(), endsWith("/something/else"));
}
@Test
public void linksToMethodWithPathVariable() {
Link link = linkTo(methodOn(ControllerWithMethods.class).methodWithPathVariable("1")).withSelfRel();
- assertThat(link.getHref(), Matchers.endsWith("/something/1/foo"));
+ assertThat(link.getHref(), endsWith("/something/1/foo"));
+ }
+
+ /**
+ * @see #33
+ */
+ @Test
+ public void usesForwardedHostAsHostIfHeaderIsSet() {
+
+ request.addHeader("X-Forwarded-Host", "somethingDifferent");
+
+ Link link = linkTo(PersonControllerImpl.class).withSelfRel();
+ assertThat(link.getHref(), startsWith("http://somethingDifferent"));
}
static class Person implements Identifiable<Long> {
View
@@ -5,6 +5,7 @@ Bundle-ManifestVersion: 2
Import-Template:
com.fasterxml.jackson.*;version="${jackson2.version:[=.=.=,+1.0.0)}";resolution:=optional,
com.jayway.jsonpath.*;version="${jsonpath.version:[=.=.=,+1.0.0)}";resolution:=optional,
+ javax.servlet.*;version="[2.5,4.0)";resolution:=optional,
javax.ws.rs.*;version="${jaxrs.version:[=.=.=,+1.0.0)}";resolution:=optional,
javax.xml.bind.*;version="0",
net.minidev.json.*;version="${minidevjson.version:[=.=.=,+1.0.0)}";resolution:=optional,

0 comments on commit 3f8b90e

Please sign in to comment.