diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 80651d07..c7cd020e 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -551,7 +551,19 @@ The location of the backend can be specified as either a "serviceId" Forwarding to the service is protected by a Hystrix circuit breaker so if a service is down the client will see an error, but once the circuit is open the proxy will not try to contact the service. -To add a prefix to the mapping, set `zuul.prefix` to a value, such as `/api`. To strip the proxy prefix from the request before the request is forwarded set `zuul.stripPrefix = true`. +To add a prefix to all mappings, set `zuul.prefix` to a value, such as `/api`. To strip the proxy prefix from the request before the request is forwarded set `zuul.stripPrefix = true`. You can also strip the non-wildcard prefix from individual routes, e.g. + +.application.yml +[source,yaml] +---- + zuul: + routes: + users: + path: /myusers/** + stripPrefix: true +---- + +In this example requests to "/myusers/101" will be forwarded to "/101" on the "users" service (the path is stripped up to the first wildcard character). The `X-Forwarded-Host` header added to the forwarded requests by default. To turn it off set `zuul.addProxyHeaders = false`. 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 new file mode 100644 index 00000000..627b6fbe --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocator.java @@ -0,0 +1,206 @@ +package org.springframework.cloud.netflix.zuul; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicReference; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.CompositePropertySource; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * @author Spencer Gibb + */ +@Slf4j +public class ProxyRouteLocator implements ApplicationListener { + + public static final String DEFAULT_ROUTE = "/"; + + private DiscoveryClient discovery; + + private ZuulProperties properties; + + private PathMatcher pathMatcher = new AntPathMatcher(); + + private Field propertySourcesField; + + private AtomicReference> routes = new AtomicReference<>(); + + private Map staticRoutes = new LinkedHashMap(); + + public ProxyRouteLocator(DiscoveryClient discovery, ZuulProperties properties) { + this.discovery = discovery; + this.properties = properties; + initField(); + } + + private void initField() { + propertySourcesField = ReflectionUtils.findField(CompositePropertySource.class, + "propertySources"); + propertySourcesField.setAccessible(true); + } + + @Override + public void onApplicationEvent(EnvironmentChangeEvent event) { + for (String key : event.getKeys()) { + if (key.startsWith("zuul.routes")) { + resetRoutes(); + return; + } + } + } + + public void addRoute(String path, String location) { + staticRoutes.put(path, location); + resetRoutes(); + } + + public Collection getRoutePaths() { + return getRoutes().keySet(); + } + + public Map getRoutes() { + + if (routes.get() == null) { + routes.set(locateRoutes()); + } + + Map values = new LinkedHashMap(); + + for (String key : routes.get().keySet()) { + String url = key; + values.put(url, routes.get().get(key).getLocation()); + } + return values; + + } + + public ProxyRouteSpec getMatchingRoute(String path) { + String location = null; + String targetPath = null; + 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); + } + } + } + return location==null ? null : new ProxyRouteSpec(targetPath, location); + } + + // Package access so ZuulHandlerMapping can reset it's mappings + void resetRoutes() { + routes.set(locateRoutes()); + } + + protected LinkedHashMap locateRoutes() { + + LinkedHashMap routesMap = new LinkedHashMap<>(); + + addConfiguredRoutes(routesMap); + addStaticRoutes(routesMap); + + // Add routes for discovery services by default + List services = discovery.getServices(); + for (String serviceId : services) { + // Ignore specifically ignored services and those that were manually + // configured + String key = "/" + serviceId + "/**"; + if (!properties.getIgnoredServices().contains(serviceId) + && !routesMap.containsKey(key)) { + routesMap.put(key, new ZuulRoute(key, serviceId)); + } + } + + if (routesMap.get(DEFAULT_ROUTE) != null) { + String defaultServiceId = routesMap.get(DEFAULT_ROUTE).getServiceId(); + // Move the defaultServiceId to the end + routesMap.remove(DEFAULT_ROUTE); + routesMap.put(DEFAULT_ROUTE, new ZuulRoute(defaultServiceId)); + } + + LinkedHashMap values = new LinkedHashMap<>(); + for (Entry entry : routesMap.entrySet()) { + + String path = entry.getKey(); + // Prepend with slash if not already present. + if (!path.startsWith("/")) { + path = "/" + path; + } + + if (StringUtils.hasText(properties.getPrefix())) { + path = properties.getPrefix() + path; + if (!path.startsWith("/")) { + path = "/" + path; + } + } + + values.put(path, entry.getValue()); + + } + + return values; + + } + + 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()) { + String route = entry.getPath(); + if (routes.containsKey(route)) { + log.warn("Overwriting route {}: already defined by {}", route, + routes.get(route)); + } + routes.put(route, entry); + } + } + + public String getTargetPath(String matchingRoute, String requestURI) { + String path = getRoutes().get(matchingRoute); + if (path==null) { + path = requestURI; + } else { + + } + return path; + + } + + @Data + @AllArgsConstructor + public static class ProxyRouteSpec { + private String path; + private String location; + } + +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java index 666b6480..3792363c 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java @@ -40,8 +40,8 @@ public class ZuulConfiguration { private ZuulProperties zuulProperties; @Bean - public ZuulRouteLocator routes() { - return new ZuulRouteLocator(discovery, zuulProperties); + public ProxyRouteLocator routes() { + return new ProxyRouteLocator(discovery, zuulProperties); } @Bean @@ -51,7 +51,7 @@ public class ZuulConfiguration { @Bean public ZuulHandlerMapping zuulHandlerMapping() { - return new ZuulHandlerMapping(routes(), zuulController(), zuulProperties); + return new ZuulHandlerMapping(routes(), zuulController()); } @Configuration diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java index b050cd00..29a54368 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java @@ -11,7 +11,6 @@ import org.springframework.context.ApplicationListener; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @@ -27,24 +26,20 @@ import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements ApplicationListener, MvcEndpoint { - private ZuulRouteLocator routeLocator; + private ProxyRouteLocator routeLocator; private ZuulController zuul; - private ZuulProperties properties; - @Autowired - public ZuulHandlerMapping(ZuulRouteLocator routeLocator, ZuulController zuul, - ZuulProperties properties) { + public ZuulHandlerMapping(ProxyRouteLocator routeLocator, ZuulController zuul) { this.routeLocator = routeLocator; this.zuul = zuul; - this.properties = properties; setOrder(-200); } @Override public void onApplicationEvent(InstanceRegisteredEvent event) { - registerHandlers(routeLocator.getRoutes().keySet()); + registerHandlers(routeLocator.getRoutePaths()); } protected void registerHandlers(Collection routes) { @@ -53,18 +48,6 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements } else { for (String url : routes) { - // Prepend with slash if not already present. - if (!url.startsWith("/")) { - url = "/" + url; - } - - if (StringUtils.hasText(properties.getPrefix())) { - url = properties.getPrefix() + url; - if (!url.startsWith("/")) { - url = "/" + url; - } - } - registerHandler(url, zuul); } } @@ -75,7 +58,7 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements @ManagedOperation public Map reset() { routeLocator.resetRoutes(); - registerHandlers(routeLocator.getRoutes().keySet()); + registerHandlers(routeLocator.getRoutePaths()); return getRoutes(); } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProperties.java index d4f90b04..2f7d6de3 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProperties.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProperties.java @@ -43,7 +43,7 @@ public class ZuulProperties { private String path; private String serviceId; private String url; - private boolean stripPath = false; + private boolean stripPrefix = false; public ZuulRoute(String text) { String location = null; diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulRouteLocator.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulRouteLocator.java deleted file mode 100644 index a8cc70da..00000000 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulRouteLocator.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import java.lang.reflect.Field; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import lombok.extern.slf4j.Slf4j; - -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.cloud.context.environment.EnvironmentChangeEvent; -import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute; -import org.springframework.context.ApplicationListener; -import org.springframework.core.env.CompositePropertySource; -import org.springframework.util.ReflectionUtils; - -/** - * @author Spencer Gibb - */ -@Slf4j -public class ZuulRouteLocator implements ApplicationListener { - - public static final String DEFAULT_ROUTE = "/"; - - private DiscoveryClient discovery; - - private ZuulProperties properties; - - private Field propertySourcesField; - private AtomicReference> routes = new AtomicReference<>(); - - public ZuulRouteLocator(DiscoveryClient discovery, ZuulProperties properties) { - this.discovery = discovery; - this.properties = properties; - initField(); - } - - private void initField() { - propertySourcesField = ReflectionUtils.findField(CompositePropertySource.class, - "propertySources"); - propertySourcesField.setAccessible(true); - } - - @Override - public void onApplicationEvent(EnvironmentChangeEvent event) { - for (String key : event.getKeys()) { - if (key.startsWith("zuul.route")) { - resetRoutes(); - return; - } - } - } - - public Collection getRoutePaths() { - return getRoutes().keySet(); - } - - public Map getRoutes() { - if (routes.get() == null) { - resetRoutes(); - } - - return routes.get(); - } - - //access so ZuulHandlerMapping actuator can reset it's mappings - /*package*/ void resetRoutes() { - LinkedHashMap newValue = locateRoutes(); - routes.set(newValue); - } - - protected LinkedHashMap locateRoutes() { - LinkedHashMap routesMap = new LinkedHashMap<>(); - - addConfiguredRoutes(routesMap); - - String defaultServiceId = routesMap.get(DEFAULT_ROUTE); - - // Add routes for discovery services by default - List services = discovery.getServices(); - for (String serviceId : services) { - // Ignore specifically ignored services and those that were manually configured - String key = "/" + serviceId + "/**"; - if (!properties.getIgnoredServices().contains(serviceId) && !routesMap.containsKey(key)) { - routesMap.put(key, serviceId); - } - } - - if (defaultServiceId != null) { - // move the defaultServiceId to the end - routesMap.remove(DEFAULT_ROUTE); - routesMap.put(DEFAULT_ROUTE, defaultServiceId); - } - return routesMap; - } - - protected void addConfiguredRoutes(Map routes) { - Map routeEntries = properties.getRoutesWithDefaultServiceIds(); - for (ZuulRoute entry : routeEntries.values()) { - String location = entry.getLocation(); - String route = entry.getPath(); - - if (routes.containsKey(route)) { - log.warn("Overwriting route {}: already defined by {}", route, - routes.get(route)); - } - routes.put(route, location); - } - } - -} 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 dae4692c..6ab499d1 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 @@ -1,37 +1,27 @@ package org.springframework.cloud.netflix.zuul.filters.pre; -import static com.google.common.collect.Iterables.tryFind; - import java.net.MalformedURLException; import java.net.URL; -import java.util.Map; -import javax.annotation.Nullable; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.cloud.netflix.zuul.ZuulRouteLocator; +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.AntPathMatcher; -import org.springframework.util.PathMatcher; -import org.springframework.util.StringUtils; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; public class PreDecorationFilter extends ZuulFilter { private static Logger LOG = LoggerFactory.getLogger(PreDecorationFilter.class); - private ZuulRouteLocator routeLocator; + private ProxyRouteLocator routeLocator; private ZuulProperties properties; - private PathMatcher pathMatcher = new AntPathMatcher(); - - public PreDecorationFilter(ZuulRouteLocator routeLocator, ZuulProperties properties) { + public PreDecorationFilter(ProxyRouteLocator routeLocator, ZuulProperties properties) { this.routeLocator = routeLocator; this.properties = properties; } @@ -55,44 +45,27 @@ public class PreDecorationFilter extends ZuulFilter { public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); - String requestURI = ctx.getRequest().getRequestURI(); + final String requestURI = ctx.getRequest().getRequestURI(); - String proxyMapping = properties.getPrefix(); + ProxyRouteSpec route = routeLocator.getMatchingRoute(requestURI); - final String uriPart; - if (StringUtils.hasText(proxyMapping) && properties.isStripPrefix() - && requestURI.startsWith(proxyMapping)) { - // TODO: better strategy? - uriPart = requestURI.substring(proxyMapping.length()); - } - else { - uriPart = requestURI; - } - ctx.put("requestURI", uriPart); + if (route!=null) { - Map routesMap = routeLocator.getRoutes(); + String location = route.getLocation(); - Optional route = tryFind(routesMap.keySet(), new Predicate() { - @Override - public boolean apply(@Nullable String path) { - return pathMatcher.match(path, uriPart); - } - }); + if (location != null) { - if (route.isPresent()) { - String target = routesMap.get(route.get()); + ctx.put("requestURI", route.getPath()); - if (target != null) { - - if (target.startsWith("http:") || target.startsWith("https:")) { - ctx.setRouteHost(getUrl(target)); - ctx.addOriginResponseHeader("X-Zuul-Service", target); + if (location.startsWith("http:") || location.startsWith("https:")) { + ctx.setRouteHost(getUrl(location)); + ctx.addOriginResponseHeader("X-Zuul-Service", location); } else { // set serviceId for use in filters.route.RibbonRequest - ctx.set("serviceId", target); + ctx.set("serviceId", location); ctx.setRouteHost(null); - ctx.addOriginResponseHeader("X-Zuul-ServiceId", target); + ctx.addOriginResponseHeader("X-Zuul-ServiceId", location); } if (properties.isAddProxyHeaders()) { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RibbonCommand.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonCommand.java similarity index 98% rename from spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RibbonCommand.java rename to spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonCommand.java index b2107601..7bd5fd6c 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RibbonCommand.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonCommand.java @@ -1,4 +1,4 @@ -package org.springframework.cloud.netflix.zuul; +package org.springframework.cloud.netflix.zuul.filters.route; import com.netflix.client.http.HttpRequest; import com.netflix.client.http.HttpResponse; diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java index 0c3fb4c3..385efb2d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java @@ -19,7 +19,6 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.netflix.ribbon.RibbonClientPreprocessor; -import org.springframework.cloud.netflix.zuul.RibbonCommand; import org.springframework.cloud.netflix.zuul.SpringFilter; import org.springframework.util.StringUtils; diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocatorTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocatorTests.java new file mode 100644 index 00000000..7946ed20 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocatorTests.java @@ -0,0 +1,179 @@ +package org.springframework.cloud.netflix.zuul; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import java.util.Map; + +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.ProxyRouteSpec; +import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute; +import org.springframework.core.env.ConfigurableEnvironment; + +import com.google.common.collect.Lists; + +/** + * @author Spencer Gibb + * @author Dave Syer + */ +public class ProxyRouteLocatorTests { + + public static final String IGNOREDSERVICE = "ignoredservice"; + public static final String ASERVICE = "aservice"; + public static final String MYSERVICE = "myservice"; + + @Mock + ConfigurableEnvironment env; + + @Mock + DiscoveryClient discovery; + + private ZuulProperties properties = new ZuulProperties(); + + @Before + public void init() { + initMocks(this); + } + + @Test + public void testGetMatchingPath() throws Exception { + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); + routeLocator.getRoutes(); // force refresh + ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1"); + assertEquals("foo", route.getLocation()); + } + + @Test + public void testGetMatchingPathWithPrefix() throws Exception { + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); + this.properties.setPrefix("/proxy"); + routeLocator.getRoutes(); // force refresh + ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); + assertEquals("foo", route.getLocation()); + assertEquals("/proxy/foo/1", route.getPath()); + } + + @Test + public void testGetMatchingPathWithPrefixStripping() throws Exception { + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); + this.properties.setStripPrefix(true); + this.properties.setPrefix("/proxy"); + routeLocator.getRoutes(); // force refresh + ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); + assertEquals("foo", route.getLocation()); + assertEquals("/foo/1", route.getPath()); + } + + @Test + public void testGetMatchingPathWithRoutePrefixStripping() throws Exception { + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + ZuulRoute zuulRoute = new ZuulRoute("/foo/**"); + zuulRoute.setStripPrefix(true); + this.properties.getRoutes().put("foo", zuulRoute); + routeLocator.getRoutes(); // force refresh + ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1"); + assertEquals("foo", route.getLocation()); + assertEquals("/1", route.getPath()); + } + + @Test + public void testGetRoutes() { + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**")); + + Map routesMap = routeLocator.getRoutes(); + + assertNotNull("routesMap was null", routesMap); + assertFalse("routesMap was empty", routesMap.isEmpty()); + assertMapping(routesMap, ASERVICE); + } + + @Test + public void testGetRoutesWithMapping() { + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**", ASERVICE)); + this.properties.setPrefix("/foo"); + + Map routesMap = routeLocator.getRoutes(); + assertMapping(routesMap, ASERVICE, "foo/" + ASERVICE); + } + + @Test + public void testGetPhysicalRoutes() { + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**", "http://" + ASERVICE)); + + Map routesMap = routeLocator.getRoutes(); + + assertNotNull("routesMap was null", routesMap); + assertFalse("routesMap was empty", routesMap.isEmpty()); + assertMapping(routesMap, "http://" + ASERVICE, ASERVICE); + } + + @Test + public void testIgnoreRoutes() { + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + this.properties.setIgnoredServices(Lists.newArrayList(IGNOREDSERVICE)); + + when(discovery.getServices()).thenReturn( + Lists.newArrayList(IGNOREDSERVICE)); + + Map routesMap = routeLocator.getRoutes(); + String serviceId = routesMap.get(getMapping(IGNOREDSERVICE)); + assertNull("routes did not ignore " + IGNOREDSERVICE, serviceId); + } + + @Test + public void testAutoRoutes() { + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + + when(discovery.getServices()).thenReturn( + Lists.newArrayList(MYSERVICE)); + + Map routesMap = routeLocator.getRoutes(); + + assertNotNull("routesMap was null", routesMap); + assertFalse("routesMap was empty", routesMap.isEmpty()); + assertMapping(routesMap, MYSERVICE); + } + + @Test + public void testAutoRoutesCanBeOverridden() { + this.properties.getRoutes().put(MYSERVICE, new ZuulRoute("/"+MYSERVICE + "/**", "http://example.com/" + MYSERVICE)); + ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + + when(discovery.getServices()).thenReturn( + Lists.newArrayList(MYSERVICE)); + + Map routesMap = routeLocator.getRoutes(); + + assertNotNull("routesMap was null", routesMap); + assertFalse("routesMap was empty", routesMap.isEmpty()); + assertMapping(routesMap, "http://example.com/" + MYSERVICE, MYSERVICE); + } + + protected void assertMapping(Map routesMap, String serviceId) { + assertMapping(routesMap, serviceId, serviceId); + } + + protected void assertMapping(Map routesMap, String expectedRoute, String key) { + String mapping = getMapping(key); + String route = routesMap.get(mapping); + assertEquals("routesMap had wrong value for " + mapping, expectedRoute, + route); + } + + private String getMapping(String serviceId) { + return "/" + serviceId + "/**"; + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java index 6da894ed..b79e9404 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java @@ -28,13 +28,13 @@ public class SampleZuulProxyApplicationTests { private int port; @Autowired - private ZuulRouteLocator routes; + private ProxyRouteLocator routes; @Autowired private ZuulHandlerMapping mapping; @Test - public void bindRouteUsingPropertyEditor() { + public void bindRouteUsingPhysicalRoute() { assertEquals("http://localhost:7777/local", routes.getRoutes().get("/test/**")); } @@ -45,8 +45,8 @@ public class SampleZuulProxyApplicationTests { @Test public void deleteOnSelfViaSimpleHostRoutingFilter() { - routes.getRoutes().put("/self/**", "http://localhost:" + port + "/local"); - mapping.registerHandlers(routes.getRoutes().keySet()); + routes.addRoute("/self/**", "http://localhost:" + port + "/local"); + mapping.reset(); ResponseEntity result = new TestRestTemplate().exchange( "http://localhost:" + port + "/self/1", HttpMethod.DELETE, new HttpEntity((Void) null), String.class); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ZuulRouteLocatorTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ZuulRouteLocatorTests.java deleted file mode 100644 index 56eb3f9f..00000000 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ZuulRouteLocatorTests.java +++ /dev/null @@ -1,142 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; - -import java.util.Map; - -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.ZuulProperties.ZuulRoute; -import org.springframework.core.env.ConfigurableEnvironment; - -import com.google.common.collect.Lists; - -/** - * @author Spencer Gibb - * @author Dave Syer - */ -public class ZuulRouteLocatorTests { - - public static final String IGNOREDSERVICE = "ignoredservice"; - public static final String ASERVICE = "aservice"; - public static final String MYSERVICE = "myservice"; - - @Mock - ConfigurableEnvironment env; - - @Mock - DiscoveryClient discovery; - - @Before - public void init() { - initMocks(this); - } - - @Test - public void testGetRoutes() { - ZuulProperties properties = new ZuulProperties(); - ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties); - properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**")); - - Map routesMap = routeLocator.getRoutes(); - - assertNotNull("routesMap was null", routesMap); - assertFalse("routesMap was empty", routesMap.isEmpty()); - assertMapping(routesMap, ASERVICE); - } - - @Test - public void testGetRoutesWithMapping() { - ZuulProperties properties = new ZuulProperties(); - ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties); - properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**", ASERVICE)); - // Prefix doesn't have any impact on the routes (it's used in the filter) - properties.setPrefix("/foo"); - - Map routesMap = routeLocator.getRoutes(); - - assertNotNull("routesMap was null", routesMap); - assertFalse("routesMap was empty", routesMap.isEmpty()); - assertMapping(routesMap, ASERVICE); - } - - @Test - public void testGetPhysicalRoutes() { - ZuulProperties properties = new ZuulProperties(); - ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties); - properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**", "http://" + ASERVICE)); - - Map routesMap = routeLocator.getRoutes(); - - assertNotNull("routesMap was null", routesMap); - assertFalse("routesMap was empty", routesMap.isEmpty()); - assertMapping(routesMap, "http://" + ASERVICE, ASERVICE); - } - - @Test - public void testIgnoreRoutes() { - ZuulProperties properties = new ZuulProperties(); - ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties); - properties.setIgnoredServices(Lists.newArrayList(IGNOREDSERVICE)); - - when(discovery.getServices()).thenReturn( - Lists.newArrayList(IGNOREDSERVICE)); - - Map routesMap = routeLocator.getRoutes(); - String serviceId = routesMap.get(getMapping(IGNOREDSERVICE)); - assertNull("routes did not ignore " + IGNOREDSERVICE, serviceId); - } - - @Test - public void testAutoRoutes() { - ZuulProperties properties = new ZuulProperties(); - ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties); - - when(discovery.getServices()).thenReturn( - Lists.newArrayList(MYSERVICE)); - - Map routesMap = routeLocator.getRoutes(); - - assertNotNull("routesMap was null", routesMap); - assertFalse("routesMap was empty", routesMap.isEmpty()); - assertMapping(routesMap, MYSERVICE); - } - - @Test - public void testAutoRoutesCanBeOverridden() { - ZuulProperties properties = new ZuulProperties(); - properties.getRoutes().put(MYSERVICE, new ZuulRoute("/"+MYSERVICE + "/**", "http://example.com/" + MYSERVICE)); - ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties); - - when(discovery.getServices()).thenReturn( - Lists.newArrayList(MYSERVICE)); - - Map routesMap = routeLocator.getRoutes(); - - assertNotNull("routesMap was null", routesMap); - assertFalse("routesMap was empty", routesMap.isEmpty()); - assertMapping(routesMap, "http://example.com/" + MYSERVICE, MYSERVICE); - } - - protected void assertMapping(Map routesMap, String serviceId) { - assertMapping(routesMap, serviceId, serviceId); - } - - protected void assertMapping(Map routesMap, String expectedRoute, String key) { - String mapping = getMapping(key); - String route = routesMap.get(mapping); - assertEquals("routesMap had wrong value for " + mapping, expectedRoute, - route); - } - - private String getMapping(String serviceId) { - return "/" + serviceId + "/**"; - } -}