Commit 75addf04 by Spencer Gibb

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
parent 7c4e3100
......@@ -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.
......@@ -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();
}
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 {
}
package org.springframework.cloud.netflix.zuul;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author Spencer Gibb
*/
public class Routes {
private static final Logger logger = LoggerFactory.getLogger(Routes.class);
@Slf4j
public class RouteLocator implements ApplicationListener<EnvironmentChangeEvent> {
public static final String DEFAULT_ROUTE = "/";
@Autowired
ConfigurableEnvironment env;
protected ConfigurableEnvironment env;
@Autowired
protected DiscoveryClient discovery;
@Autowired
protected ZuulProperties properties;
private Field propertySourcesField;
private String keyPrefix;
private AtomicReference<LinkedHashMap<String, String>> routes = new AtomicReference<>();
public Routes(String keyPrefix) {
this.keyPrefix = keyPrefix;
public RouteLocator() {
initField();
}
......@@ -35,26 +42,52 @@ public class Routes {
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<>();
@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, routes);
getRoutes(propertySource, routesMap);
}
String defaultServiceId = routes.get(DEFAULT_ROUTE);
String defaultServiceId = routesMap.get(DEFAULT_ROUTE);
if (defaultServiceId != null) {
//move the defaultServiceId to the end
routes.remove(DEFAULT_ROUTE);
routes.put(DEFAULT_ROUTE, defaultServiceId);
routesMap.remove(DEFAULT_ROUTE);
routesMap.put(DEFAULT_ROUTE, defaultServiceId);
}
return routes;
return routesMap;
}
public void getRoutes(PropertySource<?> propertySource, LinkedHashMap<String, String> routes) {
protected void getRoutes(PropertySource<?> propertySource, Map<String, String> routes) {
if (propertySource instanceof CompositePropertySource) {
try {
@SuppressWarnings("unchecked")
......@@ -69,13 +102,13 @@ public class Routes {
//EnumerablePropertySource enumerable = (EnumerablePropertySource) propertySource;
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(propertySource);
Map<String, Object> routeEntries = PropertySourceUtils.getSubProperties(propertySources, keyPrefix);
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)) {
logger.warn("Overwriting route {}: already defined by {}", route, routes.get(route));
log.warn("Overwriting route {}: already defined by {}", route, routes.get(route));
}
routes.put(route, serviceId);
}
......
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()+"/*");
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
......
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();
}
}
}
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);
}
}
}
}
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();
}
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();
}
}
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);
}
});
......
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 +"/**";
}
}
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: /
<?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>
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();
}
}
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;
}
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);
}
}
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() {
}
}
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment