diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 151f36e5..ba1b0a93 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -609,3 +609,22 @@ 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. +=== Plain Embedded Zuul + +You can also run a Zuul server without the proxying, or switch on parts of the proxying platform selectively, if you +use `@EnableZuulServer` (instead of `@EnableZuulProxy`). Any beans that you add to the application of type `ZuulFilter` +will be installed automatically. + +In this case the routes into the Zuul server are +still specified by configuring "zuul.routes.*", but there is no service discovery and no proxying, so the +"serviceId" and "url" settings are ignored. For example: + +.application.yml +[source,yaml] +---- + zuul: + routes: + api: /api/** +---- + +maps all paths in "/api/**" to the Zuul filter chain. diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfiguration.java index 877db50c..281d1211 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignConfiguration.java @@ -1,7 +1,5 @@ package org.springframework.cloud.netflix.feign; -import java.net.URI; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.netflix.archaius.ConfigurableEnvironmentConfiguration; import org.springframework.context.annotation.Configuration; @@ -78,7 +76,6 @@ public class FeignConfiguration { } protected T loadBalance(Feign.Builder builder, Class type, String schemeName) { - URI.create(schemeName).getHost(); if(ribbonClient != null) { return builder.client(ribbonClient).target(type, schemeName); } else { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulProxy.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulProxy.java index fe85b4bc..4c636788 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulProxy.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulProxy.java @@ -10,12 +10,19 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** + * Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can + * forward requests to backend servers. The backends can be registered manually through + * configuration or via Eureka. + * + * @see EnableZuulServer for how to get a Zuul server without any proxying + * * @author Spencer Gibb + * @author Dave Syer */ @EnableCircuitBreaker @EnableDiscoveryClient @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Import(ZuulConfiguration.class) +@Import(ZuulProxyConfiguration.class) public @interface EnableZuulProxy { } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulServer.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulServer.java index fdb6b80b..6cdb2fc6 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulServer.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulServer.java @@ -1,17 +1,22 @@ package org.springframework.cloud.netflix.zuul; -import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + import org.springframework.context.annotation.Import; -import java.lang.annotation.*; - /** + * Set up the application to act as a generic Zuul server without any built-in reverse + * proxy features. The routes into the Zuul server can be configured through + * {@link ZuulProperties} (by default there are none). + * + * @see EnableZuulProxy to see how to get reverse proxy out of the box + * * @author Spencer Gibb - * @deprecated @see org.springframework.cloud.netflix.zuul.EnableZuulProxy */ -@EnableCircuitBreaker -@EnableDiscoveryClient @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocator.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocator.java index 1db05ef6..cc037d79 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocator.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocator.java @@ -10,6 +10,7 @@ import java.util.concurrent.atomic.AtomicReference; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; + import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute; import org.springframework.util.AntPathMatcher; @@ -20,7 +21,7 @@ import org.springframework.util.StringUtils; * @author Spencer Gibb */ @Slf4j -public class ProxyRouteLocator { +public class ProxyRouteLocator implements RouteLocator { public static final String DEFAULT_ROUTE = "/**"; @@ -49,6 +50,7 @@ public class ProxyRouteLocator { resetRoutes(); } + @Override public Collection getRoutePaths() { return getRoutes().keySet(); } @@ -95,11 +97,11 @@ public class ProxyRouteLocator { break; } } - return location == null ? null : new ProxyRouteSpec(id, targetPath, location, prefix); + return location == null ? null : new ProxyRouteSpec(id, targetPath, location, + prefix); } - // Package access so ZuulHandlerMapping can reset it's mappings - void resetRoutes() { + public void resetRoutes() { routes.set(locateRoutes()); } @@ -110,15 +112,17 @@ public class ProxyRouteLocator { addConfiguredRoutes(routesMap); routesMap.putAll(staticRoutes); - // Add routes for discovery services by default - List services = discovery.getServices(); - for (String serviceId : services) { - // Ignore specifically ignored services and those that were manually - // configured - String key = "/" + serviceId + "/**"; - if (!properties.getIgnoredServices().contains(serviceId) - && !routesMap.containsKey(key)) { - routesMap.put(key, new ZuulRoute(key, serviceId)); + if (discovery != null) { + // Add routes for discovery services by default + List services = discovery.getServices(); + for (String serviceId : services) { + // Ignore specifically ignored services and those that were manually + // configured + String key = "/" + serviceId + "/**"; + if (!properties.getIgnoredServices().contains(serviceId) + && !routesMap.containsKey(key)) { + routesMap.put(key, new ZuulRoute(key, serviceId)); + } } } @@ -154,7 +158,7 @@ public class ProxyRouteLocator { } protected void addConfiguredRoutes(Map routes) { - Map routeEntries = properties.getRoutesWithDefaultServiceIds(); + Map routeEntries = properties.getRoutes(); for (ZuulRoute entry : routeEntries.values()) { String route = entry.getPath(); if (routes.containsKey(route)) { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RouteLocator.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RouteLocator.java new file mode 100644 index 00000000..3c67ce20 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RouteLocator.java @@ -0,0 +1,28 @@ +/* + * 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; + +import java.util.Collection; + +/** + * @author Dave Syer + * + */ +public interface RouteLocator { + + Collection getRoutePaths(); + +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RoutesEndpoint.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RoutesEndpoint.java index 029c805d..5d36880d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RoutesEndpoint.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RoutesEndpoint.java @@ -1,39 +1,54 @@ package org.springframework.cloud.netflix.zuul; +import java.util.Map; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; -import java.util.Map; - /** * Endpoint to display and reset the zuul proxy routes * * @author Spencer Gibb * @author Dave Syer */ -public class RoutesEndpoint implements MvcEndpoint { +@ManagedResource(description = "Can be used to list and reset the reverse proxy routes") +public class RoutesEndpoint implements MvcEndpoint, ApplicationEventPublisherAware { - private ZuulHandlerMapping handlerMapping; + private ProxyRouteLocator routes; + private ApplicationEventPublisher publisher; + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } @Autowired - public RoutesEndpoint(ZuulHandlerMapping handlerMapping) { - this.handlerMapping = handlerMapping; + public RoutesEndpoint(ProxyRouteLocator routes) { + this.routes = routes; } @RequestMapping(method = RequestMethod.POST) @ResponseBody + @ManagedOperation public Map reset() { - return handlerMapping.reset(); + publisher.publishEvent(new RoutesRefreshedEvent(routes)); + return getRoutes(); } @RequestMapping(method = RequestMethod.GET) @ResponseBody + @ManagedAttribute public Map getRoutes() { - return handlerMapping.getRoutes(); + return routes.getRoutes(); } @Override diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RoutesRefreshedEvent.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RoutesRefreshedEvent.java new file mode 100644 index 00000000..4560d1c3 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RoutesRefreshedEvent.java @@ -0,0 +1,38 @@ +/* + * 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; + +import org.springframework.context.ApplicationEvent; + +/** + * @author Dave Syer + * + */ +@SuppressWarnings("serial") +public class RoutesRefreshedEvent extends ApplicationEvent { + + private RouteLocator locator; + + public RoutesRefreshedEvent(RouteLocator locator) { + super(locator); + this.locator = locator; + } + + public RouteLocator getLocator() { + return locator; + } + +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/SimpleRouteLocator.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/SimpleRouteLocator.java new file mode 100644 index 00000000..87c4f011 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/SimpleRouteLocator.java @@ -0,0 +1,44 @@ +/* + * 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; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import org.springframework.cloud.netflix.zuul.ZuulProperties.ZuulRoute; + +/** + * @author Dave Syer + * + */ +public class SimpleRouteLocator implements RouteLocator { + + private ZuulProperties properties; + + public SimpleRouteLocator(ZuulProperties properties) { + this.properties = properties; + } + + @Override + public Collection getRoutePaths() { + Collection paths = new LinkedHashSet(); + for (ZuulRoute route : properties.getRoutes().values()) { + paths.add(route.getPath()); + } + return paths; + } + +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java index 3e6e320c..62423e1f 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java @@ -3,51 +3,37 @@ package org.springframework.cloud.netflix.zuul; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.cloud.netflix.ribbon.SpringClientFactory; -import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent; import org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter; import org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter; import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter; -import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter; import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter; -import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.http.ZuulServlet; /** * @author Spencer Gibb + * @author Dave Syer */ @Configuration @EnableConfigurationProperties(ZuulProperties.class) @ConditionalOnClass(ZuulServlet.class) -@ConditionalOnExpression("${zuul.enabled:true}") public class ZuulConfiguration { - @Autowired(required = false) - private TraceRepository traces; - - @Autowired - private SpringClientFactory clientFactory; - - @Autowired - private DiscoveryClient discovery; - @Autowired private ZuulProperties zuulProperties; @Bean - public ProxyRouteLocator routes() { - return new ProxyRouteLocator(discovery, zuulProperties); + public RouteLocator routes() { + return new SimpleRouteLocator(zuulProperties); } @Bean @@ -56,9 +42,8 @@ public class ZuulConfiguration { } @Bean - @RefreshScope - public ZuulHandlerMapping zuulHandlerMapping() { - return new ZuulHandlerMapping(routes(), zuulController()); + public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { + return new ZuulHandlerMapping(routes, zuulController()); } @Configuration @@ -75,8 +60,23 @@ public class ZuulConfiguration { } @Bean - public RoutesEndpoint zuulEndpoint() { - return new RoutesEndpoint(zuulHandlerMapping()); + public ApplicationListener zuulRefreshRoutesListener() { + return new ZuulRefreshListener(); + } + + private static class ZuulRefreshListener implements + ApplicationListener { + + @Autowired + private ZuulHandlerMapping zuulHandlerMapping; + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ContextRefreshedEvent + || event instanceof RefreshScopeRefreshedEvent) + zuulHandlerMapping.registerHandlers(); + } + } // pre filters @@ -85,36 +85,11 @@ public class ZuulConfiguration { return new DebugFilter(); } - @Bean - public PreDecorationFilter preDecorationFilter() { - return new PreDecorationFilter(routes(), zuulProperties); - } - @Bean public Servlet30WrapperFilter servlet30WrapperFilter() { return new Servlet30WrapperFilter(); } - // route filters - @Bean - public RibbonRoutingFilter ribbonRoutingFilter() { - ProxyRequestHelper helper = new ProxyRequestHelper(); - if (traces != null) { - helper.setTraces(traces); - } - RibbonRoutingFilter filter = new RibbonRoutingFilter(helper , clientFactory); - return filter; - } - - @Bean - public SimpleHostRoutingFilter simpleHostRoutingFilter() { - ProxyRequestHelper helper = new ProxyRequestHelper(); - if (traces != null) { - helper.setTraces(traces); - } - return new SimpleHostRoutingFilter(helper); - } - // post filters @Bean public SendResponseFilter sendResponseFilter() { @@ -125,4 +100,5 @@ public class ZuulConfiguration { public SendErrorFilter sendErrorFilter() { return new SendErrorFilter(); } + } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java index 8425fd1e..05d0fea0 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java @@ -1,19 +1,9 @@ package org.springframework.cloud.netflix.zuul; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.client.discovery.DiscoveryHeartbeatEvent; -import org.springframework.cloud.client.discovery.InstanceRegisteredEvent; -import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; - import java.util.Collection; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; /** * MVC HandlerMapping that maps incoming request paths to remote services. @@ -21,37 +11,19 @@ import java.util.concurrent.atomic.AtomicReference; * @author Spencer Gibb * @author Dave Syer */ -@ManagedResource(description = "Can be used to list and reset the reverse proxy routes") -public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements - ApplicationListener { +public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { - private ProxyRouteLocator routeLocator; + private RouteLocator routeLocator; private ZuulController zuul; - private AtomicReference latestHeartbeat = new AtomicReference<>(); - @Autowired - public ZuulHandlerMapping(ProxyRouteLocator routeLocator, ZuulController zuul) { + public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul) { this.routeLocator = routeLocator; this.zuul = zuul; setOrder(-200); } - @Override - public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof InstanceRegisteredEvent - || event instanceof RefreshScopeRefreshedEvent) { - reset(); - } else if (event instanceof DiscoveryHeartbeatEvent) { - DiscoveryHeartbeatEvent e = (DiscoveryHeartbeatEvent) event; - if (latestHeartbeat.get() == null || !latestHeartbeat.get().equals(e.getValue())) { - latestHeartbeat.set(e.getValue()); - reset(); - } - } - } - protected void registerHandlers() { Collection routes = routeLocator.getRoutePaths(); if (routes.isEmpty()) { @@ -64,16 +36,4 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements } } - @ManagedOperation - public Map reset() { - routeLocator.resetRoutes(); - registerHandlers(); - return getRoutes(); - } - - @ManagedAttribute - public Map getRoutes() { - return routeLocator.getRoutes(); - } - } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProperties.java index f1975a3b..c15172c8 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProperties.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProperties.java @@ -6,6 +6,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import javax.annotation.PostConstruct; + import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -26,7 +28,8 @@ public class ZuulProperties { private boolean addProxyHeaders = true; private List ignoredServices = new ArrayList(); - public Map getRoutesWithDefaultServiceIds() { + @PostConstruct + public void init() { for (Entry entry : this.routes.entrySet()) { ZuulRoute value = entry.getValue(); if (!StringUtils.hasText(value.getLocation())) { @@ -39,7 +42,6 @@ public class ZuulProperties { value.path = "/" + entry.getKey() + "/**"; } } - return this.routes; } @Data @@ -91,10 +93,10 @@ public class ZuulProperties { serviceId = location; } } - + private String extractId(String path) { path = path.startsWith("/") ? path.substring(1) : path; - path = path.replace("/*","").replace("*", ""); + path = path.replace("/*", "").replace("*", ""); return path; } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java new file mode 100644 index 00000000..b74b0e11 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java @@ -0,0 +1,120 @@ +package org.springframework.cloud.netflix.zuul; + +import java.util.concurrent.atomic.AtomicReference; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.trace.TraceRepository; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.DiscoveryHeartbeatEvent; +import org.springframework.cloud.client.discovery.InstanceRegisteredEvent; +import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter; +import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Spencer Gibb + * @author Dave Syer + */ +@Configuration +public class ZuulProxyConfiguration extends ZuulConfiguration { + + @Autowired(required = false) + private TraceRepository traces; + + @Autowired + private SpringClientFactory clientFactory; + + @Autowired + private DiscoveryClient discovery; + + @Autowired + private ZuulProperties zuulProperties; + + @Bean + @Override + public ProxyRouteLocator routes() { + return new ProxyRouteLocator(discovery, zuulProperties); + } + + @Bean + // @RefreshScope + public RoutesEndpoint zuulEndpoint() { + return new RoutesEndpoint(routes()); + } + + // pre filters + @Bean + public PreDecorationFilter preDecorationFilter() { + return new PreDecorationFilter(routes(), zuulProperties); + } + + // route filters + @Bean + public RibbonRoutingFilter ribbonRoutingFilter() { + ProxyRequestHelper helper = new ProxyRequestHelper(); + if (traces != null) { + helper.setTraces(traces); + } + RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, clientFactory); + return filter; + } + + @Bean + public SimpleHostRoutingFilter simpleHostRoutingFilter() { + ProxyRequestHelper helper = new ProxyRequestHelper(); + if (traces != null) { + helper.setTraces(traces); + } + return new SimpleHostRoutingFilter(helper); + } + + @Bean + @Override + public ApplicationListener zuulRefreshRoutesListener() { + return new ZuulRefreshListener(); + } + + private static class ZuulRefreshListener implements + ApplicationListener { + + private AtomicReference latestHeartbeat = new AtomicReference<>(); + + @Autowired + private ProxyRouteLocator routeLocator; + + @Autowired + ZuulHandlerMapping zuulHandlerMapping; + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof InstanceRegisteredEvent + || event instanceof RefreshScopeRefreshedEvent + || event instanceof RoutesRefreshedEvent) { + reset(); + } + else if (event instanceof DiscoveryHeartbeatEvent) { + DiscoveryHeartbeatEvent e = (DiscoveryHeartbeatEvent) event; + if (latestHeartbeat.get() == null + || !latestHeartbeat.get().equals(e.getValue())) { + latestHeartbeat.set(e.getValue()); + reset(); + } + } + + } + + private void reset() { + routeLocator.resetRoutes(); + zuulHandlerMapping.registerHandlers(); + } + + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocatorTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocatorTests.java index 5fc0e6b3..610b29e7 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocatorTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ProxyRouteLocatorTests.java @@ -46,6 +46,7 @@ public class ProxyRouteLocatorTests { public void testGetMatchingPath() throws Exception { ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); + properties.init(); routeLocator.getRoutes(); // force refresh ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1"); assertEquals("foo", route.getLocation()); @@ -57,6 +58,7 @@ public class ProxyRouteLocatorTests { ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); this.properties.setPrefix("/proxy"); + properties.init(); routeLocator.getRoutes(); // force refresh ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); assertEquals("foo", route.getLocation()); @@ -104,6 +106,7 @@ public class ProxyRouteLocatorTests { ZuulRoute zuulRoute = new ZuulRoute("/foo/**"); zuulRoute.setStripPrefix(true); this.properties.getRoutes().put("foo", zuulRoute); + properties.init(); routeLocator.getRoutes(); // force refresh ProxyRouteSpec route = routeLocator.getMatchingRoute("/foo/1"); assertEquals("foo", route.getLocation()); @@ -114,6 +117,7 @@ public class ProxyRouteLocatorTests { public void testGetRoutes() { ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/"+ASERVICE + "/**")); + properties.init(); Map routesMap = routeLocator.getRoutes(); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplication.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplication.java deleted file mode 100644 index 23a95049..00000000 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplication.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import java.util.Arrays; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.netflix.ribbon.RibbonClient; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import com.netflix.appinfo.EurekaInstanceConfig; -import com.netflix.loadbalancer.BaseLoadBalancer; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; -import com.netflix.zuul.ZuulFilter; - -// Don't use @SpringBootApplication because we don't want to component scan -@Configuration -@EnableAutoConfiguration -@RestController -@EnableZuulProxy -@EnableDiscoveryClient -@RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class) -public class SampleZuulProxyApplication { - - @RequestMapping("/testing123") - public String testing123() { - throw new RuntimeException("myerror"); - } - - @RequestMapping("/local") - public String local() { - return "Hello local"; - } - - @RequestMapping(value = "/local/{id}", method = RequestMethod.DELETE) - public String delete() { - return "Deleted!"; - } - - @RequestMapping(value = "/local/{id}", method = RequestMethod.GET) - public String get() { - return "Gotten!"; - } - - @RequestMapping("/") - public String home() { - return "Hello world"; - } - - @Bean - public ZuulFilter sampleFilter() { - return new ZuulFilter() { - @Override - public String filterType() { - return "pre"; - } - - @Override - public boolean shouldFilter() { - return true; - } - - @Override - public Object run() { - return null; - } - - @Override - public int filterOrder() { - return 0; - } - }; - } - - public static void main(String[] args) { - SpringApplication.run(SampleZuulProxyApplication.class, args); - } - -} - -// Load balancer with fixed server list for "simple" pointing to localhost -@Configuration -class SimpleRibbonClientConfiguration { - @Bean - public ILoadBalancer ribbonLoadBalancer(EurekaInstanceConfig instance) { - BaseLoadBalancer balancer = new BaseLoadBalancer(); - balancer.setServersList(Arrays.asList(new Server("localhost", instance - .getNonSecurePort()))); - return balancer; - } -} \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java index d4aa21fc..b72f31a1 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java @@ -2,13 +2,20 @@ package org.springframework.cloud.netflix.zuul; import static org.junit.Assert.assertEquals; +import java.util.Arrays; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.TestRestTemplate; +import org.springframework.cloud.netflix.ribbon.RibbonClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -16,6 +23,15 @@ import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.netflix.appinfo.EurekaInstanceConfig; +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import com.netflix.zuul.ZuulFilter; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SampleZuulProxyApplication.class) @@ -33,7 +49,7 @@ public class SampleZuulProxyApplicationTests { private ProxyRouteLocator routes; @Autowired - private ZuulHandlerMapping mapping; + private RoutesEndpoint endpoint; @Test public void bindRouteUsingPhysicalRoute() { @@ -47,7 +63,6 @@ public class SampleZuulProxyApplicationTests { @Test public void getOnSelfViaRibbonRoutingFilter() { - mapping.reset(); ResponseEntity result = new TestRestTemplate().exchange( "http://localhost:" + port + "/simple/local/1", HttpMethod.GET, new HttpEntity((Void) null), String.class); @@ -58,7 +73,7 @@ public class SampleZuulProxyApplicationTests { @Test public void deleteOnSelfViaSimpleHostRoutingFilter() { routes.addRoute("/self/**", "http://localhost:" + port + "/local"); - mapping.reset(); + endpoint.reset(); ResponseEntity result = new TestRestTemplate().exchange( "http://localhost:" + port + "/self/1", HttpMethod.DELETE, new HttpEntity((Void) null), String.class); @@ -67,3 +82,79 @@ public class SampleZuulProxyApplicationTests { } } + +//Don't use @SpringBootApplication because we don't want to component scan +@Configuration +@EnableAutoConfiguration +@RestController +@EnableZuulProxy +@RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class) +class SampleZuulProxyApplication { + + @RequestMapping("/testing123") + public String testing123() { + throw new RuntimeException("myerror"); + } + + @RequestMapping("/local") + public String local() { + return "Hello local"; + } + + @RequestMapping(value = "/local/{id}", method = RequestMethod.DELETE) + public String delete() { + return "Deleted!"; + } + + @RequestMapping(value = "/local/{id}", method = RequestMethod.GET) + public String get() { + return "Gotten!"; + } + + @RequestMapping("/") + public String home() { + return "Hello world"; + } + + @Bean + public ZuulFilter sampleFilter() { + return new ZuulFilter() { + @Override + public String filterType() { + return "pre"; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public Object run() { + return null; + } + + @Override + public int filterOrder() { + return 0; + } + }; + } + + public static void main(String[] args) { + SpringApplication.run(SampleZuulProxyApplication.class, args); + } + +} + +//Load balancer with fixed server list for "simple" pointing to localhost +@Configuration +class SimpleRibbonClientConfiguration { + @Bean + public ILoadBalancer ribbonLoadBalancer(EurekaInstanceConfig instance) { + BaseLoadBalancer balancer = new BaseLoadBalancer(); + balancer.setServersList(Arrays.asList(new Server("localhost", instance + .getNonSecurePort()))); + return balancer; + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SimpleZuulServerApplicationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SimpleZuulServerApplicationTests.java new file mode 100644 index 00000000..ffcfb05d --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SimpleZuulServerApplicationTests.java @@ -0,0 +1,112 @@ +package org.springframework.cloud.netflix.zuul; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.netflix.zuul.ZuulFilter; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SimpleZuulServerApplication.class) +@WebAppConfiguration +@IntegrationTest({ "server.port: 0"}) +@DirtiesContext +public class SimpleZuulServerApplicationTests { + + @Value("${local.server.port}") + private int port; + + @Autowired + private RouteLocator routes; + + @Test + public void bindRoute() { + assertTrue(routes.getRoutePaths().contains("/testing123/**")); + } + + @Test + public void getOnSelf() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + port + "/", HttpMethod.GET, + new HttpEntity((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Hello world", result.getBody()); + } + + @Test + public void getOnSelfViaFilter() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + port + "/testing123/1", HttpMethod.GET, + new HttpEntity((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + } + +} + +//Don't use @SpringBootApplication because we don't want to component scan +@Configuration +@EnableAutoConfiguration +@RestController +@EnableZuulServer +class SimpleZuulServerApplication { + + @RequestMapping("/local") + public String local() { + return "Hello local"; + } + + @RequestMapping("/") + public String home() { + return "Hello world"; + } + + @Bean + public ZuulFilter sampleFilter() { + return new ZuulFilter() { + @Override + public String filterType() { + return "pre"; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public Object run() { + return null; + } + + @Override + public int filterOrder() { + return 0; + } + }; + } + + public static void main(String[] args) { + SpringApplication.run(SampleZuulProxyApplication.class, args); + } + +}