#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
This commit is contained in:
Oliver Gierke
2012-12-19 17:34:47 +01:00
parent 25f68fa208
commit 3f8b90e2b5
4 changed files with 70 additions and 17 deletions

View File

@@ -21,6 +21,8 @@ import java.util.HashMap;
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.DummyInvocationUtils.LastInvocationAware
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 class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
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 class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
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 class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
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 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
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;
}
}

View File

@@ -18,8 +18,6 @@ package org.springframework.hateoas;
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 @@ import org.springframework.web.context.request.ServletRequestAttributes;
*/
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);
}

View File

@@ -17,11 +17,11 @@ package org.springframework.hateoas.mvc;
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,7 +48,7 @@ public class ControllerLinkBuilderUnitTest extends TestUtils {
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
@@ -56,7 +56,7 @@ public class ControllerLinkBuilderUnitTest extends TestUtils {
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
@@ -64,7 +64,7 @@ public class ControllerLinkBuilderUnitTest extends TestUtils {
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 class ControllerLinkBuilderUnitTest extends TestUtils {
@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 File

@@ -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,