From 7b270c4b46c20d42233218082fae0075c3a5c481 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 4 Dec 2014 09:57:34 +0000 Subject: [PATCH] Add support for X-Forwarded-Prefix to Zuul proxy Fixes gh-43 --- .../cloud/netflix/zuul/ProxyRouteLocator.java | 49 +++++----- .../zuul/filters/pre/PreDecorationFilter.java | 6 +- .../filters/pre/PreDecorationFilterTests.java | 93 +++++++++++++++++++ 3 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocator.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocator.java index e77542ef..7c84241a 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocator.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocator.java @@ -32,7 +32,7 @@ public class ProxyRouteLocator { private AtomicReference> routes = new AtomicReference<>(); - private Map staticRoutes = new LinkedHashMap(); + private Map staticRoutes = new LinkedHashMap(); public ProxyRouteLocator(DiscoveryClient discovery, ZuulProperties properties) { this.discovery = discovery; @@ -40,7 +40,12 @@ public class ProxyRouteLocator { } public void addRoute(String path, String location) { - staticRoutes.put(path, location); + staticRoutes.put(path, new ZuulRoute(path, location)); + resetRoutes(); + } + + public void addRoute(ZuulRoute route) { + staticRoutes.put(route.getPath(), route); resetRoutes(); } @@ -56,7 +61,7 @@ public class ProxyRouteLocator { Map values = new LinkedHashMap(); - for (String key : routes.get().keySet()) { + for (String key : routes.get().keySet()) { String url = key; values.put(url, routes.get().get(key).getLocation()); } @@ -67,26 +72,30 @@ public class ProxyRouteLocator { public ProxyRouteSpec getMatchingRoute(String path) { String location = null; String targetPath = null; + String prefix = properties.getPrefix(); for (Entry entry : routes.get().entrySet()) { String pattern = entry.getKey(); if (pathMatcher.match(pattern, path)) { ZuulRoute route = entry.getValue(); - String prefix = properties.getPrefix(); location = route.getLocation(); targetPath = path; if (path.startsWith(prefix) && properties.isStripPrefix()) { targetPath = path.substring(prefix.length()); } if (route.isStripPrefix()) { - int index = route.getPath().indexOf("*"); - index = index > 0 ? index-1 : 0; - targetPath = path.substring(index); + int index = route.getPath().indexOf("*") - 1; + if (index > 0) { + targetPath = path.substring(prefix.length() + index); + prefix = prefix + + path.substring(prefix.length(), prefix.length() + index); + } } + break; } } - return location==null ? null : new ProxyRouteSpec(targetPath, location); + return location == null ? null : new ProxyRouteSpec(targetPath, location, prefix); } - + // Package access so ZuulHandlerMapping can reset it's mappings void resetRoutes() { routes.set(locateRoutes()); @@ -97,7 +106,7 @@ public class ProxyRouteLocator { LinkedHashMap routesMap = new LinkedHashMap<>(); addConfiguredRoutes(routesMap); - addStaticRoutes(routesMap); + routesMap.putAll(staticRoutes); // Add routes for discovery services by default List services = discovery.getServices(); @@ -120,7 +129,7 @@ public class ProxyRouteLocator { LinkedHashMap values = new LinkedHashMap<>(); for (Entry entry : routesMap.entrySet()) { - + String path = entry.getKey(); // Prepend with slash if not already present. if (!path.startsWith("/")) { @@ -133,7 +142,7 @@ public class ProxyRouteLocator { path = "/" + path; } } - + values.put(path, entry.getValue()); } @@ -142,12 +151,6 @@ public class ProxyRouteLocator { } - protected void addStaticRoutes(LinkedHashMap routes) { - for (Entry entry : staticRoutes.entrySet()) { - routes.put(entry.getKey(), new ZuulRoute(entry.getKey(), entry.getValue())); - } - } - protected void addConfiguredRoutes(Map routes) { Map routeEntries = properties.getRoutesWithDefaultServiceIds(); for (ZuulRoute entry : routeEntries.values()) { @@ -162,13 +165,14 @@ public class ProxyRouteLocator { public String getTargetPath(String matchingRoute, String requestURI) { String path = getRoutes().get(matchingRoute); - if (path==null) { + if (path == null) { path = requestURI; - } else { - + } + else { + } return path; - + } @Data @@ -176,6 +180,7 @@ public class ProxyRouteLocator { public static class ProxyRouteSpec { private String path; private String location; + private String prefix; } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java index 6ab499d1..f4d0ac11 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java @@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory; import org.springframework.cloud.netflix.zuul.ProxyRouteLocator; import org.springframework.cloud.netflix.zuul.ProxyRouteLocator.ProxyRouteSpec; import org.springframework.cloud.netflix.zuul.ZuulProperties; +import org.springframework.util.StringUtils; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; @@ -49,7 +50,7 @@ public class PreDecorationFilter extends ZuulFilter { ProxyRouteSpec route = routeLocator.getMatchingRoute(requestURI); - if (route!=null) { + if (route != null) { String location = route.getLocation(); @@ -73,6 +74,9 @@ public class PreDecorationFilter extends ZuulFilter { "X-Forwarded-Host", ctx.getRequest().getServerName() + ":" + String.valueOf(ctx.getRequest().getServerPort())); + if (StringUtils.hasText(route.getPrefix())) { + ctx.addZuulRequestHeader("X-Forwarded-Prefix", route.getPrefix()); + } } } } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java new file mode 100644 index 00000000..e1c26b3e --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java @@ -0,0 +1,93 @@ +package org.springframework.cloud.netflix.zuul.filters.pre; + +import static org.junit.Assert.assertEquals; +import static org.mockito.MockitoAnnotations.initMocks; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.netflix.zuul.ProxyRouteLocator; +import org.springframework.cloud.netflix.zuul.ZuulProperties; +import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute; +import org.springframework.mock.web.MockHttpServletRequest; + +import com.netflix.util.Pair; +import com.netflix.zuul.context.RequestContext; + +/** + * @author Dave Syer + * + */ +public class PreDecorationFilterTests { + + private PreDecorationFilter filter; + + @Mock + DiscoveryClient discovery; + + private ZuulProperties properties = new ZuulProperties(); + + private ProxyRouteLocator routeLocator; + + private MockHttpServletRequest request = new MockHttpServletRequest(); + + @Before + public void init() { + initMocks(this); + routeLocator = new ProxyRouteLocator(discovery, properties); + filter = new PreDecorationFilter(routeLocator, properties); + RequestContext ctx = RequestContext.getCurrentContext(); + ctx.setRequest(request); + } + + @Test + public void basicProperties() throws Exception { + assertEquals(5, filter.filterOrder()); + assertEquals(true, filter.shouldFilter()); + assertEquals("pre", filter.filterType()); + } + + @Test + public void prefixRouteAddsHeader() throws Exception { + properties.setPrefix("/api"); + properties.setStripPrefix(true); + request.setRequestURI("/api/foo/1"); + routeLocator.addRoute("/foo/**", "foo"); + filter.run(); + RequestContext ctx = RequestContext.getCurrentContext(); + assertEquals("/foo/1", ctx.get("requestURI")); + assertEquals("localhost:80", ctx.getZuulRequestHeaders().get("x-forwarded-host")); + assertEquals("/api", ctx.getZuulRequestHeaders().get("x-forwarded-prefix")); + assertEquals("foo", getHeader(ctx.getOriginResponseHeaders(), "x-zuul-serviceid")); + } + + @Test + public void prefixRouteWithRouteStrippingAddsHeader() throws Exception { + properties.setPrefix("/api"); + properties.setStripPrefix(true); + request.setRequestURI("/api/foo/1"); + routeLocator.addRoute(new ZuulRoute("/foo/**", "foo", null, true)); + filter.run(); + RequestContext ctx = RequestContext.getCurrentContext(); + assertEquals("/1", ctx.get("requestURI")); + assertEquals("localhost:80", ctx.getZuulRequestHeaders().get("x-forwarded-host")); + assertEquals("/api/foo", ctx.getZuulRequestHeaders().get("x-forwarded-prefix")); + assertEquals("foo", getHeader(ctx.getOriginResponseHeaders(), "x-zuul-serviceid")); + } + + private Object getHeader(List> headers, + String key) { + String value = null; + for (Pair pair : headers) { + if (pair.first().toLowerCase().equals(key.toLowerCase())) { + value = pair.second(); + break; + } + } + return value; + } + +}