Add CompositeRouteLocator (#1448)
Configure a CompositeRouteLocator by default so it is easier to compose multiple RouteLocators.
This commit is contained in:
committed by
Spencer Gibb
parent
bf586ba2cb
commit
899d8bc24d
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -30,6 +31,7 @@ import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
||||
import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;
|
||||
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
|
||||
import org.springframework.cloud.netflix.zuul.filters.CompositeRouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
@@ -47,6 +49,7 @@ import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
@@ -78,8 +81,15 @@ public class ZuulConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RouteLocator.class)
|
||||
public RouteLocator routeLocator() {
|
||||
@Primary
|
||||
public CompositeRouteLocator primaryRouteLocator(
|
||||
Collection<RouteLocator> routeLocators) {
|
||||
return new CompositeRouteLocator(routeLocators);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(SimpleRouteLocator.class)
|
||||
public SimpleRouteLocator simpleRouteLocator() {
|
||||
return new SimpleRouteLocator(this.server.getServletPrefix(),
|
||||
this.zuulProperties);
|
||||
}
|
||||
|
||||
@@ -76,9 +76,8 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
@ConditionalOnMissingBean(RouteLocator.class)
|
||||
public DiscoveryClientRouteLocator routeLocator() {
|
||||
@ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
|
||||
public DiscoveryClientRouteLocator discoveryRouteLocator() {
|
||||
return new DiscoveryClientRouteLocator(this.server.getServletPrefix(), this.discovery, this.zuulProperties,
|
||||
this.serviceRouteMapper);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* RouteLocator that composes multiple RouteLocators.
|
||||
*
|
||||
* @author Johannes Edmeier
|
||||
*
|
||||
*/
|
||||
public class CompositeRouteLocator implements RefreshableRouteLocator {
|
||||
private final Collection<? extends RouteLocator> routeLocators;
|
||||
private ArrayList<RouteLocator> rl;
|
||||
|
||||
public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {
|
||||
Assert.notNull(routeLocators, "'routeLocators' must not be null");
|
||||
rl = new ArrayList<>(routeLocators);
|
||||
AnnotationAwareOrderComparator.sort(rl);
|
||||
this.routeLocators = rl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getIgnoredPaths() {
|
||||
List<String> ignoredPaths = new ArrayList<>();
|
||||
for (RouteLocator locator : routeLocators) {
|
||||
ignoredPaths.addAll(locator.getIgnoredPaths());
|
||||
}
|
||||
return ignoredPaths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Route> getRoutes() {
|
||||
List<Route> route = new ArrayList<>();
|
||||
for (RouteLocator locator : routeLocators) {
|
||||
route.addAll(locator.getRoutes());
|
||||
}
|
||||
return route;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Route getMatchingRoute(String path) {
|
||||
for (RouteLocator locator : routeLocators) {
|
||||
Route route = locator.getMatchingRoute(path);
|
||||
if (route != null) {
|
||||
return route;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
for (RouteLocator locator : routeLocators) {
|
||||
if (locator instanceof RefreshableRouteLocator) {
|
||||
((RefreshableRouteLocator) locator).refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
|
||||
import org.springframework.cloud.netflix.zuul.util.RequestUtils;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -38,7 +39,8 @@ import lombok.extern.apachecommons.CommonsLog;
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@CommonsLog
|
||||
public class SimpleRouteLocator implements RouteLocator {
|
||||
public class SimpleRouteLocator implements RouteLocator, Ordered {
|
||||
private static final int DEFAULT_ORDER = 0;
|
||||
|
||||
private ZuulProperties properties;
|
||||
|
||||
@@ -48,6 +50,7 @@ public class SimpleRouteLocator implements RouteLocator {
|
||||
private String zuulServletPath;
|
||||
|
||||
private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();
|
||||
private int order = DEFAULT_ORDER;
|
||||
|
||||
public SimpleRouteLocator(String servletPath, ZuulProperties properties) {
|
||||
this.properties = properties;
|
||||
@@ -200,4 +203,13 @@ public class SimpleRouteLocator implements RouteLocator {
|
||||
return adjustedPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.springframework.cloud.netflix.zuul.filters;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.CoreMatchers.hasItems;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Johannes Edmeier
|
||||
*/
|
||||
public class CompositeRouteLocatorTests {
|
||||
private CompositeRouteLocator locator;
|
||||
|
||||
public CompositeRouteLocatorTests() {
|
||||
List<RouteLocator> locators = new ArrayList<>();
|
||||
locators.add(new TestRouteLocator(asList("ign1"),
|
||||
asList(createRoute("1", "/pathA"))));
|
||||
locators.add(
|
||||
new TestRouteLocator(asList("ign1", "ign2"),
|
||||
asList(createRoute("2", "/pathA"), createRoute("2", "/pathB"))));
|
||||
this.locator = new CompositeRouteLocator(locators);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getIgnoredPaths() {
|
||||
assertThat(locator.getIgnoredPaths(), hasItems("ign1", "ign2"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getRoutes() {
|
||||
assertThat(locator.getRoutes(),
|
||||
hasItems(createRoute("1", "/pathA"), createRoute("2", "/pathB")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getMatchingRoute() {
|
||||
assertThat(locator.getMatchingRoute("/pathA"), notNullValue());
|
||||
assertThat(locator.getMatchingRoute("/pathA").getId(), is("1"));
|
||||
assertThat("Locator 1 should take precedence", locator.getMatchingRoute("/pathB").getId(),
|
||||
is("2"));
|
||||
assertThat(locator.getMatchingRoute("/pathNot"), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_refresh() {
|
||||
RefreshableRouteLocator mock = mock(RefreshableRouteLocator.class);
|
||||
new CompositeRouteLocator(asList(mock)).refresh();
|
||||
verify(mock).refresh();
|
||||
}
|
||||
|
||||
private Route createRoute(String id, String path) {
|
||||
return new Route(id, path, null, null, false, Collections.<String>emptySet());
|
||||
}
|
||||
|
||||
private static class TestRouteLocator implements RouteLocator {
|
||||
private Collection<String> ignoredPaths;
|
||||
private List<Route> routes;
|
||||
|
||||
public TestRouteLocator(Collection<String> ignoredPaths, List<Route> routes) {
|
||||
this.ignoredPaths = ignoredPaths;
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getIgnoredPaths() {
|
||||
return this.ignoredPaths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Route> getRoutes() {
|
||||
return this.routes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Route getMatchingRoute(String path) {
|
||||
for (Route route : routes) {
|
||||
if (path.startsWith(route.getPath())) {
|
||||
return route;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,6 @@ import java.util.UUID;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -49,7 +47,6 @@ import org.springframework.cloud.netflix.ribbon.RibbonClients;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.StaticServerList;
|
||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommand;
|
||||
@@ -85,6 +82,8 @@ import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = RestClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
|
||||
"zuul.routes.other: /test/**=http://localhost:7777/local",
|
||||
@@ -96,7 +95,7 @@ import com.netflix.niws.client.http.RestClient;
|
||||
public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
|
||||
@Autowired
|
||||
RouteLocator routeLocator;
|
||||
DiscoveryClientRouteLocator routeLocator;
|
||||
|
||||
@Override
|
||||
protected boolean supportsPatch() {
|
||||
@@ -252,17 +251,17 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
assertTrue("ribbonCommandFactory not a MyRibbonCommandFactory",
|
||||
this.ribbonCommandFactory instanceof TestConfig.MyRibbonCommandFactory);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void javascriptEncodedFormParams() {
|
||||
TestRestTemplate testRestTemplate = new TestRestTemplate();
|
||||
ArrayList<HttpMessageConverter<?>> converters = new ArrayList<>();
|
||||
converters.addAll(Arrays.asList(new StringHttpMessageConverter(),
|
||||
converters.addAll(Arrays.asList(new StringHttpMessageConverter(),
|
||||
new NoEncodingFormHttpMessageConverter()));
|
||||
testRestTemplate.getRestTemplate().setMessageConverters(converters);
|
||||
|
||||
|
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
|
||||
map.add("foo", "(bar)");
|
||||
ResponseEntity<String> result = testRestTemplate.postForEntity(
|
||||
@@ -306,7 +305,7 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
public ResponseEntity<String> addHeader(HttpServletRequest request) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("X-Header", "FOO");
|
||||
ResponseEntity<String> result = new ResponseEntity<String>(
|
||||
ResponseEntity<String> result = new ResponseEntity<>(
|
||||
request.getRequestURI(), headers, HttpStatus.OK);
|
||||
return result;
|
||||
}
|
||||
@@ -337,7 +336,8 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouteLocator routeLocator(DiscoveryClient discoveryClient,
|
||||
public DiscoveryClientRouteLocator discoveryRouteLocator(
|
||||
DiscoveryClient discoveryClient,
|
||||
ZuulProperties zuulProperties) {
|
||||
return new MyRouteLocator("/", discoveryClient, zuulProperties);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user