Browse Source
* Support for re-writing Location Headers * Added additional constructor for Route, removed auto-configuration of LocationRewriteFilter * Polished docs, more testspull/6/head
Biju Kunjummen
7 years ago
committed by
Spencer Gibb
7 changed files with 485 additions and 2 deletions
@ -0,0 +1,160 @@
@@ -0,0 +1,160 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
package org.springframework.cloud.netflix.zuul.filters.post; |
||||
|
||||
import com.netflix.util.Pair; |
||||
import com.netflix.zuul.ZuulFilter; |
||||
import com.netflix.zuul.context.RequestContext; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.cloud.netflix.zuul.filters.Route; |
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator; |
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.server.ServletServerHttpRequest; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.web.util.UriComponents; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
import org.springframework.web.util.UrlPathHelper; |
||||
|
||||
import java.net.URI; |
||||
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE; |
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_RESPONSE_FILTER_ORDER; |
||||
|
||||
/** |
||||
* {@link ZuulFilter} Responsible for rewriting the Location header to be the Zuul URL |
||||
* |
||||
* @author Biju Kunjummen |
||||
*/ |
||||
public class LocationRewriteFilter extends ZuulFilter { |
||||
|
||||
private final UrlPathHelper urlPathHelper = new UrlPathHelper(); |
||||
|
||||
@Autowired |
||||
private ZuulProperties zuulProperties; |
||||
|
||||
@Autowired |
||||
private RouteLocator routeLocator; |
||||
|
||||
private static final String LOCATION_HEADER = "Location"; |
||||
|
||||
public LocationRewriteFilter() { |
||||
} |
||||
|
||||
public LocationRewriteFilter(ZuulProperties zuulProperties, |
||||
RouteLocator routeLocator) { |
||||
this.routeLocator = routeLocator; |
||||
this.zuulProperties = zuulProperties; |
||||
} |
||||
|
||||
@Override |
||||
public String filterType() { |
||||
return POST_TYPE; |
||||
} |
||||
|
||||
@Override |
||||
public int filterOrder() { |
||||
return SEND_RESPONSE_FILTER_ORDER - 100; |
||||
} |
||||
|
||||
@Override |
||||
public boolean shouldFilter() { |
||||
RequestContext ctx = RequestContext.getCurrentContext(); |
||||
int statusCode = ctx.getResponseStatusCode(); |
||||
return HttpStatus.valueOf(statusCode).is3xxRedirection(); |
||||
} |
||||
|
||||
@Override |
||||
public Object run() { |
||||
RequestContext ctx = RequestContext.getCurrentContext(); |
||||
Route route = routeLocator.getMatchingRoute( |
||||
urlPathHelper.getPathWithinApplication(ctx.getRequest())); |
||||
|
||||
if (route != null) { |
||||
Pair<String, String> lh = locationHeader(ctx); |
||||
if (lh != null) { |
||||
String location = lh.second(); |
||||
URI originalRequestUri = UriComponentsBuilder |
||||
.fromHttpRequest(new ServletServerHttpRequest(ctx.getRequest())) |
||||
.build().toUri(); |
||||
|
||||
UriComponentsBuilder redirectedUriBuilder = UriComponentsBuilder |
||||
.fromUriString(location); |
||||
|
||||
UriComponents redirectedUriComps = redirectedUriBuilder.build(); |
||||
|
||||
String newPath = getRestoredPath(this.zuulProperties, route, |
||||
redirectedUriComps); |
||||
|
||||
String modifiedLocation = redirectedUriBuilder |
||||
.scheme(originalRequestUri.getScheme()) |
||||
.host(originalRequestUri.getHost()) |
||||
.port(originalRequestUri.getPort()).replacePath(newPath).build() |
||||
.toUriString(); |
||||
|
||||
lh.setSecond(modifiedLocation); |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private String getRestoredPath(ZuulProperties zuulProperties, Route route, |
||||
UriComponents redirectedUriComps) { |
||||
StringBuilder path = new StringBuilder(); |
||||
String redirectedPathWithoutGlobal = downstreamHasGlobalPrefix(zuulProperties) |
||||
? redirectedUriComps.getPath() |
||||
.substring(("/" + zuulProperties.getPrefix()).length()) |
||||
: redirectedUriComps.getPath(); |
||||
|
||||
if (downstreamHasGlobalPrefix(zuulProperties)) { |
||||
path.append("/" + zuulProperties.getPrefix()); |
||||
} |
||||
else { |
||||
path.append(zuulHasGlobalPrefix(zuulProperties) |
||||
? "/" + zuulProperties.getPrefix() : ""); |
||||
} |
||||
|
||||
path.append(downstreamHasRoutePrefix(route) ? "" : "/" + route.getPrefix()) |
||||
.append(redirectedPathWithoutGlobal); |
||||
|
||||
return path.toString(); |
||||
} |
||||
|
||||
private boolean downstreamHasGlobalPrefix(ZuulProperties zuulProperties) { |
||||
return (!zuulProperties.isStripPrefix() |
||||
&& StringUtils.hasText(zuulProperties.getPrefix())); |
||||
} |
||||
|
||||
private boolean zuulHasGlobalPrefix(ZuulProperties zuulProperties) { |
||||
return StringUtils.hasText(zuulProperties.getPrefix()); |
||||
} |
||||
|
||||
private boolean downstreamHasRoutePrefix(Route route) { |
||||
return (!route.isPrefixStripped() && StringUtils.hasText(route.getPrefix())); |
||||
} |
||||
|
||||
private Pair<String, String> locationHeader(RequestContext ctx) { |
||||
if (ctx.getZuulResponseHeaders() != null) { |
||||
for (Pair<String, String> pair : ctx.getZuulResponseHeaders()) { |
||||
if (pair.first().equals(LOCATION_HEADER)) { |
||||
return pair; |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
} |
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.post; |
||||
|
||||
import com.netflix.loadbalancer.Server; |
||||
import com.netflix.loadbalancer.ServerList; |
||||
import com.netflix.zuul.context.RequestContext; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.springframework.boot.SpringBootConfiguration; |
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
||||
import org.springframework.boot.context.embedded.LocalServerPort; |
||||
import org.springframework.boot.test.context.SpringBootTest; |
||||
import org.springframework.boot.test.web.client.TestRestTemplate; |
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient; |
||||
import org.springframework.cloud.netflix.ribbon.StaticServerList; |
||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.test.annotation.DirtiesContext; |
||||
import org.springframework.test.context.junit4.SpringRunner; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
||||
import java.util.List; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* @author Biju Kunjummen |
||||
*/ |
||||
|
||||
@RunWith(SpringRunner.class) |
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { |
||||
"zuul.routes.aservice.path:/service/**", "zuul.routes.aservice.strip-prefix:true", |
||||
"eureka.client.enabled:false" }) |
||||
@DirtiesContext |
||||
public class LocationRewriteFilterIntegrationTests { |
||||
|
||||
@LocalServerPort |
||||
private int port; |
||||
|
||||
@Before |
||||
public void before() { |
||||
RequestContext context = new RequestContext(); |
||||
RequestContext.testSetCurrentContext(context); |
||||
} |
||||
|
||||
@Test |
||||
public void testWithRedirectPrefixStripped() { |
||||
String url = "http://localhost:" + port + "/service/redirectingUri"; |
||||
ResponseEntity<String> response = new TestRestTemplate().getForEntity(url, |
||||
String.class); |
||||
List<String> locationHeaders = response.getHeaders().get("Location"); |
||||
|
||||
assertThat(locationHeaders).hasSize(1); |
||||
String locationHeader = locationHeaders.get(0); |
||||
assertThat(locationHeader).withFailMessage("Location should have prefix") |
||||
.isEqualTo( |
||||
String.format("http://localhost:%d/service/redirectedUri", port)); |
||||
|
||||
} |
||||
|
||||
@SpringBootConfiguration |
||||
@EnableAutoConfiguration |
||||
@EnableZuulProxy |
||||
@Controller |
||||
@RibbonClient(name = "aservice", configuration = RibbonConfig.class) |
||||
protected static class Config { |
||||
|
||||
@RequestMapping("/redirectingUri") |
||||
public String redirect1() { |
||||
return "redirect:/redirectedUri"; |
||||
} |
||||
|
||||
@Bean |
||||
public LocationRewriteFilter locationRewriteFilter() { |
||||
return new LocationRewriteFilter(); |
||||
} |
||||
|
||||
} |
||||
|
||||
public static class RibbonConfig { |
||||
@LocalServerPort |
||||
private int port; |
||||
|
||||
@Bean |
||||
public ServerList<Server> ribbonServerList() { |
||||
return new StaticServerList<>(new Server("localhost", this.port)); |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,178 @@
@@ -0,0 +1,178 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.post; |
||||
|
||||
import com.netflix.util.Pair; |
||||
import com.netflix.zuul.context.RequestContext; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.cloud.netflix.zuul.filters.Route; |
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator; |
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
/** |
||||
* @author Biju Kunjummen |
||||
*/ |
||||
|
||||
public class LocationRewriteFilterTests { |
||||
|
||||
private final String ZUUL_HOST = "myzuul.com"; |
||||
private final String ZUUL_SCHEME = "https"; |
||||
private final int ZUUL_PORT = 8443; |
||||
private final String ZUUL_BASE_URL = String.format("%s://%s:%d", ZUUL_SCHEME, |
||||
ZUUL_HOST, ZUUL_PORT); |
||||
|
||||
private final String SERVER_HOST = "someserver.com"; |
||||
private final String SERVER_SCHEME = "http"; |
||||
private final int SERVER_PORT = 8564; |
||||
private final String SERVER_BASE_URL = String.format("%s://%s:%d", SERVER_SCHEME, |
||||
SERVER_HOST, SERVER_PORT); |
||||
|
||||
@Before |
||||
public void before() { |
||||
RequestContext context = new RequestContext(); |
||||
RequestContext.testSetCurrentContext(context); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRewriteLocationHeadersWithRoutePrefix() { |
||||
RequestContext context = RequestContext.getCurrentContext(); |
||||
ZuulProperties zuulProperties = new ZuulProperties(); |
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties, |
||||
new Route("service1", "/redirectingUri", "service1", "prefix", false, |
||||
Collections.EMPTY_SET, true), |
||||
"/prefix/redirectingUri", "/redirectedUri;someparam?param1=abc"); |
||||
filter.run(); |
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String |
||||
.format("%s/prefix/redirectedUri;someparam?param1=abc", ZUUL_BASE_URL)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldBeUntouchedIfNoRoutesFound() { |
||||
RequestContext context = RequestContext.getCurrentContext(); |
||||
ZuulProperties zuulProperties = new ZuulProperties(); |
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties, null, |
||||
"/prefix/redirectingUri", "/redirectedUri;someparam?param1=abc"); |
||||
filter.run(); |
||||
assertThat(getLocationHeader(context).second()).isEqualTo( |
||||
String.format("%s/redirectedUri;someparam?param1=abc", SERVER_BASE_URL)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRewriteLocationHeadersIfPrefixIsNotStripped() { |
||||
RequestContext context = RequestContext.getCurrentContext(); |
||||
ZuulProperties zuulProperties = new ZuulProperties(); |
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties, |
||||
new Route("service1", "/something/redirectingUri", "service1", "prefix", |
||||
false, Collections.EMPTY_SET, false), |
||||
"/prefix/redirectingUri", |
||||
"/something/redirectedUri;someparam?param1=abc"); |
||||
filter.run(); |
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String.format( |
||||
"%s/something/redirectedUri;someparam?param1=abc", ZUUL_BASE_URL)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRewriteLocationHeadersIfPrefixIsEmpty() { |
||||
RequestContext context = RequestContext.getCurrentContext(); |
||||
ZuulProperties zuulProperties = new ZuulProperties(); |
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties, |
||||
new Route("service1", "/something/redirectingUri", "service1", "", false, |
||||
Collections.EMPTY_SET, true), |
||||
"/redirectingUri", "/something/redirectedUri;someparam?param1=abc"); |
||||
filter.run(); |
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String.format( |
||||
"%s/something/redirectedUri;someparam?param1=abc", ZUUL_BASE_URL)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAddBackGlobalPrefixIfPresent() { |
||||
RequestContext context = RequestContext.getCurrentContext(); |
||||
ZuulProperties zuulProperties = new ZuulProperties(); |
||||
zuulProperties.setPrefix("global"); |
||||
zuulProperties.setStripPrefix(true); |
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties, |
||||
new Route("service1", "/something/redirectingUri", "service1", "prefix", |
||||
false, Collections.EMPTY_SET, true), |
||||
"/global/prefix/redirectingUri", |
||||
"/something/redirectedUri;someparam?param1=abc"); |
||||
filter.run(); |
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String.format( |
||||
"%s/global/prefix/something/redirectedUri;someparam?param1=abc", |
||||
ZUUL_BASE_URL)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotAddBackGlobalPrefixIfNotStripped() { |
||||
RequestContext context = RequestContext.getCurrentContext(); |
||||
ZuulProperties zuulProperties = new ZuulProperties(); |
||||
zuulProperties.setPrefix("global"); |
||||
zuulProperties.setStripPrefix(false); |
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties, |
||||
new Route("service1", "/something/redirectingUri", "service1", "prefix", |
||||
false, Collections.EMPTY_SET, true), |
||||
"/global/prefix/redirectingUri", |
||||
"/global/something/redirectedUri;someparam?param1=abc"); |
||||
filter.run(); |
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String.format( |
||||
"%s/global/prefix/something/redirectedUri;someparam?param1=abc", |
||||
ZUUL_BASE_URL)); |
||||
} |
||||
|
||||
private LocationRewriteFilter setFilterUpWith(RequestContext context, |
||||
ZuulProperties zuulProperties, Route route, String toZuulRequestUri, |
||||
String redirectedUri) { |
||||
MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); |
||||
httpServletRequest.setRequestURI(toZuulRequestUri); |
||||
httpServletRequest.setServerName(ZUUL_HOST); |
||||
httpServletRequest.setScheme(ZUUL_SCHEME); |
||||
httpServletRequest.setServerPort(ZUUL_PORT); |
||||
context.setRequest(httpServletRequest); |
||||
|
||||
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); |
||||
context.getZuulResponseHeaders().add(new Pair<>("Location", |
||||
String.format("%s%s", SERVER_BASE_URL, redirectedUri))); |
||||
context.setResponse(httpServletResponse); |
||||
|
||||
RouteLocator routeLocator = mock(RouteLocator.class); |
||||
when(routeLocator.getMatchingRoute(toZuulRequestUri)).thenReturn(route); |
||||
LocationRewriteFilter filter = new LocationRewriteFilter(zuulProperties, |
||||
routeLocator); |
||||
|
||||
return filter; |
||||
} |
||||
|
||||
private Pair<String, String> getLocationHeader(RequestContext ctx) { |
||||
if (ctx.getZuulResponseHeaders() != null) { |
||||
for (Pair<String, String> pair : ctx.getZuulResponseHeaders()) { |
||||
if (pair.first().equals("Location")) { |
||||
return pair; |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
} |
Loading…
Reference in new issue