Commit 9161c54c by Dave Syer

Factor out common interface from ProxyRouteLocator

The RouteLocator interface wasn't very useful to anyone in its 1.0.x form. If ProxyRouteLocator features are consolidated into the RouteLocator interface then users can supply their own strategies more easily. Also added RefreshableRouteLocator for implementations that need to recompute routes when something changes. SimpleRouteLocator doesn't need to do that because all the routes are in the configuration.
parent b4cf3750
/*
* Copyright 2013-2015 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;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
/**
* Interface for a route locator that can be refreshed if routes change.
*
* @author Dave Syer
*/
public interface RefreshableRouteLocator extends RouteLocator {
void refresh();
}
...@@ -21,7 +21,7 @@ import java.util.Map; ...@@ -21,7 +21,7 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator; import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedAttribute;
...@@ -40,7 +40,7 @@ import org.springframework.web.bind.annotation.ResponseBody; ...@@ -40,7 +40,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@ManagedResource(description = "Can be used to list and reset the reverse proxy routes") @ManagedResource(description = "Can be used to list and reset the reverse proxy routes")
public class RoutesEndpoint implements MvcEndpoint, ApplicationEventPublisherAware { public class RoutesEndpoint implements MvcEndpoint, ApplicationEventPublisherAware {
private ProxyRouteLocator routes; private RouteLocator routes;
private ApplicationEventPublisher publisher; private ApplicationEventPublisher publisher;
...@@ -50,7 +50,7 @@ public class RoutesEndpoint implements MvcEndpoint, ApplicationEventPublisherAwa ...@@ -50,7 +50,7 @@ public class RoutesEndpoint implements MvcEndpoint, ApplicationEventPublisherAwa
} }
@Autowired @Autowired
public RoutesEndpoint(ProxyRouteLocator routes) { public RoutesEndpoint(RouteLocator routes) {
this.routes = routes; this.routes = routes;
} }
......
...@@ -20,6 +20,7 @@ import java.util.Map; ...@@ -20,6 +20,7 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ErrorController; import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
...@@ -66,6 +67,7 @@ public class ZuulConfiguration { ...@@ -66,6 +67,7 @@ public class ZuulConfiguration {
} }
@Bean @Bean
@ConditionalOnMissingBean
public RouteLocator routeLocator() { public RouteLocator routeLocator() {
return new SimpleRouteLocator(this.zuulProperties); return new SimpleRouteLocator(this.zuulProperties);
} }
...@@ -140,8 +142,8 @@ public class ZuulConfiguration { ...@@ -140,8 +142,8 @@ public class ZuulConfiguration {
} }
private static class ZuulRefreshListener implements private static class ZuulRefreshListener
ApplicationListener<ApplicationEvent> { implements ApplicationListener<ApplicationEvent> {
@Autowired @Autowired
private ZuulHandlerMapping zuulHandlerMapping; private ZuulHandlerMapping zuulHandlerMapping;
...@@ -149,8 +151,9 @@ public class ZuulConfiguration { ...@@ -149,8 +151,9 @@ public class ZuulConfiguration {
@Override @Override
public void onApplicationEvent(ApplicationEvent event) { public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent if (event instanceof ContextRefreshedEvent
|| event instanceof RefreshScopeRefreshedEvent) { || event instanceof RefreshScopeRefreshedEvent
this.zuulHandlerMapping.registerHandlers(); || event instanceof RoutesRefreshedEvent) {
this.zuulHandlerMapping.setDirty(true);
} }
} }
......
...@@ -29,10 +29,10 @@ import org.springframework.cloud.client.discovery.event.HeartbeatEvent; ...@@ -29,10 +29,10 @@ import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.discovery.event.HeartbeatMonitor; import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent; import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator; import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ServiceRouteMapper; import org.springframework.cloud.netflix.zuul.filters.ServiceRouteMapper;
import org.springframework.cloud.netflix.zuul.filters.SimpleServiceRouteMapper; import org.springframework.cloud.netflix.zuul.filters.SimpleServiceRouteMapper;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
...@@ -80,32 +80,35 @@ public class ZuulProxyConfiguration extends ZuulConfiguration { ...@@ -80,32 +80,35 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
@Bean @Bean
@Override @Override
@ConditionalOnMissingBean(RouteLocator.class)
public ProxyRouteLocator routeLocator() { public ProxyRouteLocator routeLocator() {
return new ProxyRouteLocator(this.server.getServletPrefix(), this.discovery, return new ProxyRouteLocator(this.server.getServletPrefix(), this.discovery,
this.zuulProperties, serviceRouteMapper); this.zuulProperties, this.serviceRouteMapper);
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public RibbonCommandFactory ribbonCommandFactory() { public RibbonCommandFactory<?> ribbonCommandFactory() {
return new RestClientRibbonCommandFactory(this.clientFactory); return new RestClientRibbonCommandFactory(this.clientFactory);
} }
// pre filters // pre filters
@Bean @Bean
public PreDecorationFilter preDecorationFilter(ProxyRouteLocator routeLocator) { public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator) {
return new PreDecorationFilter(routeLocator, return new PreDecorationFilter(routeLocator,
this.zuulProperties.isAddProxyHeaders()); this.zuulProperties.isAddProxyHeaders());
} }
// route filters // route filters
@Bean @Bean
public RibbonRoutingFilter ribbonRoutingFilter(RibbonCommandFactory ribbonCommandFactory) { public RibbonRoutingFilter ribbonRoutingFilter(
RibbonCommandFactory<?> ribbonCommandFactory) {
ProxyRequestHelper helper = new ProxyRequestHelper(); ProxyRequestHelper helper = new ProxyRequestHelper();
if (this.traces != null) { if (this.traces != null) {
helper.setTraces(this.traces); helper.setTraces(this.traces);
} }
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory); RibbonRoutingFilter filter = new RibbonRoutingFilter(helper,
ribbonCommandFactory);
return filter; return filter;
} }
...@@ -119,9 +122,8 @@ public class ZuulProxyConfiguration extends ZuulConfiguration { ...@@ -119,9 +122,8 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
} }
@Bean @Bean
@Override public ApplicationListener<ApplicationEvent> zuulDiscoveryRefreshRoutesListener() {
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() { return new ZuulDiscoveryRefreshListener();
return new ZuulRefreshListener();
} }
@Configuration @Configuration
...@@ -144,38 +146,28 @@ public class ZuulProxyConfiguration extends ZuulConfiguration { ...@@ -144,38 +146,28 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
} }
} }
@Configuration @Configuration
@ConditionalOnClass(Endpoint.class) @ConditionalOnClass(Endpoint.class)
protected static class RoutesEndpointConfiguration { protected static class RoutesEndpointConfiguration {
@Autowired
private ProxyRouteLocator routeLocator;
@Bean @Bean
// @RefreshScope public RoutesEndpoint zuulEndpoint(ProxyRouteLocator routeLocator) {
public RoutesEndpoint zuulEndpoint() { return new RoutesEndpoint(routeLocator);
return new RoutesEndpoint(this.routeLocator);
} }
} }
private static class ZuulRefreshListener implements private static class ZuulDiscoveryRefreshListener
ApplicationListener<ApplicationEvent> { implements ApplicationListener<ApplicationEvent> {
private HeartbeatMonitor monitor = new HeartbeatMonitor(); private HeartbeatMonitor monitor = new HeartbeatMonitor();
@Autowired @Autowired
private ProxyRouteLocator routeLocator;
@Autowired
private ZuulHandlerMapping zuulHandlerMapping; private ZuulHandlerMapping zuulHandlerMapping;
@Override @Override
public void onApplicationEvent(ApplicationEvent event) { public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof InstanceRegisteredEvent if (event instanceof InstanceRegisteredEvent) {
|| event instanceof RefreshScopeRefreshedEvent
|| event instanceof RoutesRefreshedEvent) {
reset(); reset();
} }
else if (event instanceof ParentHeartbeatEvent) { else if (event instanceof ParentHeartbeatEvent) {
...@@ -196,8 +188,7 @@ public class ZuulProxyConfiguration extends ZuulConfiguration { ...@@ -196,8 +188,7 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
} }
private void reset() { private void reset() {
this.routeLocator.resetRoutes(); this.zuulHandlerMapping.setDirty(true);
this.zuulHandlerMapping.registerHandlers();
} }
} }
......
...@@ -25,21 +25,20 @@ import java.util.concurrent.atomic.AtomicReference; ...@@ -25,21 +25,20 @@ import java.util.concurrent.atomic.AtomicReference;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.zuul.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.util.PatternMatchUtils; import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.apachecommons.CommonsLog; import lombok.extern.apachecommons.CommonsLog;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
*/ */
@CommonsLog @CommonsLog
public class ProxyRouteLocator implements RouteLocator { public class ProxyRouteLocator implements RefreshableRouteLocator {
public static final String DEFAULT_ROUTE = "/**"; public static final String DEFAULT_ROUTE = "/**";
...@@ -99,15 +98,11 @@ public class ProxyRouteLocator implements RouteLocator { ...@@ -99,15 +98,11 @@ public class ProxyRouteLocator implements RouteLocator {
} }
@Override @Override
public Collection<String> getRoutePaths() {
return getRoutes().keySet();
}
@Override
public Collection<String> getIgnoredPaths() { public Collection<String> getIgnoredPaths() {
return this.properties.getIgnoredPatterns(); return this.properties.getIgnoredPatterns();
} }
@Override
public Map<String, String> getRoutes() { public Map<String, String> getRoutes() {
if (this.routes.get() == null) { if (this.routes.get() == null) {
this.routes.set(locateRoutes()); this.routes.set(locateRoutes());
...@@ -120,11 +115,16 @@ public class ProxyRouteLocator implements RouteLocator { ...@@ -120,11 +115,16 @@ public class ProxyRouteLocator implements RouteLocator {
return values; return values;
} }
public ProxyRouteSpec getMatchingRoute(String path) { @Override
public Route getMatchingRoute(String path) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Finding route for path: " + path); log.debug("Finding route for path: " + path);
} }
if (this.routes.get() == null) {
this.routes.set(locateRoutes());
}
String location = null; String location = null;
String targetPath = null; String targetPath = null;
String id = null; String id = null;
...@@ -164,7 +164,12 @@ public class ProxyRouteLocator implements RouteLocator { ...@@ -164,7 +164,12 @@ public class ProxyRouteLocator implements RouteLocator {
} }
} }
return (location == null ? null return (location == null ? null
: new ProxyRouteSpec(id, targetPath, location, prefix, retryable)); : new Route(id, targetPath, location, prefix, retryable));
}
@Override
public void refresh() {
resetRoutes();
} }
protected boolean matchesIgnoredPatterns(String path) { protected boolean matchesIgnoredPatterns(String path) {
...@@ -178,7 +183,7 @@ public class ProxyRouteLocator implements RouteLocator { ...@@ -178,7 +183,7 @@ public class ProxyRouteLocator implements RouteLocator {
return false; return false;
} }
public void resetRoutes() { private void resetRoutes() {
this.routes.set(locateRoutes()); this.routes.set(locateRoutes());
} }
...@@ -262,26 +267,4 @@ public class ProxyRouteLocator implements RouteLocator { ...@@ -262,26 +267,4 @@ public class ProxyRouteLocator implements RouteLocator {
} }
} }
public String getTargetPath(String matchingRoute, String requestURI) {
String path = getRoutes().get(matchingRoute);
return (path != null ? path : requestURI);
}
@Data
@AllArgsConstructor
public static class ProxyRouteSpec {
private String id;
private String path;
private String location;
private String prefix;
private Boolean retryable;
}
} }
/*
* Copyright 2013-2015 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 lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Route {
private String id;
private String path;
private String location;
private String prefix;
private Boolean retryable;
}
\ No newline at end of file
...@@ -17,14 +17,26 @@ ...@@ -17,14 +17,26 @@
package org.springframework.cloud.netflix.zuul.filters; package org.springframework.cloud.netflix.zuul.filters;
import java.util.Collection; import java.util.Collection;
import java.util.Map;
/** /**
* @author Dave Syer * @author Dave Syer
*/ */
public interface RouteLocator { public interface RouteLocator {
Collection<String> getRoutePaths(); /**
* Ignored route paths (or patterns), if any.
*/
Collection<String> getIgnoredPaths(); Collection<String> getIgnoredPaths();
/**
* A map of route path (pattern) to location (e.g. service id or URL).
*/
Map<String, String> getRoutes();
/**
* Maps a path to an actual route with full metadata.
*/
Route getMatchingRoute(String path);
} }
package org.springframework.cloud.netflix.zuul.filters; package org.springframework.cloud.netflix.zuul.filters;
/** /**
* @author Stéphane LEROY
*
* Provide a way to apply convention between routes and discovered services name. * Provide a way to apply convention between routes and discovered services name.
* *
* @author Stéphane LEROY
*
*/ */
public interface ServiceRouteMapper { public interface ServiceRouteMapper {
......
...@@ -17,9 +17,12 @@ ...@@ -17,9 +17,12 @@
package org.springframework.cloud.netflix.zuul.filters; package org.springframework.cloud.netflix.zuul.filters;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
/** /**
* @author Dave Syer * @author Dave Syer
...@@ -28,15 +31,17 @@ public class SimpleRouteLocator implements RouteLocator { ...@@ -28,15 +31,17 @@ public class SimpleRouteLocator implements RouteLocator {
private ZuulProperties properties; private ZuulProperties properties;
private PathMatcher pathMatcher = new AntPathMatcher();
public SimpleRouteLocator(ZuulProperties properties) { public SimpleRouteLocator(ZuulProperties properties) {
this.properties = properties; this.properties = properties;
} }
@Override @Override
public Collection<String> getRoutePaths() { public Map<String, String> getRoutes() {
Collection<String> paths = new LinkedHashSet<String>(); Map<String, String> paths = new LinkedHashMap<String, String>();
for (ZuulRoute route : this.properties.getRoutes().values()) { for (ZuulRoute route : this.properties.getRoutes().values()) {
paths.add(route.getPath()); paths.put(route.getPath(), route.getId());
} }
return paths; return paths;
} }
...@@ -46,4 +51,13 @@ public class SimpleRouteLocator implements RouteLocator { ...@@ -46,4 +51,13 @@ public class SimpleRouteLocator implements RouteLocator {
return this.properties.getIgnoredPatterns(); return this.properties.getIgnoredPatterns();
} }
@Override
public Route getMatchingRoute(String path) {
for (ZuulRoute route : this.properties.getRoutes().values()) {
if (this.pathMatcher.match(route.getPath(), path)) {
return route.getRoute(this.properties.getPrefix());
}
}
return null;
}
} }
...@@ -21,6 +21,7 @@ import java.util.LinkedHashMap; ...@@ -21,6 +21,7 @@ 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 java.util.Map.Entry;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
...@@ -106,8 +107,8 @@ public class ZuulProperties { ...@@ -106,8 +107,8 @@ public class ZuulProperties {
String location = null; String location = null;
String path = text; String path = text;
if (text.contains("=")) { if (text.contains("=")) {
String[] values = StringUtils.trimArrayElements(StringUtils.split(text, String[] values = StringUtils
"=")); .trimArrayElements(StringUtils.split(text, "="));
location = values[1]; location = values[1];
path = values[0]; path = values[0];
} }
...@@ -148,6 +149,10 @@ public class ZuulProperties { ...@@ -148,6 +149,10 @@ public class ZuulProperties {
return path; return path;
} }
public Route getRoute(String prefix) {
return new Route(this.id, this.path, getLocation(), prefix, this.retryable);
}
} }
public String getServletPattern() { public String getServletPattern() {
......
...@@ -19,8 +19,8 @@ package org.springframework.cloud.netflix.zuul.filters.pre; ...@@ -19,8 +19,8 @@ package org.springframework.cloud.netflix.zuul.filters.pre;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator; import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator.ProxyRouteSpec; import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
...@@ -33,13 +33,13 @@ import lombok.extern.apachecommons.CommonsLog; ...@@ -33,13 +33,13 @@ import lombok.extern.apachecommons.CommonsLog;
@CommonsLog @CommonsLog
public class PreDecorationFilter extends ZuulFilter { public class PreDecorationFilter extends ZuulFilter {
private ProxyRouteLocator routeLocator; private RouteLocator routeLocator;
private boolean addProxyHeaders; private boolean addProxyHeaders;
private UrlPathHelper urlPathHelper = new UrlPathHelper(); private UrlPathHelper urlPathHelper = new UrlPathHelper();
public PreDecorationFilter(ProxyRouteLocator routeLocator, boolean addProxyHeaders) { public PreDecorationFilter(RouteLocator routeLocator, boolean addProxyHeaders) {
this.routeLocator = routeLocator; this.routeLocator = routeLocator;
this.addProxyHeaders = addProxyHeaders; this.addProxyHeaders = addProxyHeaders;
} }
...@@ -65,7 +65,7 @@ public class PreDecorationFilter extends ZuulFilter { ...@@ -65,7 +65,7 @@ public class PreDecorationFilter extends ZuulFilter {
RequestContext ctx = RequestContext.getCurrentContext(); RequestContext ctx = RequestContext.getCurrentContext();
final String requestURI = this.urlPathHelper final String requestURI = this.urlPathHelper
.getPathWithinApplication(ctx.getRequest()); .getPathWithinApplication(ctx.getRequest());
ProxyRouteSpec route = this.routeLocator.getMatchingRoute(requestURI); Route route = this.routeLocator.getMatchingRoute(requestURI);
if (route != null) { if (route != null) {
String location = route.getLocation(); String location = route.getLocation();
if (location != null) { if (location != null) {
......
...@@ -21,6 +21,7 @@ import java.util.Collection; ...@@ -21,6 +21,7 @@ import java.util.Collection;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.web.ErrorController; import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.cloud.netflix.zuul.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.util.PatternMatchUtils; import org.springframework.util.PatternMatchUtils;
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
...@@ -41,6 +42,8 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { ...@@ -41,6 +42,8 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
private ErrorController errorController; private ErrorController errorController;
private volatile boolean dirty = true;
public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul) { public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul) {
this.routeLocator = routeLocator; this.routeLocator = routeLocator;
this.zuul = zuul; this.zuul = zuul;
...@@ -51,6 +54,13 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { ...@@ -51,6 +54,13 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
this.errorController = errorController; this.errorController = errorController;
} }
public void setDirty(boolean dirty) {
this.dirty = dirty;
if (this.routeLocator instanceof RefreshableRouteLocator) {
((RefreshableRouteLocator) this.routeLocator).refresh();
}
}
@Override @Override
protected Object lookupHandler(String urlPath, HttpServletRequest request) protected Object lookupHandler(String urlPath, HttpServletRequest request)
throws Exception { throws Exception {
...@@ -66,13 +76,21 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { ...@@ -66,13 +76,21 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
if (ctx.containsKey("forward.to")) { if (ctx.containsKey("forward.to")) {
return null; return null;
} }
if (this.dirty) {
synchronized (this) {
if (this.dirty) {
registerHandlers();
this.dirty = false;
}
}
}
return super.lookupHandler(urlPath, request); return super.lookupHandler(urlPath, request);
} }
public void registerHandlers() { private void registerHandlers() {
Collection<String> routes = this.routeLocator.getRoutePaths(); Collection<String> routes = this.routeLocator.getRoutes().keySet();
if (routes.isEmpty()) { if (routes.isEmpty()) {
this.logger.warn("No routes found from ProxyRouteLocator"); this.logger.warn("No routes found from RouteLocator");
} }
else { else {
for (String url : routes) { for (String url : routes) {
......
...@@ -79,7 +79,7 @@ public class SampleZuulProxyAppTestsWithHttpClient { ...@@ -79,7 +79,7 @@ public class SampleZuulProxyAppTestsWithHttpClient {
private RoutesEndpoint endpoint; private RoutesEndpoint endpoint;
@Autowired @Autowired
private RibbonCommandFactory ribbonCommandFactory; private RibbonCommandFactory<?> ribbonCommandFactory;
@Test @Test
public void bindRouteUsingPhysicalRoute() { public void bindRouteUsingPhysicalRoute() {
...@@ -195,7 +195,7 @@ public class SampleZuulProxyAppTestsWithHttpClient { ...@@ -195,7 +195,7 @@ public class SampleZuulProxyAppTestsWithHttpClient {
@Test @Test
public void simpleHostRouteWithSpace() { public void simpleHostRouteWithSpace() {
routes.addRoute("/self/**", "http://localhost:" + this.port); this.routes.addRoute("/self/**", "http://localhost:" + this.port);
this.endpoint.reset(); this.endpoint.reset();
ResponseEntity<String> result = new TestRestTemplate().exchange( ResponseEntity<String> result = new TestRestTemplate().exchange(
...@@ -207,7 +207,7 @@ public class SampleZuulProxyAppTestsWithHttpClient { ...@@ -207,7 +207,7 @@ public class SampleZuulProxyAppTestsWithHttpClient {
@Test @Test
public void simpleHostRouteWithOriginalQString() { public void simpleHostRouteWithOriginalQString() {
routes.addRoute("/self/**", "http://localhost:" + this.port); this.routes.addRoute("/self/**", "http://localhost:" + this.port);
this.endpoint.reset(); this.endpoint.reset();
ResponseEntity<String> result = new TestRestTemplate().exchange( ResponseEntity<String> result = new TestRestTemplate().exchange(
...@@ -220,13 +220,13 @@ public class SampleZuulProxyAppTestsWithHttpClient { ...@@ -220,13 +220,13 @@ public class SampleZuulProxyAppTestsWithHttpClient {
@Test @Test
public void simpleHostRouteWithOverriddenQString() { public void simpleHostRouteWithOverriddenQString() {
routes.addRoute("/self/**", "http://localhost:" + this.port); this.routes.addRoute("/self/**", "http://localhost:" + this.port);
this.endpoint.reset(); this.endpoint.reset();
ResponseEntity<String> result = new TestRestTemplate().exchange( ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port "http://localhost:" + this.port
+ "/self/qstring?override=true&different=key", HttpMethod.GET, + "/self/qstring?override=true&different=key",
new HttpEntity<>((Void) null), String.class); HttpMethod.GET, new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("Received {key=[overridden]}", result.getBody()); assertEquals("Received {key=[overridden]}", result.getBody());
} }
...@@ -234,7 +234,7 @@ public class SampleZuulProxyAppTestsWithHttpClient { ...@@ -234,7 +234,7 @@ public class SampleZuulProxyAppTestsWithHttpClient {
@Test @Test
public void ribbonCommandFactoryOverridden() { public void ribbonCommandFactoryOverridden() {
assertTrue("ribbonCommandFactory not a MyRibbonCommandFactory", assertTrue("ribbonCommandFactory not a MyRibbonCommandFactory",
ribbonCommandFactory instanceof HttpClientRibbonCommandFactory); this.ribbonCommandFactory instanceof HttpClientRibbonCommandFactory);
} }
} }
...@@ -299,7 +299,7 @@ class SampleHttpClientZuulProxyApplication { ...@@ -299,7 +299,7 @@ class SampleHttpClientZuulProxyApplication {
} }
@Bean @Bean
public RibbonCommandFactory ribbonCommandFactory( public RibbonCommandFactory<?> ribbonCommandFactory(
final SpringClientFactory clientFactory) { final SpringClientFactory clientFactory) {
return new HttpClientRibbonCommandFactory(clientFactory); return new HttpClientRibbonCommandFactory(clientFactory);
} }
......
...@@ -58,7 +58,7 @@ public class SimpleZuulServerApplicationTests { ...@@ -58,7 +58,7 @@ public class SimpleZuulServerApplicationTests {
@Test @Test
public void bindRoute() { public void bindRoute() {
assertTrue(this.routes.getRoutePaths().contains("/testing123/**")); assertTrue(this.routes.getRoutes().keySet().contains("/testing123/**"));
} }
@Test @Test
......
...@@ -25,7 +25,6 @@ import org.junit.Test; ...@@ -25,7 +25,6 @@ import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator.ProxyRouteSpec;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.cloud.netflix.zuul.filters.regex.RegExServiceRouteMapper; import org.springframework.cloud.netflix.zuul.filters.regex.RegExServiceRouteMapper;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
...@@ -72,7 +71,7 @@ public class ProxyRouteLocatorTests { ...@@ -72,7 +71,7 @@ public class ProxyRouteLocatorTests {
this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**"));
this.properties.init(); this.properties.init();
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1"); Route route = routeLocator.getMatchingRoute("/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("foo", route.getId()); assertEquals("foo", route.getId());
} }
...@@ -85,7 +84,7 @@ public class ProxyRouteLocatorTests { ...@@ -85,7 +84,7 @@ public class ProxyRouteLocatorTests {
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
this.properties.init(); this.properties.init();
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/1", route.getPath()); assertEquals("/1", route.getPath());
} }
...@@ -97,7 +96,7 @@ public class ProxyRouteLocatorTests { ...@@ -97,7 +96,7 @@ public class ProxyRouteLocatorTests {
this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**"));
this.properties.init(); this.properties.init();
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/app/foo/1"); Route route = routeLocator.getMatchingRoute("/app/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/1", route.getPath()); assertEquals("/1", route.getPath());
} }
...@@ -111,7 +110,7 @@ public class ProxyRouteLocatorTests { ...@@ -111,7 +110,7 @@ public class ProxyRouteLocatorTests {
this.properties.setStripPrefix(false); this.properties.setStripPrefix(false);
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/proxy/foo/1", route.getPath()); assertEquals("/proxy/foo/1", route.getPath());
} }
...@@ -124,7 +123,7 @@ public class ProxyRouteLocatorTests { ...@@ -124,7 +123,7 @@ public class ProxyRouteLocatorTests {
this.properties.setStripPrefix(false); this.properties.setStripPrefix(false);
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/proxy/1", route.getPath()); assertEquals("/proxy/1", route.getPath());
} }
...@@ -137,7 +136,7 @@ public class ProxyRouteLocatorTests { ...@@ -137,7 +136,7 @@ public class ProxyRouteLocatorTests {
new ZuulRoute("foo", "/foo/**", "foo", null, false, null)); new ZuulRoute("foo", "/foo/**", "foo", null, false, null));
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/foo/1", route.getPath()); assertEquals("/foo/1", route.getPath());
} }
...@@ -151,7 +150,7 @@ public class ProxyRouteLocatorTests { ...@@ -151,7 +150,7 @@ public class ProxyRouteLocatorTests {
this.properties.getRoutes().put("foo", zuulRoute); this.properties.getRoutes().put("foo", zuulRoute);
this.properties.init(); this.properties.init();
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1"); Route route = routeLocator.getMatchingRoute("/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/1", route.getPath()); assertEquals("/1", route.getPath());
} }
...@@ -164,7 +163,7 @@ public class ProxyRouteLocatorTests { ...@@ -164,7 +163,7 @@ public class ProxyRouteLocatorTests {
this.properties.getRoutes().put("bar", new ZuulRoute("/bar/**")); this.properties.getRoutes().put("bar", new ZuulRoute("/bar/**"));
this.properties.init(); this.properties.init();
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/bar/1"); Route route = routeLocator.getMatchingRoute("/bar/1");
assertEquals("bar", route.getLocation()); assertEquals("bar", route.getLocation());
assertEquals("bar", route.getId()); assertEquals("bar", route.getId());
} }
...@@ -177,7 +176,7 @@ public class ProxyRouteLocatorTests { ...@@ -177,7 +176,7 @@ public class ProxyRouteLocatorTests {
this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**"));
this.properties.init(); this.properties.init();
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1"); Route route = routeLocator.getMatchingRoute("/foo/1");
assertNull("routes did not ignore " + IGNOREDPATTERN, route); assertNull("routes did not ignore " + IGNOREDPATTERN, route);
} }
...@@ -190,7 +189,7 @@ public class ProxyRouteLocatorTests { ...@@ -190,7 +189,7 @@ public class ProxyRouteLocatorTests {
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
this.properties.init(); this.properties.init();
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/1", route.getPath()); assertEquals("/1", route.getPath());
} }
...@@ -203,7 +202,7 @@ public class ProxyRouteLocatorTests { ...@@ -203,7 +202,7 @@ public class ProxyRouteLocatorTests {
this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**"));
this.properties.init(); this.properties.init();
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/app/foo/1"); Route route = routeLocator.getMatchingRoute("/app/foo/1");
assertNull("routes did not ignore " + IGNOREDPATTERN, route); assertNull("routes did not ignore " + IGNOREDPATTERN, route);
} }
...@@ -217,7 +216,7 @@ public class ProxyRouteLocatorTests { ...@@ -217,7 +216,7 @@ public class ProxyRouteLocatorTests {
this.properties.setStripPrefix(false); this.properties.setStripPrefix(false);
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/proxy/foo/1", route.getPath()); assertEquals("/proxy/foo/1", route.getPath());
} }
...@@ -232,7 +231,7 @@ public class ProxyRouteLocatorTests { ...@@ -232,7 +231,7 @@ public class ProxyRouteLocatorTests {
this.properties.setStripPrefix(false); this.properties.setStripPrefix(false);
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertNull("routes did not ignore " + "/proxy" + IGNOREDPATTERN, route); assertNull("routes did not ignore " + "/proxy" + IGNOREDPATTERN, route);
} }
...@@ -245,7 +244,7 @@ public class ProxyRouteLocatorTests { ...@@ -245,7 +244,7 @@ public class ProxyRouteLocatorTests {
this.properties.setStripPrefix(false); this.properties.setStripPrefix(false);
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/proxy/1", route.getPath()); assertEquals("/proxy/1", route.getPath());
} }
...@@ -259,7 +258,7 @@ public class ProxyRouteLocatorTests { ...@@ -259,7 +258,7 @@ public class ProxyRouteLocatorTests {
this.properties.setStripPrefix(false); this.properties.setStripPrefix(false);
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertNull("routes did not ignore " + "/proxy" + IGNOREDPATTERN, route); assertNull("routes did not ignore " + "/proxy" + IGNOREDPATTERN, route);
} }
...@@ -272,7 +271,7 @@ public class ProxyRouteLocatorTests { ...@@ -272,7 +271,7 @@ public class ProxyRouteLocatorTests {
new ZuulRoute("foo", "/foo/**", "foo", null, false, null)); new ZuulRoute("foo", "/foo/**", "foo", null, false, null));
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/foo/1", route.getPath()); assertEquals("/foo/1", route.getPath());
} }
...@@ -286,7 +285,7 @@ public class ProxyRouteLocatorTests { ...@@ -286,7 +285,7 @@ public class ProxyRouteLocatorTests {
new ZuulRoute("foo", "/foo/**", "foo", null, false, null)); new ZuulRoute("foo", "/foo/**", "foo", null, false, null));
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); Route route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertNull("routes did not ignore " + "/proxy" + IGNOREDPATTERN, route); assertNull("routes did not ignore " + "/proxy" + IGNOREDPATTERN, route);
} }
...@@ -300,7 +299,7 @@ public class ProxyRouteLocatorTests { ...@@ -300,7 +299,7 @@ public class ProxyRouteLocatorTests {
this.properties.getRoutes().put("foo", zuulRoute); this.properties.getRoutes().put("foo", zuulRoute);
this.properties.init(); this.properties.init();
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1"); Route route = routeLocator.getMatchingRoute("/foo/1");
assertNull("routes did not ignore " + IGNOREDPATTERN, route); assertNull("routes did not ignore " + IGNOREDPATTERN, route);
} }
......
...@@ -16,10 +16,7 @@ ...@@ -16,10 +16,7 @@
package org.springframework.cloud.netflix.zuul.web; package org.springframework.cloud.netflix.zuul.web;
import static org.junit.Assert.assertNotNull; import java.util.Collections;
import static org.junit.Assert.assertNull;
import java.util.Arrays;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -30,6 +27,9 @@ import org.springframework.mock.web.MockHttpServletRequest; ...@@ -30,6 +27,9 @@ import org.springframework.mock.web.MockHttpServletRequest;
import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.context.RequestContext;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/** /**
* @author Dave Syer * @author Dave Syer
*/ */
...@@ -53,25 +53,29 @@ public class ZuulHandlerMappingTests { ...@@ -53,25 +53,29 @@ public class ZuulHandlerMappingTests {
@Test @Test
public void mappedPath() throws Exception { public void mappedPath() throws Exception {
Mockito.when(this.locator.getRoutePaths()).thenReturn(Arrays.asList("/foo/**")); Mockito.when(this.locator.getRoutes())
.thenReturn(Collections.singletonMap("/foo/**", "foo"));
this.request.setServletPath("/foo/"); this.request.setServletPath("/foo/");
this.mapping.registerHandlers(); this.mapping.setDirty(true);
assertNotNull(this.mapping.getHandler(this.request)); assertNotNull(this.mapping.getHandler(this.request));
} }
@Test @Test
public void defaultPath() throws Exception { public void defaultPath() throws Exception {
Mockito.when(this.locator.getRoutePaths()).thenReturn(Arrays.asList("/**")); Mockito.when(this.locator.getRoutes())
.thenReturn(Collections.singletonMap("/**", "default"));
;
this.request.setServletPath("/"); this.request.setServletPath("/");
this.mapping.registerHandlers(); this.mapping.setDirty(true);
assertNotNull(this.mapping.getHandler(this.request)); assertNotNull(this.mapping.getHandler(this.request));
} }
@Test @Test
public void errorPath() throws Exception { public void errorPath() throws Exception {
Mockito.when(this.locator.getRoutePaths()).thenReturn(Arrays.asList("/**")); Mockito.when(this.locator.getRoutes())
.thenReturn(Collections.singletonMap("/**", "default"));
this.request.setServletPath("/error"); this.request.setServletPath("/error");
this.mapping.registerHandlers(); this.mapping.setDirty(true);
assertNull(this.mapping.getHandler(this.request)); assertNull(this.mapping.getHandler(this.request));
} }
......
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