Commit 34f277b8 by Dave Syer

Add ZuulRoute as values object in zuul.routes

User can now specify zuul.routes.*.{path,url,serviceId} (with "url" and "serviceId" mutually exclusive (and "location" is a synonym) separately, or can use a one-one short form, like the old style. See gh-77
parent 7e91b9ea
...@@ -500,15 +500,60 @@ Zuul's rule engine allows rules and filters to be written in essentially any JVM ...@@ -500,15 +500,60 @@ Zuul's rule engine allows rules and filters to be written in essentially any JVM
[[netflix-zuul-reverse-proxy]] [[netflix-zuul-reverse-proxy]]
=== Embedded Zuul Reverse Proxy === Embedded Zuul Reverse Proxy
Spring Cloud has created an embedded Zuul proxy to ease the development of a very common use case where a UI application wants to proxy calls to one or more back end services. To enable it, annotate a Spring Boot main class with `@EnableZuulProxy`. This forwards local calls to the appropriate service. By convention, a service with the `spring.application.name` of `users`, will receive requests from the proxy located at `/users`. The proxy uses Ribbon to locate an instance to forward to via Eureka. To skip having a service automatically added from eureka, set `zuul.ignored-services = service1`. Forwarding to the service is protected by a Hystrix circuit breaker. Additional rules can be configured via the Spring environment. The Config Server is an ideal place for the Zuul configuration. Zuul Embedded Proxy configuration rules look like the following: Spring Cloud has created an embedded Zuul proxy to ease the development of a very common use case where a UI application wants to proxy calls to one or more back end services. To enable it, annotate a Spring Boot main class with `@EnableZuulProxy`, and this forwards local calls to the appropriate service. By convention, a service with the Eureka ID "users", will receive requests from the proxy located at `/users`. The proxy uses Ribbon to locate an instance to forward to via Eureka. To skip having a service automatically added from eureka, set `zuul.ignored-services = service1`.
zuul.route.users: /myusers/** To augment or change the proxy routes, you can add external
configuration like the following:
This means that http calls to /myusers get forwarded to the users service. This proxy configuration is useful for services that host a user interface to proxy to the backend services it requires. To add a prefix to the mapping, set `zuul.proxy.mapping` to a value, such as `/api`. To strip the proxy mapping from the request before forwarding set `zuul.proxy.strip-mapping = true`. .application.yml
[source,yaml]
----
zuul:
routes:
users: /myusers/**
----
This means that http calls to "/myusers" get forwarded to the "users"
service. This configuration is useful for a user interface to proxy to
the backend services it requires (avoiding the need to manage CORS and
authentication concerns independently for all the backends).
To get more fine-grained control over a route you can specify the path
and the serviceId independently:
.application.yml
[source,yaml]
----
zuul:
routes:
users:
path: /myusers/**
serviceId: users_service
----
This means that http calls to "/myusers" get forwarded to the
"users_service" service. The route has to have a "path" which can be
specified as an ant-style pattern, so "/myusers/*" only matches one
level, but "/myusers/**" matches hierarchically.
The location of the backend can be specified as either a "serviceId"
(for a Eureka service) or a "url" (for a physical location), e.g.
.application.yml
[source,yaml]
----
zuul:
routes:
users:
path: /myusers/**
url: http://example.com/users_service
----
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.
The Zuul proxy supports Ant-style patterns. 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`.
The `X-Forwarded-Host` header added to the forwarded requests by default. To turn it off set `zuul.proxy.add-proxy-headers = false`. The `X-Forwarded-Host` header added to the forwarded requests by default. To turn it off set `zuul.addProxyHeaders = false`.
An application with the `@EnableZuulProxy` could act as a standalone server if you set a default route, for example `zuul.route.home: /` would route `/` to the home service. An application with the `@EnableZuulProxy` could act as a standalone server if you set a default route ("/"), for example `zuul.route.home: /` would route all traffic (i.e. "/**") to the "home" service.
...@@ -40,8 +40,8 @@ public class ZuulConfiguration { ...@@ -40,8 +40,8 @@ public class ZuulConfiguration {
private ZuulProperties zuulProperties; private ZuulProperties zuulProperties;
@Bean @Bean
public RouteLocator routes() { public ZuulRouteLocator routes() {
return new RouteLocator(discovery, zuulProperties); return new ZuulRouteLocator(discovery, zuulProperties);
} }
@Bean @Bean
......
package org.springframework.cloud.netflix.zuul; package org.springframework.cloud.netflix.zuul;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -26,14 +27,14 @@ import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; ...@@ -26,14 +27,14 @@ import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements
ApplicationListener<InstanceRegisteredEvent>, MvcEndpoint { ApplicationListener<InstanceRegisteredEvent>, MvcEndpoint {
private RouteLocator routeLocator; private ZuulRouteLocator routeLocator;
private ZuulController zuul; private ZuulController zuul;
private ZuulProperties properties; private ZuulProperties properties;
@Autowired @Autowired
public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul, public ZuulHandlerMapping(ZuulRouteLocator routeLocator, ZuulController zuul,
ZuulProperties properties) { ZuulProperties properties) {
this.routeLocator = routeLocator; this.routeLocator = routeLocator;
this.zuul = zuul; this.zuul = zuul;
...@@ -43,16 +44,15 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements ...@@ -43,16 +44,15 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements
@Override @Override
public void onApplicationEvent(InstanceRegisteredEvent event) { public void onApplicationEvent(InstanceRegisteredEvent event) {
registerHandlers(routeLocator.getRoutes()); registerHandlers(routeLocator.getRoutes().keySet());
} }
protected void registerHandlers(Map<String, String> routes) { protected void registerHandlers(Collection<String> routes) {
if (routes.isEmpty()) { if (routes.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping"); logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
} }
else { else {
for (Map.Entry<String, String> entry : routes.entrySet()) { for (String url : routes) {
String url = entry.getKey();
// Prepend with slash if not already present. // Prepend with slash if not already present.
if (!url.startsWith("/")) { if (!url.startsWith("/")) {
url = "/" + url; url = "/" + url;
...@@ -74,9 +74,9 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements ...@@ -74,9 +74,9 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements
@ResponseBody @ResponseBody
@ManagedOperation @ManagedOperation
public Map<String, String> reset() { public Map<String, String> reset() {
Map<String, String> routes = routeLocator.resetRoutes(); routeLocator.resetRoutes();
registerHandlers(routes); registerHandlers(routeLocator.getRoutes().keySet());
return routes; return getRoutes();
} }
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
......
package org.springframework.cloud.netflix.zuul; package org.springframework.cloud.netflix.zuul;
import java.util.Collections; import java.util.ArrayList;
import java.util.HashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Dave Syer
*/ */
@Data @Data
@ConfigurationProperties("zuul") @ConfigurationProperties("zuul")
public class ZuulProperties { public class ZuulProperties {
private String prefix = ""; private String prefix = "";
private boolean stripPrefix = false; private boolean stripPrefix = false;
private Map<String,String> routes = new HashMap<String, String>(); private Map<String, ZuulRoute> routes = new LinkedHashMap<String, ZuulRoute>();
private boolean addProxyHeaders = true; private boolean addProxyHeaders = true;
private List<String> ignoredServices = Collections.emptyList(); private List<String> ignoredServices = new ArrayList<String>();
public Map<String, ZuulRoute> getRoutesWithDefaultServiceIds() {
for (Entry<String, ZuulRoute> entry : this.routes.entrySet()) {
ZuulRoute value = entry.getValue();
if (!StringUtils.hasText(value.getLocation())) {
value.serviceId = entry.getKey();
}
}
return this.routes;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ZuulRoute {
private String path;
private String serviceId;
private String url;
private boolean stripPath = false;
public ZuulRoute(String text) {
String location = null;
String path = text;
if (text.contains("=")) {
String[] values = StringUtils.trimArrayElements(StringUtils.split(text,
"="));
location = values[1];
path = values[0];
}
if (!path.startsWith("/")) {
path = "/" + path;
}
setLocation(location);
this.path = path;
}
public ZuulRoute(String path, String location) {
this.path = path;
setLocation(location);
}
public String getLocation() {
if (StringUtils.hasText(url)) {
return url;
}
return serviceId;
}
public void setLocation(String location) {
if (location != null
&& (location.startsWith("http:") || location.startsWith("https:"))) {
url = location;
}
else {
serviceId = location;
}
}
}
} }
package org.springframework.cloud.netflix.zuul; package org.springframework.cloud.netflix.zuul;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -10,6 +11,7 @@ import lombok.extern.slf4j.Slf4j; ...@@ -10,6 +11,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.CompositePropertySource;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
...@@ -18,7 +20,7 @@ import org.springframework.util.ReflectionUtils; ...@@ -18,7 +20,7 @@ import org.springframework.util.ReflectionUtils;
* @author Spencer Gibb * @author Spencer Gibb
*/ */
@Slf4j @Slf4j
public class RouteLocator implements ApplicationListener<EnvironmentChangeEvent> { public class ZuulRouteLocator implements ApplicationListener<EnvironmentChangeEvent> {
public static final String DEFAULT_ROUTE = "/"; public static final String DEFAULT_ROUTE = "/";
...@@ -29,7 +31,7 @@ public class RouteLocator implements ApplicationListener<EnvironmentChangeEvent> ...@@ -29,7 +31,7 @@ public class RouteLocator implements ApplicationListener<EnvironmentChangeEvent>
private Field propertySourcesField; private Field propertySourcesField;
private AtomicReference<LinkedHashMap<String, String>> routes = new AtomicReference<>(); private AtomicReference<LinkedHashMap<String, String>> routes = new AtomicReference<>();
public RouteLocator(DiscoveryClient discovery, ZuulProperties properties) { public ZuulRouteLocator(DiscoveryClient discovery, ZuulProperties properties) {
this.discovery = discovery; this.discovery = discovery;
this.properties = properties; this.properties = properties;
initField(); initField();
...@@ -51,35 +53,40 @@ public class RouteLocator implements ApplicationListener<EnvironmentChangeEvent> ...@@ -51,35 +53,40 @@ public class RouteLocator implements ApplicationListener<EnvironmentChangeEvent>
} }
} }
public Collection<String> getRoutePaths() {
return getRoutes().keySet();
}
public Map<String, String> getRoutes() { public Map<String, String> getRoutes() {
if (routes.get() == null) { if (routes.get() == null) {
return resetRoutes(); resetRoutes();
} }
return routes.get(); return routes.get();
} }
//access so ZuulHandlerMapping actuator can reset it's mappings //access so ZuulHandlerMapping actuator can reset it's mappings
/*package*/ Map<String, String> resetRoutes() { /*package*/ void resetRoutes() {
LinkedHashMap<String, String> newValue = locateRoutes(); LinkedHashMap<String, String> newValue = locateRoutes();
routes.set(newValue); routes.set(newValue);
return newValue;
} }
protected LinkedHashMap<String, String> locateRoutes() { protected LinkedHashMap<String, String> locateRoutes() {
LinkedHashMap<String, String> routesMap = new LinkedHashMap<>(); LinkedHashMap<String, String> routesMap = new LinkedHashMap<>();
addConfiguredRoutes(routesMap);
String defaultServiceId = routesMap.get(DEFAULT_ROUTE);
// Add routes for discovery services by default // Add routes for discovery services by default
List<String> services = discovery.getServices(); List<String> services = discovery.getServices();
for (String serviceId : services) { for (String serviceId : services) {
// Ignore specified services // Ignore specifically ignored services and those that were manually configured
if (!properties.getIgnoredServices().contains(serviceId)) String key = "/" + serviceId + "/**";
routesMap.put("/" + serviceId + "/**", serviceId); if (!properties.getIgnoredServices().contains(serviceId) && !routesMap.containsKey(key)) {
routesMap.put(key, serviceId);
}
} }
addConfiguredRoutes(routesMap);
String defaultServiceId = routesMap.get(DEFAULT_ROUTE);
if (defaultServiceId != null) { if (defaultServiceId != null) {
// move the defaultServiceId to the end // move the defaultServiceId to the end
...@@ -90,16 +97,17 @@ public class RouteLocator implements ApplicationListener<EnvironmentChangeEvent> ...@@ -90,16 +97,17 @@ public class RouteLocator implements ApplicationListener<EnvironmentChangeEvent>
} }
protected void addConfiguredRoutes(Map<String, String> routes) { protected void addConfiguredRoutes(Map<String, String> routes) {
Map<String, String> routeEntries = properties.getRoutes(); Map<String, ZuulRoute> routeEntries = properties.getRoutesWithDefaultServiceIds();
for (Map.Entry<String, String> entry : routeEntries.entrySet()) { for (ZuulRoute entry : routeEntries.values()) {
String serviceId = entry.getKey(); String location = entry.getLocation();
String route = entry.getValue() ; String route = entry.getPath();
if (routes.containsKey(route)) { if (routes.containsKey(route)) {
log.warn("Overwriting route {}: already defined by {}", route, log.warn("Overwriting route {}: already defined by {}", route,
routes.get(route)); routes.get(route));
} }
routes.put(route, serviceId); routes.put(route, location);
} }
} }
} }
...@@ -11,7 +11,7 @@ import javax.servlet.http.HttpServletResponse; ...@@ -11,7 +11,7 @@ import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.RouteLocator; import org.springframework.cloud.netflix.zuul.ZuulRouteLocator;
import org.springframework.cloud.netflix.zuul.ZuulProperties; import org.springframework.cloud.netflix.zuul.ZuulProperties;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
...@@ -25,13 +25,13 @@ import com.netflix.zuul.context.RequestContext; ...@@ -25,13 +25,13 @@ import com.netflix.zuul.context.RequestContext;
public class PreDecorationFilter extends ZuulFilter { public class PreDecorationFilter extends ZuulFilter {
private static Logger LOG = LoggerFactory.getLogger(PreDecorationFilter.class); private static Logger LOG = LoggerFactory.getLogger(PreDecorationFilter.class);
private RouteLocator routeLocator; private ZuulRouteLocator routeLocator;
private ZuulProperties properties; private ZuulProperties properties;
private PathMatcher pathMatcher = new AntPathMatcher(); private PathMatcher pathMatcher = new AntPathMatcher();
public PreDecorationFilter(RouteLocator routeLocator, ZuulProperties properties) { public PreDecorationFilter(ZuulRouteLocator routeLocator, ZuulProperties properties) {
this.routeLocator = routeLocator; this.routeLocator = routeLocator;
this.properties = properties; this.properties = properties;
} }
......
...@@ -19,24 +19,37 @@ import org.springframework.test.context.web.WebAppConfiguration; ...@@ -19,24 +19,37 @@ import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleZuulProxyApplication.class) @SpringApplicationConfiguration(classes = SampleZuulProxyApplication.class)
@WebAppConfiguration @WebAppConfiguration
@IntegrationTest("server.port=0") @IntegrationTest({ "server.port: 0",
"zuul.routes.other: /test/**=http://localhost:7777/local",
"zuul.routes.simple: /simple/**" })
public class SampleZuulProxyApplicationTests { public class SampleZuulProxyApplicationTests {
@Value("${local.server.port}") @Value("${local.server.port}")
private int port; private int port;
@Autowired @Autowired
private RouteLocator routes; private ZuulRouteLocator routes;
@Autowired @Autowired
private ZuulHandlerMapping mapping; private ZuulHandlerMapping mapping;
@Test @Test
public void bindRouteUsingPropertyEditor() {
assertEquals("http://localhost:7777/local", routes.getRoutes().get("/test/**"));
}
@Test
public void bindRouteUsingOnlyPath() {
assertEquals("simple", routes.getRoutes().get("/simple/**"));
}
@Test
public void deleteOnSelfViaSimpleHostRoutingFilter() { public void deleteOnSelfViaSimpleHostRoutingFilter() {
routes.getRoutes().put("/self/**", "http://localhost:" + port + "/local"); routes.getRoutes().put("/self/**", "http://localhost:" + port + "/local");
mapping.registerHandlers(routes.getRoutes()); mapping.registerHandlers(routes.getRoutes().keySet());
ResponseEntity<String> result = new TestRestTemplate().exchange("http://localhost:" + port + "/self/1", ResponseEntity<String> result = new TestRestTemplate().exchange(
HttpMethod.DELETE, new HttpEntity<Void>((Void) null), String.class); "http://localhost:" + port + "/self/1", HttpMethod.DELETE,
new HttpEntity<Void>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("Deleted!", result.getBody()); assertEquals("Deleted!", result.getBody());
} }
......
...@@ -13,14 +13,16 @@ import org.junit.Before; ...@@ -13,14 +13,16 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Dave Syer
*/ */
public class RouteLocatorTests { public class ZuulRouteLocatorTests {
public static final String IGNOREDSERVICE = "ignoredservice"; public static final String IGNOREDSERVICE = "ignoredservice";
public static final String ASERVICE = "aservice"; public static final String ASERVICE = "aservice";
...@@ -40,8 +42,8 @@ public class RouteLocatorTests { ...@@ -40,8 +42,8 @@ public class RouteLocatorTests {
@Test @Test
public void testGetRoutes() { public void testGetRoutes() {
ZuulProperties properties = new ZuulProperties(); ZuulProperties properties = new ZuulProperties();
RouteLocator routeLocator = new RouteLocator(this.discovery, properties); ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties);
properties.getRoutes().put(ASERVICE, "/"+ASERVICE + "/**"); properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**"));
Map<String, String> routesMap = routeLocator.getRoutes(); Map<String, String> routesMap = routeLocator.getRoutes();
...@@ -53,8 +55,8 @@ public class RouteLocatorTests { ...@@ -53,8 +55,8 @@ public class RouteLocatorTests {
@Test @Test
public void testGetRoutesWithMapping() { public void testGetRoutesWithMapping() {
ZuulProperties properties = new ZuulProperties(); ZuulProperties properties = new ZuulProperties();
RouteLocator routeLocator = new RouteLocator(this.discovery, properties); ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties);
properties.getRoutes().put(ASERVICE, "/"+ASERVICE + "/**"); properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**", ASERVICE));
// Prefix doesn't have any impact on the routes (it's used in the filter) // Prefix doesn't have any impact on the routes (it's used in the filter)
properties.setPrefix("/foo"); properties.setPrefix("/foo");
...@@ -68,8 +70,8 @@ public class RouteLocatorTests { ...@@ -68,8 +70,8 @@ public class RouteLocatorTests {
@Test @Test
public void testGetPhysicalRoutes() { public void testGetPhysicalRoutes() {
ZuulProperties properties = new ZuulProperties(); ZuulProperties properties = new ZuulProperties();
RouteLocator routeLocator = new RouteLocator(this.discovery, properties); ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties);
properties.getRoutes().put("http://" + ASERVICE, "/"+ASERVICE + "/**"); properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**", "http://" + ASERVICE));
Map<String, String> routesMap = routeLocator.getRoutes(); Map<String, String> routesMap = routeLocator.getRoutes();
...@@ -81,7 +83,7 @@ public class RouteLocatorTests { ...@@ -81,7 +83,7 @@ public class RouteLocatorTests {
@Test @Test
public void testIgnoreRoutes() { public void testIgnoreRoutes() {
ZuulProperties properties = new ZuulProperties(); ZuulProperties properties = new ZuulProperties();
RouteLocator routeLocator = new RouteLocator(this.discovery, properties); ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties);
properties.setIgnoredServices(Lists.newArrayList(IGNOREDSERVICE)); properties.setIgnoredServices(Lists.newArrayList(IGNOREDSERVICE));
when(discovery.getServices()).thenReturn( when(discovery.getServices()).thenReturn(
...@@ -95,7 +97,7 @@ public class RouteLocatorTests { ...@@ -95,7 +97,7 @@ public class RouteLocatorTests {
@Test @Test
public void testAutoRoutes() { public void testAutoRoutes() {
ZuulProperties properties = new ZuulProperties(); ZuulProperties properties = new ZuulProperties();
RouteLocator routeLocator = new RouteLocator(this.discovery, properties); ZuulRouteLocator routeLocator = new ZuulRouteLocator(this.discovery, properties);
when(discovery.getServices()).thenReturn( when(discovery.getServices()).thenReturn(
Lists.newArrayList(MYSERVICE)); Lists.newArrayList(MYSERVICE));
...@@ -107,6 +109,22 @@ public class RouteLocatorTests { ...@@ -107,6 +109,22 @@ public class RouteLocatorTests {
assertMapping(routesMap, MYSERVICE); 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<String, String> 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<String, String> routesMap, String serviceId) { protected void assertMapping(Map<String, String> routesMap, String serviceId) {
assertMapping(routesMap, serviceId, serviceId); assertMapping(routesMap, serviceId, serviceId);
} }
......
...@@ -20,5 +20,9 @@ zuul: ...@@ -20,5 +20,9 @@ zuul:
#prefix: /api #prefix: /api
#strip-prefix: true #strip-prefix: true
routes: routes:
testclient: /testing123/** test:
http://localhost:8081: /stores/** serviceId: testclient
path: /testing123/**
stores:
url: http://localhost:8081
path: /stores/**
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment