Skip to content

Commit

Permalink
Add CompositeRouteLocator (#1448)
Browse files Browse the repository at this point in the history
Configure a CompositeRouteLocator by default so it is easier to compose
multiple RouteLocators.
  • Loading branch information
joshiste authored and spencergibb committed Jan 12, 2017
1 parent bf586ba commit 899d8bc
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 15 deletions.
Expand Up @@ -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;
Expand All @@ -30,6 +31,7 @@
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;
Expand All @@ -47,6 +49,7 @@
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;
Expand Down Expand Up @@ -78,8 +81,15 @@ public HasFeatures zuulFeature() {
}

@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);
}
Expand Down
Expand Up @@ -76,9 +76,8 @@ public HasFeatures zuulFeature() {
}

@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);
}
Expand Down
@@ -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();
}
}
}
}
Expand Up @@ -26,6 +26,7 @@

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;
Expand All @@ -38,7 +39,8 @@
* @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;

Expand All @@ -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;
Expand Down Expand Up @@ -200,4 +203,13 @@ else if (RequestUtils.isZuulServletRequest()) {
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;
}

}
}
Expand Up @@ -32,8 +32,6 @@

import javax.servlet.http.HttpServletRequest;

import lombok.SneakyThrows;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -49,7 +47,6 @@
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;
Expand Down Expand Up @@ -85,6 +82,8 @@
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",
Expand All @@ -96,7 +95,7 @@
public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {

@Autowired
RouteLocator routeLocator;
DiscoveryClientRouteLocator routeLocator;

@Override
protected boolean supportsPatch() {
Expand Down Expand Up @@ -252,17 +251,17 @@ public void ribbonCommandFactoryOverridden() {
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(
Expand Down Expand Up @@ -306,7 +305,7 @@ public String contentType(HttpServletRequest request) {
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;
}
Expand Down Expand Up @@ -337,7 +336,8 @@ public RibbonCommandFactory<?> ribbonCommandFactory(
}

@Bean
public RouteLocator routeLocator(DiscoveryClient discoveryClient,
public DiscoveryClientRouteLocator discoveryRouteLocator(
DiscoveryClient discoveryClient,
ZuulProperties zuulProperties) {
return new MyRouteLocator("/", discoveryClient, zuulProperties);
}
Expand Down

0 comments on commit 899d8bc

Please sign in to comment.