From 75addf04d3a49ac10b576801e01e4f63eca73c51 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 25 Nov 2014 15:48:17 -0700 Subject: [PATCH] change zuul implementation to be controller/handler mapping based. This allows mappings to be at the root and not have to be prefixed. It also allows mappings to fall through to other handler mappings. Patterns are now Ant-style via AntPathMatcher. fixes gh-72 --- .../main/asciidoc/spring-cloud-netflix.adoc | 30 +---- pom.xml | 15 ++- spring-cloud-netflix-core/pom.xml | 6 - .../client/discovery/DiscoveryClient.java | 4 + .../netflix/eureka/EurekaDiscoveryClient.java | 29 +++++ .../cloud/netflix/zuul/EnableZuulProxy.java | 3 +- .../cloud/netflix/zuul/EnableZuulServer.java | 3 +- .../cloud/netflix/zuul/RouteLocator.java | 117 ++++++++++++++++++ .../cloud/netflix/zuul/Routes.java | 84 ------------- ...figuration.java => ZuulConfiguration.java} | 39 +++--- .../cloud/netflix/zuul/ZuulController.java | 30 +++++ .../netflix/zuul/ZuulHandlerMapping.java | 65 ++++++++++ .../cloud/netflix/zuul/ZuulProperties.java | 19 ++- .../netflix/zuul/ZuulProxyConfiguration.java | 55 -------- .../netflix/zuul/ZuulProxyProperties.java | 16 --- .../zuul/filters/pre/PreDecorationFilter.java | 19 +-- .../cloud/netflix/zuul/RouteLocatorTests.java | 71 +++++++++++ .../zuul/sample/ZuulProxyApplication.java | 14 +-- .../src/test/resources/application.yml | 21 ++-- spring-cloud-netflix-zuul-server/pom.xml | 77 ------------ .../netflix/zuul/ZuulServerConfiguration.java | 54 -------- .../netflix/zuul/ZuulServerProperties.java | 16 --- .../netflix/zuul/ZuulServerApplication.java | 16 --- .../zuul/ZuulServerApplicationTests.java | 20 --- .../src/test/resources/application.yml | 18 --- 25 files changed, 402 insertions(+), 439 deletions(-) rename {spring-cloud-netflix-zuul-server => spring-cloud-netflix-core}/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulServer.java (80%) create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RouteLocator.java delete mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/Routes.java rename spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/{AbstractZuulConfiguration.java => ZuulConfiguration.java} (64%) create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulController.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java delete mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java delete mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyProperties.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/RouteLocatorTests.java delete mode 100644 spring-cloud-netflix-zuul-server/pom.xml delete mode 100644 spring-cloud-netflix-zuul-server/src/main/java/org/springframework/cloud/netflix/zuul/ZuulServerConfiguration.java delete mode 100644 spring-cloud-netflix-zuul-server/src/main/java/org/springframework/cloud/netflix/zuul/ZuulServerProperties.java delete mode 100644 spring-cloud-netflix-zuul-server/src/test/java/org/springframework/cloud/netflix/zuul/ZuulServerApplication.java delete mode 100644 spring-cloud-netflix-zuul-server/src/test/java/org/springframework/cloud/netflix/zuul/ZuulServerApplicationTests.java delete mode 100644 spring-cloud-netflix-zuul-server/src/test/resources/application.yml diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 310d8126..6ab5d541 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -500,33 +500,15 @@ Zuul's rule engine allows rules and filters to be written in essentially any JVM [[netflix-zuul-reverse-proxy]] === Embedded Zuul Reverse Proxy -Spring Cloud has created an embedded Zuul proxy to ease the development of a very common use case where a UI application wants to proxy calls to one or more back end services. To enable it, annotate a Spring Boot main class with `@EnableZuulProxy`. This forwards local calls to `/proxy/*` to the appropriate service. The proxy uses Ribbon to locate an instance to forward to via Eureka. Forwarding to the service is protected by a Hystrix circuit breaker. Rules are configured via the Spring environment. The Config Server is an ideal place for the Zuul configuration. Zuul Embedded Proxy configuration rules look like the following: +Spring Cloud has created an embedded Zuul proxy to ease the development of a very common use case where a UI application wants to proxy calls to one or more back end services. To enable it, annotate a Spring Boot main class with `@EnableZuulProxy`. This forwards local calls to the appropriate service. By convention, a service with the `spring.application.name` of `users`, will receive requests from the proxy located at `/users`. The proxy uses Ribbon to locate an instance to forward to via Eureka. To skip having a service automatically added from eureka, set `zuul.ignored-services = service1`. Forwarding to the service is protected by a Hystrix circuit breaker. Additional rules can be configured via the Spring environment. The Config Server is an ideal place for the Zuul configuration. Zuul Embedded Proxy configuration rules look like the following: - zuul.proxy.route.users: /users + zuul.route.users: /myusers/** -This means that http calls to /proxy/users get forwarded to the users service. This proxy configuration is useful for services that host a user interface to proxy to the backend services it requires. To change the mapping, set `zuul.proxy.mapping` to a different value, such as `/api`. By default, the proxy mapping gets stripped from the request before forwarding. To keep the proxy mapping on the forwarded call, set `zuul.proxy.stripMapping = false`. +This means that http calls to /myusers get forwarded to the users service. This proxy configuration is useful for services that host a user interface to proxy to the backend services it requires. To add a prefix to the mapping, set `zuul.proxy.mapping` to a value, such as `/api`. To strip the proxy mapping from the request before forwarding set `zuul.proxy.strip-mapping = true`. -To have the `X-Forwarded-Host` header added to the forwarded requests, set `zuul.proxy.addProxyHeaders = true`. +The Zuul proxy supports Ant-style patterns. -[[netflix-zuul-server]] -=== Standalone Zuul Server +The `X-Forwarded-Host` header added to the forwarded requests by default. To turn it off set `zuul.proxy.add-proxy-headers = false`. -Spring Cloud has created a standalone Zuul server. To enable it, annotate a Spring Boot main class with `@EnableZuulServer`. This routes all calls to the appropriate service. The server uses Ribbon to locate an instance to forward to via Eureka. Forwarding to the service is protected by a Hystrix circuit breaker. Rules are configured via the Spring environment. The Config Server is an ideal place for the Zuul configuration. Zuul Server configuration rules look like the following: - - zuul.server.route.users: /users - -This means that http calls to /users get forwarded to the users service. - -Since zuul, by default, intercepts all requests (`/*`), to enable actuator, you should set the `management.port`. - -To use the Spring Boot error facilities while using the standalone Zuul server, please set the following properties - ----- -# moves the Spring Dispatch Servlet to a path below `/` -server: - servletPath: /app -# sets the error path to use the Dispatch Servlet to resolve the error view -error: - path: ${server.servletPath}/error ----- +An application with the `@EnableZuulProxy` could act as a standalone server if you set a default route, for example `zuul.route.home: /` would route `/` to the home service. diff --git a/pom.xml b/pom.xml index 1c70ea95..dd8ec313 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,6 @@ spring-cloud-netflix-hystrix-dashboard spring-cloud-netflix-eureka-server spring-cloud-netflix-turbine - spring-cloud-netflix-zuul-server docs @@ -254,6 +253,20 @@ + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.7 + 1.7 + + + + + 0.6.3 1.1.145 diff --git a/spring-cloud-netflix-core/pom.xml b/spring-cloud-netflix-core/pom.xml index fd98c9c7..1f932025 100644 --- a/spring-cloud-netflix-core/pom.xml +++ b/spring-cloud-netflix-core/pom.xml @@ -43,12 +43,6 @@ com.netflix.eureka eureka-client true - - - netflix-eventbus - com.netflix.netflix-commons - - com.netflix.eureka diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java index b17b3cf5..2cd7da29 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java @@ -2,6 +2,8 @@ package org.springframework.cloud.client.discovery; import org.springframework.cloud.client.ServiceInstance; +import java.util.List; + /** * @author Spencer Gibb */ @@ -10,4 +12,6 @@ public interface DiscoveryClient { * @return ServiceInstance with information used to register the local service */ public ServiceInstance getLocalServiceInstance(); + + public List getServices(); } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaDiscoveryClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaDiscoveryClient.java index 308b491b..05aef595 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaDiscoveryClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka/EurekaDiscoveryClient.java @@ -1,9 +1,20 @@ package org.springframework.cloud.netflix.eureka; +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.discovery.shared.Application; +import com.netflix.discovery.shared.Applications; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; + +import static com.google.common.collect.Iterables.*; + /** * @author Spencer Gibb */ @@ -12,6 +23,9 @@ public class EurekaDiscoveryClient implements DiscoveryClient { @Autowired private EurekaInstanceConfigBean config; + @Autowired + private com.netflix.discovery.DiscoveryClient discovery; + @Override public ServiceInstance getLocalServiceInstance() { return new ServiceInstance() { @@ -31,4 +45,19 @@ public class EurekaDiscoveryClient implements DiscoveryClient { } }; } + + @Override + public List getServices() { + Applications applications = discovery.getApplications(); + if (applications == null) { + return Collections.emptyList(); + } + return Lists.newArrayList(transform(applications.getRegisteredApplications(), new Function() { + @Nullable + @Override + public String apply(@Nullable Application app) { + return app.getName().toLowerCase(); + } + })); + } } 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 5e4a3dc8..b0e095a4 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 @@ -13,7 +13,6 @@ import org.springframework.cloud.netflix.hystrix.EnableHystrix; @EnableEurekaClient @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented -@Import(ZuulProxyConfiguration.class) +@Import(ZuulConfiguration.class) public @interface EnableZuulProxy { } diff --git a/spring-cloud-netflix-zuul-server/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 similarity index 80% rename from spring-cloud-netflix-zuul-server/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulServer.java rename to spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/EnableZuulServer.java index dede28d5..f4a1f361 100644 --- a/spring-cloud-netflix-zuul-server/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 @@ -8,12 +8,13 @@ import java.lang.annotation.*; /** * @author Spencer Gibb + * @deprecated @see org.springframework.cloud.netflix.zuul.EnableZuulProxy */ @EnableHystrix @EnableEurekaClient @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@Import(ZuulServerConfiguration.class) +@Import(ZuulConfiguration.class) public @interface EnableZuulServer { } 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..879808eb --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RouteLocator.java @@ -0,0 +1,117 @@ +package org.springframework.cloud.netflix.zuul; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.bind.PropertySourceUtils; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.*; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Spencer Gibb + */ +@Slf4j +public class RouteLocator implements ApplicationListener { + + public static final String DEFAULT_ROUTE = "/"; + + @Autowired + protected ConfigurableEnvironment env; + + @Autowired + protected DiscoveryClient discovery; + + @Autowired + protected ZuulProperties properties; + + private Field propertySourcesField; + private AtomicReference> routes = new AtomicReference<>(); + + public RouteLocator() { + initField(); + } + + private void initField() { + propertySourcesField = ReflectionUtils.findField(CompositePropertySource.class, "propertySources"); + propertySourcesField.setAccessible(true); + } + + @Override + public void onApplicationEvent(EnvironmentChangeEvent event) { + for (String key : event.getKeys()) { + if (key.startsWith(properties.getRoutePrefix())) { + routes.set(locateRoutes()); + return; + } + } + } + + //TODO: respond to changes in eureka + public Map getRoutes() { + if (routes.get() == null) { + routes.set(locateRoutes()); + } + + return routes.get(); + } + + protected LinkedHashMap locateRoutes() { + LinkedHashMap routesMap = new LinkedHashMap<>(); + + //Add routes for discovery services by default + List services = discovery.getServices(); + for (String serviceId : services) { + //Ignore specified services + if (!properties.getIgnoredServices().contains(serviceId)) + routesMap.put("/" + serviceId + "/**", serviceId); + } + + MutablePropertySources propertySources = env.getPropertySources(); + for (PropertySource propertySource : propertySources) { + getRoutes(propertySource, routesMap); + } + + String defaultServiceId = routesMap.get(DEFAULT_ROUTE); + + if (defaultServiceId != null) { + //move the defaultServiceId to the end + routesMap.remove(DEFAULT_ROUTE); + routesMap.put(DEFAULT_ROUTE, defaultServiceId); + } + return routesMap; + } + + protected void getRoutes(PropertySource propertySource, Map routes) { + if (propertySource instanceof CompositePropertySource) { + try { + @SuppressWarnings("unchecked") + Set> sources = (Set>) propertySourcesField.get(propertySource); + for (PropertySource source : sources) { + getRoutes(source, routes); + } + } catch (IllegalAccessException e) { + return; + } + } else { + //EnumerablePropertySource enumerable = (EnumerablePropertySource) propertySource; + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addLast(propertySource); + Map routeEntries = PropertySourceUtils.getSubProperties(propertySources, properties.getRoutePrefix()); + for (Map.Entry entry : routeEntries.entrySet()) { + String serviceId = entry.getKey(); + String route = entry.getValue().toString(); + + if (routes.containsKey(route)) { + log.warn("Overwriting route {}: already defined by {}", route, routes.get(route)); + } + routes.put(route, serviceId); + } + } + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/Routes.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/Routes.java deleted file mode 100644 index c0cf9d23..00000000 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/Routes.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.bind.PropertySourceUtils; -import org.springframework.core.env.*; -import org.springframework.util.ReflectionUtils; - -import java.lang.reflect.Field; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -/** - * @author Spencer Gibb - */ -public class Routes { - private static final Logger logger = LoggerFactory.getLogger(Routes.class); - - public static final String DEFAULT_ROUTE = "/"; - - @Autowired - ConfigurableEnvironment env; - private Field propertySourcesField; - private String keyPrefix; - - public Routes(String keyPrefix) { - this.keyPrefix = keyPrefix; - initField(); - } - - private void initField() { - propertySourcesField = ReflectionUtils.findField(CompositePropertySource.class, "propertySources"); - propertySourcesField.setAccessible(true); - } - - //TODO: cache routes or respond to environment event and refresh all routes - public LinkedHashMap getRoutes() { - LinkedHashMap routes = new LinkedHashMap<>(); - MutablePropertySources propertySources = env.getPropertySources(); - for (PropertySource propertySource : propertySources) { - getRoutes(propertySource, routes); - } - - String defaultServiceId = routes.get(DEFAULT_ROUTE); - - if (defaultServiceId != null) { - //move the defaultServiceId to the end - routes.remove(DEFAULT_ROUTE); - routes.put(DEFAULT_ROUTE, defaultServiceId); - } - - return routes; - } - - public void getRoutes(PropertySource propertySource, LinkedHashMap routes) { - if (propertySource instanceof CompositePropertySource) { - try { - @SuppressWarnings("unchecked") - Set> sources = (Set>) propertySourcesField.get(propertySource); - for (PropertySource source : sources) { - getRoutes(source, routes); - } - } catch (IllegalAccessException e) { - return; - } - } else { - //EnumerablePropertySource enumerable = (EnumerablePropertySource) propertySource; - MutablePropertySources propertySources = new MutablePropertySources(); - propertySources.addLast(propertySource); - Map routeEntries = PropertySourceUtils.getSubProperties(propertySources, keyPrefix); - for (Map.Entry entry : routeEntries.entrySet()) { - String serviceId = entry.getKey(); - String route = entry.getValue().toString(); - - if (routes.containsKey(route)) { - logger.warn("Overwriting route {}: already defined by {}", route, routes.get(route)); - } - routes.put(route, serviceId); - } - } - } -} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/AbstractZuulConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java similarity index 64% rename from spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/AbstractZuulConfiguration.java rename to spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java index edff3cf8..95b0f497 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/AbstractZuulConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java @@ -1,11 +1,11 @@ package org.springframework.cloud.netflix.zuul; -import com.netflix.zuul.context.ContextLifecycleFilter; import com.netflix.zuul.http.ZuulServlet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.trace.TraceRepository; -import org.springframework.boot.context.embedded.FilterRegistrationBean; -import org.springframework.boot.context.embedded.ServletRegistrationBean; +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.netflix.zuul.filters.post.SendErrorFilter; import org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter; import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter; @@ -13,39 +13,38 @@ 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.context.annotation.Bean; - -import java.util.ArrayList; -import java.util.Collection; +import org.springframework.context.annotation.Configuration; /** * @author Spencer Gibb */ -public abstract class AbstractZuulConfiguration { +@Configuration +@EnableConfigurationProperties() +@ConditionalOnClass(ZuulServlet.class) +@ConditionalOnExpression("${zuul.enabled:true}") +public class ZuulConfiguration { @Autowired(required=false) private TraceRepository traces; - protected abstract ZuulProperties getProperties(); - @Bean - public Routes routes() { - return new Routes(getProperties().getRoutePrefix()); + public ZuulProperties zuulProperties() { + return new ZuulProperties(); } @Bean - public ServletRegistrationBean zuulServlet() { - return new ServletRegistrationBean(new ZuulServlet(), getProperties().getMapping()+"/*"); + public RouteLocator routes(){ + return new RouteLocator(); } @Bean - public FilterRegistrationBean contextLifecycleFilter() { - Collection urlPatterns = new ArrayList<>(); - urlPatterns.add(getProperties().getMapping()+"/*"); - - FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new ContextLifecycleFilter()); - filterRegistrationBean.setUrlPatterns(urlPatterns); + public ZuulController zuulController() { + return new ZuulController(); + } - return filterRegistrationBean; + @Bean + public ZuulHandlerMapping zuulHandlerMapping() { + return new ZuulHandlerMapping(); } @Bean diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulController.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulController.java new file mode 100644 index 00000000..82905929 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulController.java @@ -0,0 +1,30 @@ +package org.springframework.cloud.netflix.zuul; + +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.http.ZuulServlet; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.ServletWrappingController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author Spencer Gibb + */ +public class ZuulController extends ServletWrappingController { + + public ZuulController() { + setServletClass(ZuulServlet.class); + setServletName("zuul"); + } + + @Override + protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { + try { + return super.handleRequestInternal(request, response); + } finally { + // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter + RequestContext.getCurrentContext().unset(); + } + } +} 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 new file mode 100644 index 00000000..d9fa994f --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulHandlerMapping.java @@ -0,0 +1,65 @@ +package org.springframework.cloud.netflix.zuul; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; + +import javax.annotation.PostConstruct; +import java.util.Map; + +/** + * @author Spencer Gibb + */ +@Slf4j +public class ZuulHandlerMapping extends AbstractUrlHandlerMapping implements ApplicationListener { + + @Autowired + protected RouteLocator routeLocator; + + @Autowired + protected ZuulController zuul; + + @Autowired + protected ZuulProperties properties; + + public ZuulHandlerMapping() { + setOrder(-200); + } + + @PostConstruct + public void init() { + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + registerHandlers(routeLocator.getRoutes()); + } + + private void registerHandlers(Map routes) { + if (routes.isEmpty()) { + logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping"); + } + else { + for (Map.Entry entry : routes.entrySet()) { + String url = entry.getKey(); + // Prepend with slash if not already present. + if (!url.startsWith("/")) { + url = "/" + url; + } + + if (StringUtils.hasText(properties.getRoutePrefix())) { + url = properties.getMapping()+url; + if (!url.startsWith("/")) { + url = "/" + url; + } + } + + registerHandler(url, zuul); + } + } + } + +} 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 837715e7..def65ab8 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 @@ -1,11 +1,20 @@ package org.springframework.cloud.netflix.zuul; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Collections; +import java.util.List; + /** * @author Spencer Gibb */ -public interface ZuulProperties { - public String getMapping(); - public boolean isStripMapping(); - public String getRoutePrefix(); - public boolean isAddProxyHeaders(); +@Data +@ConfigurationProperties("zuul") +public class ZuulProperties { + private String mapping = ""; + private boolean stripMapping = false; + private String routePrefix = "zuul.route."; + private boolean addProxyHeaders = true; + private List ignoredServices = Collections.emptyList(); } 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 deleted file mode 100644 index b2802c1b..00000000 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.netflix.zuul.http.ZuulServlet; - -/** - * @author Spencer Gibb - */ -@Configuration -@EnableConfigurationProperties(ZuulProxyProperties.class) -@ConditionalOnClass(ZuulServlet.class) -@ConditionalOnExpression("${zuul.proxy.enabled:true}") -public class ZuulProxyConfiguration extends AbstractZuulConfiguration { - - @Bean - public ZuulProxyProperties zuulProxyProperties() { - return new ZuulProxyProperties(); - } - - @Bean - //ZuulProxyProperties doesn't implement ZuulProperties so there are not 2 implementations (see ZuulServerConfiguration - public ZuulProperties zuulProperties() { - return new ZuulProperties() { - @Override - public String getMapping() { - return zuulProxyProperties().getMapping(); - } - - @Override - public boolean isStripMapping() { - return zuulProxyProperties().isStripMapping(); - } - - @Override - public String getRoutePrefix() { - return zuulProxyProperties().getRoutePrefix(); - } - - @Override - public boolean isAddProxyHeaders() { - return zuulProxyProperties().isAddProxyHeaders(); - } - }; - } - - @Override - protected ZuulProperties getProperties() { - return zuulProperties(); - } -} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyProperties.java deleted file mode 100644 index 81af0265..00000000 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyProperties.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * @author Spencer Gibb - */ -@Data -@ConfigurationProperties("zuul.proxy") -public class ZuulProxyProperties { - private String mapping = "/proxy"; - private boolean stripMapping = true; // this is currently the default behaviour - private String routePrefix = "zuul.proxy.route."; - private boolean addProxyHeaders = false; // TODO: current CF demo's rely on this -} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java index 5c449dc6..217fb392 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java @@ -2,28 +2,33 @@ package org.springframework.cloud.netflix.zuul.filters.pre; import com.google.common.base.Optional; import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.netflix.zuul.Routes; +import org.springframework.cloud.netflix.zuul.RouteLocator; import org.springframework.cloud.netflix.zuul.ZuulProperties; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; import javax.annotation.Nullable; import javax.servlet.http.HttpServletResponse; -import java.util.LinkedHashMap; +import java.util.Map; + +import static com.google.common.collect.Iterables.*; public class PreDecorationFilter extends ZuulFilter { private static Logger LOG = LoggerFactory.getLogger(PreDecorationFilter.class); @Autowired - private Routes routes; + private RouteLocator routeLocator; @Autowired private ZuulProperties properties; + private PathMatcher pathMatcher = new AntPathMatcher(); + @Override public int filterOrder() { return 5; @@ -55,12 +60,12 @@ public class PreDecorationFilter extends ZuulFilter { } ctx.put("requestURI", uriPart); - LinkedHashMap routesMap = routes.getRoutes(); + Map routesMap = routeLocator.getRoutes(); - Optional route = Iterables.tryFind(routesMap.keySet(), new Predicate() { + Optional route = tryFind(routesMap.keySet(), new Predicate() { @Override public boolean apply(@Nullable String path) { - return uriPart.startsWith(path); + return pathMatcher.match(path, uriPart); } }); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/RouteLocatorTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/RouteLocatorTests.java new file mode 100644 index 00000000..ed63a947 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/RouteLocatorTests.java @@ -0,0 +1,71 @@ +package org.springframework.cloud.netflix.zuul; + +import com.google.common.collect.Lists; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.mock.env.MockPropertySource; + +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.initMocks; + +/** + * @author Spencer Gibb + */ +public class RouteLocatorTests { + + public static final String IGNOREDSERVICE = "ignoredservice"; + public static final String ASERVICE = "aservice"; + public static final String MYSERVICE = "myservice"; + @Mock + ConfigurableEnvironment env; + + @Mock + DiscoveryClient discovery; + + + @Before + public void init() { + initMocks(this); + } + + @Test + public void testGetRoutes() { + RouteLocator routeLocator = new RouteLocator(); + routeLocator.properties = new ZuulProperties(); + routeLocator.properties.setIgnoredServices(Lists.newArrayList(IGNOREDSERVICE)); + routeLocator.discovery = this.discovery; + routeLocator.env = this.env; + + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MockPropertySource().withProperty("zuul.route."+ ASERVICE, getMapping(ASERVICE))); + when(env.getPropertySources()).thenReturn(propertySources); + when(discovery.getServices()).thenReturn(Lists.newArrayList(MYSERVICE, IGNOREDSERVICE)); + + Map routesMap = routeLocator.getRoutes(); + + assertNotNull("routesMap was null", routesMap); + assertFalse("routesMap was empty", routesMap.isEmpty()); + assertMapping(routesMap, MYSERVICE); + assertMapping(routesMap, ASERVICE); + + String serviceId = routesMap.get(getMapping(IGNOREDSERVICE)); + assertNull("routes did not ignore "+IGNOREDSERVICE, serviceId); + } + + protected void assertMapping(Map routesMap, String expectedServiceId) { + String mapping = getMapping(expectedServiceId); + String serviceId = routesMap.get(mapping); + assertEquals("routesMap had wrong value for "+mapping, expectedServiceId, serviceId); + } + + private String getMapping(String serviceId) { + return "/"+ serviceId +"/**"; + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/sample/ZuulProxyApplication.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/sample/ZuulProxyApplication.java index 56814652..cb6c46ed 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/sample/ZuulProxyApplication.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/sample/ZuulProxyApplication.java @@ -1,18 +1,16 @@ package org.springframework.cloud.netflix.zuul.sample; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Configuration -@ComponentScan -@EnableAutoConfiguration +@SpringBootApplication @RestController @EnableZuulProxy +@EnableEurekaClient public class ZuulProxyApplication { @RequestMapping("/testing123") @@ -26,7 +24,7 @@ public class ZuulProxyApplication { } public static void main(String[] args) { - new SpringApplicationBuilder(ZuulProxyApplication.class).web(true).run(args); + SpringApplication.run(ZuulProxyApplication.class, args); } } diff --git a/spring-cloud-netflix-core/src/test/resources/application.yml b/spring-cloud-netflix-core/src/test/resources/application.yml index d87e656d..39d6078b 100644 --- a/spring-cloud-netflix-core/src/test/resources/application.yml +++ b/spring-cloud-netflix-core/src/test/resources/application.yml @@ -6,13 +6,16 @@ spring: eureka: server: enabled: false -error: - path: /myerror +#error: +# path: /myerror +management: + context-path: /admin +endpoints: + health: + sensitive: false zuul: - proxy: - mapping: /api - stripMapping: true - route: - testclient: /testing123 - stores: /stores - customers: /customers + #mapping: /api + #strip-mapping: true + route: + testclient: /testing123 + #stores: / diff --git a/spring-cloud-netflix-zuul-server/pom.xml b/spring-cloud-netflix-zuul-server/pom.xml deleted file mode 100644 index c81fe7d8..00000000 --- a/spring-cloud-netflix-zuul-server/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - 4.0.0 - spring-cloud-netflix-zuul-server - jar - Spring Cloud Netflix Zuul Server - http://projects.spring.io/spring-cloud/ - - org.springframework.cloud - spring-cloud-netflix - 1.0.0.BUILD-SNAPSHOT - .. - - - - - - - - - - - - - org.springframework.cloud - spring-cloud-netflix-core - - - com.netflix.eureka - eureka-client - - - com.netflix.hystrix - hystrix-core - - - com.netflix.hystrix - hystrix-metrics-event-stream - - - com.netflix.hystrix - hystrix-javanica - - - com.netflix.ribbon - ribbon - - - com.netflix.ribbon - ribbon-core - - - com.netflix.ribbon - ribbon-eureka - - - com.netflix.ribbon - ribbon-httpclient - - - com.netflix.zuul - zuul-core - - - org.projectlombok - lombok - - provided - - - org.springframework.boot - spring-boot-starter-test - test - - - diff --git a/spring-cloud-netflix-zuul-server/src/main/java/org/springframework/cloud/netflix/zuul/ZuulServerConfiguration.java b/spring-cloud-netflix-zuul-server/src/main/java/org/springframework/cloud/netflix/zuul/ZuulServerConfiguration.java deleted file mode 100644 index 9b22b0e0..00000000 --- a/spring-cloud-netflix-zuul-server/src/main/java/org/springframework/cloud/netflix/zuul/ZuulServerConfiguration.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import com.netflix.zuul.http.ZuulServlet; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * @author Spencer Gibb - */ -@Configuration -@ConditionalOnClass(ZuulServlet.class) -@EnableConfigurationProperties(ZuulServerProperties.class) -@ConditionalOnExpression("${zuul.server.enabled:true}") -public class ZuulServerConfiguration extends AbstractZuulConfiguration { - - @Bean - public ZuulServerProperties zuulServerProperties() { - return new ZuulServerProperties(); - } - - @Bean - //ZuulServerProperties doesn't implement ZuulProperties so there are not 2 implementations (see ZuulProxyConfiguration - public ZuulProperties zuulProperties() { - return new ZuulProperties() { - @Override - public String getMapping() { - return zuulServerProperties().getMapping(); - } - - @Override - public boolean isStripMapping() { - return zuulServerProperties().isStripMapping(); - } - - @Override - public String getRoutePrefix() { - return zuulServerProperties().getRoutePrefix(); - } - - @Override - public boolean isAddProxyHeaders() { - return zuulServerProperties().isAddProxyHeaders(); - } - }; - } - - @Override - protected ZuulProperties getProperties() { - return zuulProperties(); - } -} diff --git a/spring-cloud-netflix-zuul-server/src/main/java/org/springframework/cloud/netflix/zuul/ZuulServerProperties.java b/spring-cloud-netflix-zuul-server/src/main/java/org/springframework/cloud/netflix/zuul/ZuulServerProperties.java deleted file mode 100644 index 01d2bebd..00000000 --- a/spring-cloud-netflix-zuul-server/src/main/java/org/springframework/cloud/netflix/zuul/ZuulServerProperties.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * @author Spencer Gibb - */ -@Data -@ConfigurationProperties("zuul.server") -public class ZuulServerProperties { - private String mapping = ""; - private boolean stripMapping = false; - private String routePrefix = "zuul.server.route."; - private boolean addProxyHeaders = true; -} diff --git a/spring-cloud-netflix-zuul-server/src/test/java/org/springframework/cloud/netflix/zuul/ZuulServerApplication.java b/spring-cloud-netflix-zuul-server/src/test/java/org/springframework/cloud/netflix/zuul/ZuulServerApplication.java deleted file mode 100644 index 5c9c105d..00000000 --- a/spring-cloud-netflix-zuul-server/src/test/java/org/springframework/cloud/netflix/zuul/ZuulServerApplication.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableAutoConfiguration -@EnableZuulServer -public class ZuulServerApplication { - - public static void main(String[] args) { - new SpringApplicationBuilder(ZuulServerApplication.class).web(true).run(args); - } - -} diff --git a/spring-cloud-netflix-zuul-server/src/test/java/org/springframework/cloud/netflix/zuul/ZuulServerApplicationTests.java b/spring-cloud-netflix-zuul-server/src/test/java/org/springframework/cloud/netflix/zuul/ZuulServerApplicationTests.java deleted file mode 100644 index 55b69182..00000000 --- a/spring-cloud-netflix-zuul-server/src/test/java/org/springframework/cloud/netflix/zuul/ZuulServerApplicationTests.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.springframework.cloud.netflix.zuul; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; - -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = ZuulServerApplication.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") -public class ZuulServerApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/spring-cloud-netflix-zuul-server/src/test/resources/application.yml b/spring-cloud-netflix-zuul-server/src/test/resources/application.yml deleted file mode 100644 index 6c06fc0f..00000000 --- a/spring-cloud-netflix-zuul-server/src/test/resources/application.yml +++ /dev/null @@ -1,18 +0,0 @@ -server: - port: 9876 - servletPath: /app -management: - port: 9877 -spring: - application: - name: testzuulserver -error: - path: ${server.servletPath}/error - -zuul: - server: - route: - testclient: /testing123 - stores: /stores - customers: /customers -