Commit e2e193ea by Spencer Gibb

added support for standalone zuul server with @EnableZuulServer.

Removed old zuul module. fixes gh-14
parent eba7f0da
......@@ -432,9 +432,20 @@ 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. 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 `/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:
zuul.proxy.route.users: /users
This means that http calls to /proxy/users to the users service. This proxy configuration is useful for services that host a user interface to proxy to the backend services it requires.
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. By default, the proxy mapping gets stripped from the request before forwarding.
[[netflix-zuul-server]]
=== Standalone Zuul Server
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`.
......@@ -30,7 +30,7 @@
<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</module>
<module>spring-cloud-netflix-zuul-server</module>
<module>docs</module>
</modules>
......
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.cloud.netflix.zuul.filters.post.SendResponseFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;
import org.springframework.context.annotation.Bean;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author Spencer Gibb
*/
public abstract class AbstractZuulConfiguration {
@Autowired(required=false)
private TraceRepository traces;
protected abstract ZuulProperties getProperties();
@Bean
public Routes routes() {
return new Routes(getProperties().getRoutePrefix());
}
@Bean
public ServletRegistrationBean zuulServlet() {
return new ServletRegistrationBean(new ZuulServlet(), getProperties().getMapping()+"/*");
}
@Bean
public FilterRegistrationBean contextLifecycleFilter() {
Collection<String> urlPatterns = new ArrayList<>();
urlPatterns.add(getProperties().getMapping()+"/*");
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new ContextLifecycleFilter());
filterRegistrationBean.setUrlPatterns(urlPatterns);
return filterRegistrationBean;
}
@Bean
public FilterInitializer zuulFilterInitializer() {
return new FilterInitializer();
}
// pre filters
@Bean
public DebugFilter debugFilter() {
return new DebugFilter();
}
@Bean
public PreDecorationFilter preDecorationFilter() {
return new PreDecorationFilter();
}
// route filters
@Bean
public RibbonRoutingFilter ribbonRoutingFilter() {
RibbonRoutingFilter filter = new RibbonRoutingFilter();
if (traces!=null) {
filter.setTraces(traces);
}
return filter;
}
// post filters
@Bean
public SendResponseFilter sendResponseFilter() {
return new SendResponseFilter();
}
}
......@@ -25,11 +25,6 @@ public class Routes {
private Field propertySourcesField;
private String keyPrefix;
public Routes() {
keyPrefix = "zuul.proxy.route.";
initField();
}
public Routes(String keyPrefix) {
this.keyPrefix = keyPrefix;
initField();
......
package org.springframework.cloud.netflix.zuul;
/**
* @author Spencer Gibb
*/
public interface ZuulProperties {
public String getMapping();
public boolean isStripMapping();
public String getRoutePrefix();
public boolean isAddProxyHeaders();
}
package org.springframework.cloud.netflix.zuul;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;
import com.netflix.zuul.context.ContextLifecycleFilter;
import com.netflix.zuul.http.ZuulServlet;
/**
......@@ -27,65 +15,41 @@ import com.netflix.zuul.http.ZuulServlet;
@EnableConfigurationProperties(ZuulProxyProperties.class)
@ConditionalOnClass(ZuulServlet.class)
@ConditionalOnExpression("${zuul.proxy.enabled:true}")
public class ZuulProxyConfiguration {
@Autowired
private ZuulProxyProperties props;
@Autowired(required=false)
private TraceRepository traces;
@Bean
public FilterRegistrationBean contextLifecycleFilter() {
Collection<String> urlPatterns = new ArrayList<>();
urlPatterns.add(props.getMapping()+"/*");
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new ContextLifecycleFilter());
filterRegistrationBean.setUrlPatterns(urlPatterns);
return filterRegistrationBean;
}
public class ZuulProxyConfiguration extends AbstractZuulConfiguration {
@Bean
public ServletRegistrationBean zuulServlet() {
return new ServletRegistrationBean(new ZuulServlet(), props.getMapping()+"/*");
public ZuulProxyProperties zuulProxyProperties() {
return new ZuulProxyProperties();
}
@Bean
Routes routes() {
return new Routes();
//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();
}
@Bean
FilterInitializer zuulFilterInitializer() {
return new FilterInitializer();
}
// pre filters
@Bean
public DebugFilter debugFilter() {
return new DebugFilter();
@Override
public boolean isStripMapping() {
return zuulProxyProperties().isStripMapping();
}
@Bean
public PreDecorationFilter preDecorationFilter() {
return new PreDecorationFilter();
@Override
public String getRoutePrefix() {
return zuulProxyProperties().getRoutePrefix();
}
// route filters
@Bean
public RibbonRoutingFilter ribbonRoutingFilter() {
RibbonRoutingFilter filter = new RibbonRoutingFilter();
if (traces!=null) {
filter.setTraces(traces);
@Override
public boolean isAddProxyHeaders() {
return zuulProxyProperties().isAddProxyHeaders();
}
return filter;
};
}
// post filters
@Bean
public SendResponseFilter sendResponseFilter() {
return new SendResponseFilter();
@Override
protected ZuulProperties getProperties() {
return zuulProperties();
}
}
......@@ -11,4 +11,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
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
}
......@@ -9,7 +9,7 @@ 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.ZuulProxyProperties;
import org.springframework.cloud.netflix.zuul.ZuulProperties;
import javax.annotation.Nullable;
import java.util.LinkedHashMap;
......@@ -21,7 +21,7 @@ public class PreDecorationFilter extends ZuulFilter {
private Routes routes;
@Autowired
private ZuulProxyProperties properties;
private ZuulProperties properties;
@Override
public int filterOrder() {
......@@ -71,6 +71,10 @@ public class PreDecorationFilter extends ZuulFilter {
ctx.set("serviceId", serviceId);
ctx.setRouteHost(null);
ctx.addOriginResponseHeader("X-Zuul-ServiceId", serviceId);
if (properties.isAddProxyHeaders()) {
ctx.addZuulRequestHeader("X-Forwarded-Host", ctx.getRequest().getServerName() + ":" + String.valueOf(ctx.getRequest().getServerPort()));
}
}
} else {
LOG.warn("No route found for uri: "+requestURI);
......
......@@ -4,7 +4,6 @@ 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.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;
......@@ -14,8 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
@EnableAutoConfiguration
@RestController
@EnableZuulProxy
@EnableEurekaClient
public class Application {
public class ZuulProxyApplication {
@RequestMapping("/testing123")
public String testing123() {
......@@ -28,7 +26,7 @@ public class Application {
}
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
new SpringApplicationBuilder(ZuulProxyApplication.class).web(true).run(args);
}
}
......@@ -8,10 +8,10 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@SpringApplicationConfiguration(classes = ZuulProxyApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
public class ApplicationTests {
public class ZuulProxyApplicationTests {
@Test
public void contextLoads() {
......
......@@ -10,8 +10,8 @@ eureka:
zuul:
proxy:
mapping: /api
stripMapping: false
stripMapping: true
route:
testclient: /api/testing123
stores: /api/stores
customers: /api/customers
testclient: /testing123
stores: /stores
customers: /customers
<?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">
<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</artifactId>
<artifactId>spring-cloud-netflix-zuul-server</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Netflix Zuul</name>
<name>Spring Cloud Netflix Zuul Server</name>
<url>http://projects.spring.io/spring-cloud/</url>
<parent>
<groupId>org.springframework.cloud</groupId>
......@@ -14,47 +15,20 @@
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<spring-cloud-config.version>1.0.0.BUILD-SNAPSHOT</spring-cloud-config.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>${spring-cloud-config.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
......@@ -65,6 +39,14 @@
<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>
......@@ -80,5 +62,16 @@
<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 org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author Spencer Gibb
*/
@EnableHystrix
@EnableEurekaClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ZuulServerConfiguration.class)
public @interface EnableZuulServer {
}
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
management:
port: 9877
spring:
application:
name: testzuulserver
zuul:
server:
route:
testclient: /testing123
stores: /stores
customers: /customers
package io.spring.cloud.netflix.zuul;
import com.netflix.zuul.context.ContextLifecycleFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.netflix.hystrix.HystrixStreamEndpoint;
import org.springframework.cloud.netflix.zuul.Routes;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.ArrayList;
import java.util.Collection;
/**
* User: spencergibb
* Date: 4/24/14
* Time: 8:57 PM
*/
@Configuration
@EnableAutoConfiguration
@EnableScheduling
@ComponentScan(basePackageClasses = Application.class)
public class Application {
@Bean
ZuulProperties routerProperties() {
return new ZuulProperties();
}
@Bean
public FilterRegistrationBean contextLifecycleFilter() {
Collection<String> urlPatterns = new ArrayList<>();
urlPatterns.add("/*");
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new ContextLifecycleFilter());
filterRegistrationBean.setUrlPatterns(urlPatterns);
return filterRegistrationBean;
}
//TODO: replace with auto config
@Bean
HystrixStreamEndpoint hystrixStreamEndpoint() {
return new HystrixStreamEndpoint();
}
@Bean
Routes routes() {
return new Routes();
}
@Bean
FilterIntializer filterIntializer() {
return new FilterIntializer();
}
@Bean
ZuulController zuulController() {
return new ZuulController();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
package io.spring.cloud.netflix.zuul;
import com.netflix.zuul.FilterFileManager;
import com.netflix.zuul.FilterLoader;
import com.netflix.zuul.groovy.GroovyCompiler;
import com.netflix.zuul.groovy.GroovyFileFilter;
import com.netflix.zuul.monitoring.MonitoringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* User: spencergibb
* Date: 4/24/14
* Time: 9:23 PM
* TODO: .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
*/
public class FilterIntializer implements ServletContextListener {
private static final Logger LOGGER = LoggerFactory.getLogger(FilterIntializer.class);
@Autowired
ZuulProperties props;
@Override
public void contextInitialized(ServletContextEvent sce) {
LOGGER.info("Starting filter initialzer context listener");
//FIXME: mocks monitoring infrastructure as we don't need it for this simple app
MonitoringHelper.initMocks();
// initializes groovy filesystem poller
initGroovyFilterManager();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
LOGGER.info("Stopping filter initializer context listener");
}
private void initGroovyFilterManager() {
//TODO: pluggable filter initialzer
FilterLoader.getInstance().setCompiler(new GroovyCompiler());
final String scriptRoot = props.getFilterRoot();
LOGGER.info("Using file system script: " + scriptRoot);
try {
FilterFileManager.setFilenameFilter(new GroovyFileFilter());
FilterFileManager.init(5,
scriptRoot + "/pre",
scriptRoot + "/route",
scriptRoot + "/post"
);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package io.spring.cloud.netflix.zuul;
import com.netflix.zuul.http.ZuulServlet;
import io.spring.cloud.util.AbstractServletWrappingController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* User: spencergibb
* Date: 4/24/14
* Time: 9:12 PM
*/
@Controller
public class ZuulController extends AbstractServletWrappingController {
public ZuulController() {
super(ZuulServlet.class, "zuulServlet");
}
@RequestMapping("/**")
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
return this.controller.handleRequest(request, response);
}
}
package io.spring.cloud.netflix.zuul;
import org.springframework.boot.context.properties.ConfigurationProperties;
import static java.util.concurrent.TimeUnit.MINUTES;
/**
* User: spencergibb
* Date: 5/2/14
* Time: 9:20 PM
*/
@ConfigurationProperties("zuul")
public class ZuulProperties {
private String filterRoot;
private long cacheRefresh = MINUTES.toMillis(5L);
public String getFilterRoot() {
return filterRoot;
}
public void setFilterRoot(String filterRoot) {
this.filterRoot = filterRoot;
}
public long getCacheRefresh() {
return cacheRefresh;
}
public void setCacheRefresh(long cacheRefresh) {
this.cacheRefresh = cacheRefresh;
}
}
package io.spring.cloud.util;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.mvc.ServletWrappingController;
import javax.servlet.ServletContext;
/**
* User: spencergibb
* Date: 5/1/14
* Time: 10:03 AM
*/
public abstract class AbstractServletWrappingController implements InitializingBean,
ApplicationContextAware, ServletContextAware {
protected final ServletWrappingController controller = new ServletWrappingController();
public AbstractServletWrappingController(Class<?> servletClass, String servletName) {
controller.setServletClass(servletClass);
controller.setServletName(servletName);
}
@Override
public void afterPropertiesSet() throws Exception {
this.controller.afterPropertiesSet();
}
@Override
public void setServletContext(ServletContext servletContext) {
this.controller.setServletContext(servletContext);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.controller.setApplicationContext(applicationContext);
}
}
#org.springframework.context.ApplicationListener=\
#org.springframework.cloud.netflix.eureka.EurekaStartingListener,\
#org.springframework.cloud.netflix.eureka.EurekaUpListener,\
#org.springframework.cloud.netflix.eureka.EurekaOutOfServiceListener
#org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
#org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration,\
#org.springframework.cloud.netflix.feign.FeignAutoConfiguration
info:
component: Platform Router (Zuul)
endpoints:
restart:
enabled: true
shutdown:
enabled: true
server:
port: 6080
management:
port: 6081
logging:
level: INFO
logging:
level:
com:
netflix: WARN
eureka:
client:
#Region where eureka is deployed -For AWS specify one of the AWS regions, for other datacenters specify a arbitrary string
#indicating the region.This is normally specified as a -D option (eg) -Deureka.region=us-east-1
region: default
#For eureka clients running in eureka server, it needs to connect to servers in other zones
preferSameZone: false
#Change this if you want to use a DNS based lookup for determining other eureka servers. For example
#of specifying the DNS entries, check the eureka-client-test.properties, eureka-client-prod.properties
#shouldUseDns: false
us-east-1:
availabilityZones: default
#serviceUrl:
#default: http://localhost:8761/eureka/
#defaultZone: http://localhost:8761/eureka/
instance:
#Virtual host name by which the clients identifies this service
virtualHostName: ${spring.application.name}
spring:
application:
name: zuul
platform:
config:
uri: http://localhost:${config.port:8888}
\ No newline at end of file
package filters.post
import com.netflix.config.DynamicBooleanProperty
import com.netflix.config.DynamicIntProperty
import com.netflix.config.DynamicPropertyFactory
import com.netflix.util.Pair
import com.netflix.zuul.ZuulFilter
import com.netflix.zuul.constants.ZuulConstants
import com.netflix.zuul.constants.ZuulHeaders
import com.netflix.zuul.context.Debug
import com.netflix.zuul.context.RequestContext
import javax.servlet.http.HttpServletResponse
import java.util.zip.GZIPInputStream
class SendResponseFilter extends ZuulFilter {
static DynamicBooleanProperty INCLUDE_DEBUG_HEADER =
DynamicPropertyFactory.getInstance().getBooleanProperty(ZuulConstants.ZUUL_INCLUDE_DEBUG_HEADER, false);
static DynamicIntProperty INITIAL_STREAM_BUFFER_SIZE =
DynamicPropertyFactory.getInstance().getIntProperty(ZuulConstants.ZUUL_INITIAL_STREAM_BUFFER_SIZE, 1024);
static DynamicBooleanProperty SET_CONTENT_LENGTH = DynamicPropertyFactory.getInstance().getBooleanProperty(ZuulConstants.ZUUL_SET_CONTENT_LENGTH, false);
@Override
String filterType() {
return 'post'
}
@Override
int filterOrder() {
return 1000
}
boolean shouldFilter() {
return !RequestContext.currentContext.getZuulResponseHeaders().isEmpty() ||
RequestContext.currentContext.getResponseDataStream() != null ||
RequestContext.currentContext.responseBody != null
}
Object run() {
addResponseHeaders()
writeResponse()
}
void writeResponse() {
RequestContext context = RequestContext.currentContext
// there is no body to send
if (context.getResponseBody() == null && context.getResponseDataStream() == null) return;
HttpServletResponse servletResponse = context.getResponse()
servletResponse.setCharacterEncoding("UTF-8")
OutputStream outStream = servletResponse.getOutputStream();
InputStream is = null
try {
if (RequestContext.currentContext.responseBody != null) {
String body = RequestContext.currentContext.responseBody
writeResponse(new ByteArrayInputStream(body.bytes), outStream)
return;
}
boolean isGzipRequested = false
final String requestEncoding = context.getRequest().getHeader(ZuulHeaders.ACCEPT_ENCODING)
if (requestEncoding != null && requestEncoding.equals("gzip"))
isGzipRequested = true;
is = context.getResponseDataStream();
InputStream inputStream = is
if (is != null) {
if (context.sendZuulResponse()) {
// if origin response is gzipped, and client has not requested gzip, decompress stream
// before sending to client
// else, stream gzip directly to client
if (context.getResponseGZipped() && !isGzipRequested)
try {
inputStream = new GZIPInputStream(is);
} catch (java.util.zip.ZipException e) {
println("gzip expected but not received assuming unencoded response" + RequestContext.currentContext.getRequest().getRequestURL().toString())
inputStream = is
}
else if (context.getResponseGZipped() && isGzipRequested)
servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING, "gzip")
writeResponse(inputStream, outStream)
}
}
} finally {
try {
is?.close();
outStream.flush()
outStream.close()
} catch (IOException e) {
}
}
}
def writeResponse(InputStream zin, OutputStream out) {
byte[] bytes = new byte[INITIAL_STREAM_BUFFER_SIZE.get()];
int bytesRead = -1;
while ((bytesRead = zin.read(bytes)) != -1) {
// if (Debug.debugRequest() && !Debug.debugRequestHeadersOnly()) {
// Debug.addRequestDebug("OUTBOUND: < " + new String(bytes, 0, bytesRead));
// }
try {
out.write(bytes, 0, bytesRead);
out.flush();
} catch (IOException e) {
//ignore
e.printStackTrace()
}
// doubles buffer size if previous read filled it
if (bytesRead == bytes.length) {
bytes = new byte[bytes.length * 2]
}
}
}
private void addResponseHeaders() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
String debugHeader = ""
List<String> rd
rd = (List<String>) RequestContext.getCurrentContext().get("routingDebug");
rd?.each {
debugHeader += "[[[${it}]]]";
}
/*
rd = (List<String>) RequestContext.getCurrentContext().get("requestDebug");
rd?.each {
debugHeader += "[[[REQUEST_DEBUG::${it}]]]";
}
*/
if (INCLUDE_DEBUG_HEADER.get()) servletResponse.addHeader("X-Zuul-Debug-Header", debugHeader)
if (Debug.debugRequest()) {
zuulResponseHeaders?.each { Pair<String, String> it ->
servletResponse.addHeader(it.first(), it.second())
Debug.addRequestDebug("OUTBOUND: < " + it.first() + ":" + it.second())
}
} else {
zuulResponseHeaders?.each { Pair<String, String> it ->
servletResponse.addHeader(it.first(), it.second())
}
}
RequestContext ctx = RequestContext.getCurrentContext()
Integer contentLength = ctx.getOriginContentLength()
// only inserts Content-Length if origin provides it and origin response is not gzipped
if (SET_CONTENT_LENGTH.get()) {
if (contentLength != null && !ctx.getResponseGZipped())
servletResponse.setContentLength(contentLength)
}
}
}
\ No newline at end of file
package filters.post
import com.netflix.zuul.ZuulFilter
import com.netflix.zuul.context.RequestContext
class Stats extends ZuulFilter {
@Override
String filterType() {
return "post"
}
@Override
int filterOrder() {
return 2000
}
@Override
boolean shouldFilter() {
return true
}
@Override
Object run() {
dumpRoutingDebug()
dumpRequestDebug()
}
public void dumpRequestDebug() {
List<String> rd = (List<String>) RequestContext.getCurrentContext().get("requestDebug");
rd?.each {
println("REQUEST_DEBUG::${it}");
}
}
public void dumpRoutingDebug() {
List<String> rd = (List<String>) RequestContext.getCurrentContext().get("routingDebug");
rd?.each {
println("ZUUL_DEBUG::${it}");
}
}
}
package filters.pre
import com.netflix.config.DynamicBooleanProperty
import com.netflix.config.DynamicPropertyFactory
import com.netflix.config.DynamicStringProperty
import com.netflix.zuul.ZuulFilter
import com.netflix.zuul.constants.ZuulConstants
import com.netflix.zuul.context.RequestContext
class DebugFilter extends ZuulFilter {
static final DynamicBooleanProperty routingDebug = DynamicPropertyFactory.getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, true)
static final DynamicStringProperty debugParameter = DynamicPropertyFactory.getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "d")
@Override
String filterType() {
return 'pre'
}
@Override
int filterOrder() {
return 1
}
boolean shouldFilter() {
if ("true".equals(RequestContext.getCurrentContext().getRequest().getParameter(debugParameter.get()))) return true;
return routingDebug.get();
}
Object run() {
RequestContext ctx = RequestContext.getCurrentContext()
ctx.setDebugRouting(true)
ctx.setDebugRequest(true)
return null;
}
}
package filters.pre
import com.netflix.zuul.ZuulFilter
import com.netflix.zuul.context.Debug
import com.netflix.zuul.context.RequestContext
import javax.servlet.http.HttpServletRequest
class DebugRequest extends ZuulFilter {
@Override
String filterType() {
return 'pre'
}
@Override
int filterOrder() {
return 10000
}
@Override
boolean shouldFilter() {
return Debug.debugRequest()
}
@Override
Object run() {
HttpServletRequest req = RequestContext.currentContext.request as HttpServletRequest
Debug.addRequestDebug("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort())
Debug.addRequestDebug("REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + " " + req.getProtocol())
Iterator headerIt = req.getHeaderNames().iterator()
while (headerIt.hasNext()) {
String name = (String) headerIt.next()
String value = req.getHeader(name)
Debug.addRequestDebug("REQUEST:: > " + name + ":" + value)
}
final RequestContext ctx = RequestContext.getCurrentContext()
if (!ctx.isChunkedRequestBody()) {
InputStream inp = ctx.request.getInputStream()
String body = null
if (inp != null) {
body = inp.getText()
Debug.addRequestDebug("REQUEST:: > " + body)
}
}
return null;
}
}
package filters.pre
import com.netflix.zuul.context.RequestContext
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.cloud.netflix.zuul.Routes
import org.springframework.cloud.netflix.zuul.SpringFilter
class PreDecorationFilter extends SpringFilter {
private static Logger LOG = LoggerFactory.getLogger(PreDecorationFilter.class);
@Override
int filterOrder() {
return 5
}
@Override
String filterType() {
return "pre"
}
@Override
boolean shouldFilter() {
return true;
}
@Override
Object run() {
RequestContext ctx = RequestContext.getCurrentContext()
def requestURI = ctx.getRequest().getRequestURI()
Routes routes = getBean(Routes.class)
def routesMap = routes.getRoutes()
def route = routesMap.keySet().find { path ->
//TODO: use ant matchers?
if (requestURI.startsWith(path)) {
return true
}
return false
}
def serviceId = routesMap.get(route)
if (serviceId != null) {
// set serviceId for use in filters.route.RibbonRequest
ctx.set("serviceId", serviceId)
ctx.setRouteHost(null)
ctx.addOriginResponseHeader("X-Zuul-ServiceId", serviceId);
}
}
}
package filters.route
import com.netflix.client.ClientException
import com.netflix.client.ClientFactory
import com.netflix.client.IClient
import com.netflix.client.http.HttpRequest
import com.netflix.client.http.HttpResponse
import com.netflix.hystrix.exception.HystrixRuntimeException
import com.netflix.niws.client.http.RestClient
import com.netflix.zuul.ZuulFilter
import com.netflix.zuul.context.Debug
import com.netflix.zuul.context.RequestContext
import com.netflix.zuul.exception.ZuulException
import com.netflix.zuul.util.HTTPRequestUtils
import com.sun.jersey.core.util.MultivaluedMapImpl
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.cloud.netflix.zuul.RibbonCommand
import javax.servlet.http.HttpServletRequest
import javax.ws.rs.core.MultivaluedMap
import java.util.zip.GZIPInputStream
import static HttpRequest.Verb
import static org.springframework.cloud.netflix.feign.FeignConfigurer.setServiceListClassAndVIP
class RibbonRequest extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(RibbonRequest.class);
public static final String CONTENT_ENCODING = "Content-Encoding";
@Override
String filterType() {
return 'route'
}
@Override
int filterOrder() {
return 10
}
boolean shouldFilter() {
def ctx = RequestContext.currentContext
return (ctx.getRouteHost() == null && ctx.get("serviceId") != null && ctx.sendZuulResponse())
}
Object run() {
RequestContext context = RequestContext.currentContext
HttpServletRequest request = context.getRequest();
MultivaluedMap<String, String> headers = buildZuulRequestHeaders(request)
MultivaluedMap<String, String> params = buildZuulRequestQueryParams(request)
Verb verb = getVerb(request);
Object requestEntity = getRequestBody(request)
def serviceId = context.get("serviceId")
//TODO: can this be set be default? or an implementation of an interface?
setServiceListClassAndVIP(serviceId)
IClient restClient = ClientFactory.getNamedClient(serviceId);
String uri = request.getRequestURI()
if (context.requestURI != null) {
uri = context.requestURI
}
//remove double slashes
uri = uri.replace("//", "/")
HttpResponse response = forward(restClient, verb, uri, headers, params, requestEntity)
setResponse(response)
return response
}
void debug(RestClient restClient, Verb verb, uri, MultivaluedMap<String, String> headers, MultivaluedMap<String, String> params, InputStream requestEntity) {
if (Debug.debugRequest()) {
headers.each {
Debug.addRequestDebug("ZUUL:: > ${it.key} ${it.value[0]}")
}
String query = ""
params.each {
it.value.each { v ->
query += it.key + "=" + v + "&"
}
}
Debug.addRequestDebug("ZUUL:: > ${verb.verb()} ${uri}?${query} HTTP/1.1")
RequestContext ctx = RequestContext.getCurrentContext()
if (!ctx.isChunkedRequestBody()) {
if (requestEntity != null) {
debugRequestEntity(ctx.request.getInputStream())
}
}
}
}
void debugRequestEntity(InputStream inputStream) {
if (!Debug.debugRequestHeadersOnly()) {
String entity = inputStream.getText()
Debug.addRequestDebug("ZUUL:: > ${entity}")
}
}
def HttpResponse forward(RestClient restClient, Verb verb, uri, MultivaluedMap<String, String> headers,
MultivaluedMap<String, String> params, InputStream requestEntity) {
debug(restClient, verb, uri, headers, params, requestEntity)
RibbonCommand command = new RibbonCommand(restClient, verb, uri, headers, params, requestEntity);
try {
HttpResponse response = command.execute();
return response
} catch (HystrixRuntimeException e) {
if (e?.fallbackException?.cause instanceof ClientException) {
ClientException ex = e.fallbackException.cause as ClientException
throw new ZuulException(ex, "Forwarding error", 500, ex.getErrorType().toString())
}
throw new ZuulException(e, "Forwarding error", 500, e.failureType.toString())
}
}
def getRequestBody(HttpServletRequest request) {
Object requestEntity = null;
try {
requestEntity = RequestContext.currentContext.requestEntity
if (requestEntity == null) {
requestEntity = request.getInputStream();
}
} catch (IOException e) {
LOG.error(e);
}
return requestEntity
}
def MultivaluedMap<String, String> buildZuulRequestQueryParams(HttpServletRequest request) {
Map<String, List<String>> map = HTTPRequestUtils.getInstance().getQueryParams()
MultivaluedMap<String, String> params = new MultivaluedMapImpl<String, String>();
if (map == null) return params;
map.entrySet().each {
it.value.each { v ->
params.add(it.key, v)
}
}
return params
}
def MultivaluedMap<String, String> buildZuulRequestHeaders(HttpServletRequest request) {
RequestContext context = RequestContext.currentContext
MultivaluedMap<String, String> headers = new MultivaluedMapImpl<String, String>();
Enumeration headerNames = request.getHeaderNames();
while (headerNames?.hasMoreElements()) {
String name = (String) headerNames.nextElement();
String value = request.getHeader(name);
if (!name.toLowerCase().contains("content-length")) headers.putSingle(name, value);
}
Map zuulRequestHeaders = context.getZuulRequestHeaders();
zuulRequestHeaders.keySet().each {
headers.putSingle((String) it, (String) zuulRequestHeaders[it])
}
headers.putSingle("accept-encoding", "deflate, gzip")
if (headers.containsKey("transfer-encoding"))
headers.remove("transfer-encoding")
return headers
}
Verb getVerb(HttpServletRequest request) {
String sMethod = request.getMethod();
return getVerb(sMethod);
}
Verb getVerb(String sMethod) {
if (sMethod == null) return Verb.GET;
sMethod = sMethod.toLowerCase();
if (sMethod.equals("post")) return Verb.POST;
if (sMethod.equals("put")) return Verb.PUT;
if (sMethod.equals("delete")) return Verb.DELETE;
if (sMethod.equals("options")) return Verb.OPTIONS;
if (sMethod.equals("head")) return Verb.HEAD;
return Verb.GET;
}
void setResponse(HttpResponse resp) {
RequestContext context = RequestContext.getCurrentContext()
context.setResponseStatusCode(resp.getStatus());
if (resp.hasEntity()) {
context.responseDataStream = resp.inputStream;
}
String contentEncoding = resp.getHeaders().get(CONTENT_ENCODING)?.first();
if (contentEncoding != null && HTTPRequestUtils.getInstance().isGzipped(contentEncoding)) {
context.setResponseGZipped(true);
} else {
context.setResponseGZipped(false);
}
if (Debug.debugRequest()) {
resp.getHeaders().keySet().each { key ->
boolean isValidHeader = isValidHeader(key)
Collection<String> list = resp.getHeaders().get(key)
list.each { header ->
context.addOriginResponseHeader(key, header)
if (key.equalsIgnoreCase("content-length"))
context.setOriginContentLength(header);
if (isValidHeader) {
context.addZuulResponseHeader(key, header);
Debug.addRequestDebug("ORIGIN_RESPONSE:: < ${key} ${header}")
}
}
}
if (context.responseDataStream) {
byte[] origBytes = context.getResponseDataStream().bytes
InputStream inStream = new ByteArrayInputStream(origBytes);
if (context.getResponseGZipped())
inStream = new GZIPInputStream(inStream);
String responseEntity = inStream.getText()
Debug.addRequestDebug("ORIGIN_RESPONSE:: < ${responseEntity}")
context.setResponseDataStream(new ByteArrayInputStream(origBytes))
}
} else {
resp.getHeaders().keySet().each { key ->
boolean isValidHeader = isValidHeader(key)
Collection<java.lang.String> list = resp.getHeaders().get(key)
list.each { header ->
context.addOriginResponseHeader(key, header)
if (key.equalsIgnoreCase("content-length"))
context.setOriginContentLength(header);
if (isValidHeader) {
context.addZuulResponseHeader(key, header);
}
}
}
}
}
boolean isValidHeader(String headerName) {
switch (headerName.toLowerCase()) {
case "connection":
case "content-length":
case "content-encoding":
case "server":
case "transfer-encoding":
return false
default:
return true
}
}
}
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