#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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user