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
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
15
pom.xml
15
pom.xml
@@ -30,7 +30,6 @@
|
||||
<module>spring-cloud-netflix-hystrix-dashboard</module>
|
||||
<module>spring-cloud-netflix-eureka-server</module>
|
||||
<module>spring-cloud-netflix-turbine</module>
|
||||
<module>spring-cloud-netflix-zuul-server</module>
|
||||
<module>docs</module>
|
||||
</modules>
|
||||
|
||||
@@ -254,6 +253,20 @@
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<archaius.version>0.6.3</archaius.version>
|
||||
<eureka.version>1.1.145</eureka.version>
|
||||
|
||||
@@ -43,12 +43,6 @@
|
||||
<groupId>com.netflix.eureka</groupId>
|
||||
<artifactId>eureka-client</artifactId>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>netflix-eventbus</artifactId>
|
||||
<groupId>com.netflix.netflix-commons</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.eureka</groupId>
|
||||
|
||||
@@ -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<String> getServices();
|
||||
}
|
||||
|
||||
@@ -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<String> getServices() {
|
||||
Applications applications = discovery.getApplications();
|
||||
if (applications == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Lists.newArrayList(transform(applications.getRegisteredApplications(), new Function<Application, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable Application app) {
|
||||
return app.getName().toLowerCase();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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<EnvironmentChangeEvent> {
|
||||
|
||||
public static final String DEFAULT_ROUTE = "/";
|
||||
|
||||
@Autowired
|
||||
protected ConfigurableEnvironment env;
|
||||
|
||||
@Autowired
|
||||
protected DiscoveryClient discovery;
|
||||
|
||||
@Autowired
|
||||
protected ZuulProperties properties;
|
||||
|
||||
private Field propertySourcesField;
|
||||
private AtomicReference<LinkedHashMap<String, String>> 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<String, String> getRoutes() {
|
||||
if (routes.get() == null) {
|
||||
routes.set(locateRoutes());
|
||||
}
|
||||
|
||||
return routes.get();
|
||||
}
|
||||
|
||||
protected LinkedHashMap<String, String> locateRoutes() {
|
||||
LinkedHashMap<String, String> routesMap = new LinkedHashMap<>();
|
||||
|
||||
//Add routes for discovery services by default
|
||||
List<String> 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<String, String> routes) {
|
||||
if (propertySource instanceof CompositePropertySource) {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<PropertySource<?>> sources = (Set<PropertySource<?>>) 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<String, Object> routeEntries = PropertySourceUtils.getSubProperties(propertySources, properties.getRoutePrefix());
|
||||
for (Map.Entry<String, Object> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String, String> getRoutes() {
|
||||
LinkedHashMap<String, String> 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<String, String> routes) {
|
||||
if (propertySource instanceof CompositePropertySource) {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<PropertySource<?>> sources = (Set<PropertySource<?>>) 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<String, Object> routeEntries = PropertySourceUtils.getSubProperties(propertySources, keyPrefix);
|
||||
for (Map.Entry<String, Object> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> urlPatterns = new ArrayList<>();
|
||||
urlPatterns.add(getProperties().getMapping()+"/*");
|
||||
public ZuulController zuulController() {
|
||||
return new ZuulController();
|
||||
}
|
||||
|
||||
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new ContextLifecycleFilter());
|
||||
filterRegistrationBean.setUrlPatterns(urlPatterns);
|
||||
|
||||
return filterRegistrationBean;
|
||||
@Bean
|
||||
public ZuulHandlerMapping zuulHandlerMapping() {
|
||||
return new ZuulHandlerMapping();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ContextRefreshedEvent> {
|
||||
|
||||
@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<String, String> routes) {
|
||||
if (routes.isEmpty()) {
|
||||
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
|
||||
}
|
||||
else {
|
||||
for (Map.Entry<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String> ignoredServices = Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<String, String> routesMap = routes.getRoutes();
|
||||
Map<String, String> routesMap = routeLocator.getRoutes();
|
||||
|
||||
Optional<String> route = Iterables.tryFind(routesMap.keySet(), new Predicate<String>() {
|
||||
Optional<String> route = tryFind(routesMap.keySet(), new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable String path) {
|
||||
return uriPart.startsWith(path);
|
||||
return pathMatcher.match(path, uriPart);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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<String, String> 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<String, String> 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 +"/**";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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: /
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-netflix-zuul-server</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Netflix Zuul Server</name>
|
||||
<url>http://projects.spring.io/spring-cloud/</url>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.eureka</groupId>
|
||||
<artifactId>eureka-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-metrics-event-stream</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-javanica</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.ribbon</groupId>
|
||||
<artifactId>ribbon</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.ribbon</groupId>
|
||||
<artifactId>ribbon-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.ribbon</groupId>
|
||||
<artifactId>ribbon-eureka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.ribbon</groupId>
|
||||
<artifactId>ribbon-httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.zuul</groupId>
|
||||
<artifactId>zuul-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<!-- Only needed at compile time -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user