added support for standalone zuul server with @EnableZuulServer.
Removed old zuul module. fixes gh-14
This commit is contained in:
@@ -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`.
|
||||
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -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>
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
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();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public interface ZuulProperties {
|
||||
public String getMapping();
|
||||
public boolean isStripMapping();
|
||||
public String getRoutePrefix();
|
||||
public boolean isAddProxyHeaders();
|
||||
}
|
||||
@@ -1,23 +1,11 @@
|
||||
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;
|
||||
public class ZuulProxyConfiguration extends AbstractZuulConfiguration {
|
||||
|
||||
@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 ZuulProxyProperties zuulProxyProperties() {
|
||||
return new ZuulProxyProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean zuulServlet() {
|
||||
return new ServletRegistrationBean(new ZuulServlet(), props.getMapping()+"/*");
|
||||
//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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
Routes routes() {
|
||||
return new Routes();
|
||||
@Override
|
||||
protected ZuulProperties getProperties() {
|
||||
return zuulProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
77
spring-cloud-netflix-zuul-server/pom.xml
Normal file
77
spring-cloud-netflix-zuul-server/pom.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,19 @@
|
||||
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 {
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
server:
|
||||
port: 9876
|
||||
management:
|
||||
port: 9877
|
||||
spring:
|
||||
application:
|
||||
name: testzuulserver
|
||||
|
||||
zuul:
|
||||
server:
|
||||
route:
|
||||
testclient: /testing123
|
||||
stores: /stores
|
||||
customers: /customers
|
||||
|
||||
@@ -1,84 +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</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Netflix Zuul</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>
|
||||
<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>
|
||||
</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.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>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,69 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#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
|
||||
@@ -1,48 +0,0 @@
|
||||
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}
|
||||
@@ -1,6 +0,0 @@
|
||||
spring:
|
||||
application:
|
||||
name: zuul
|
||||
platform:
|
||||
config:
|
||||
uri: http://localhost:${config.port:8888}
|
||||
@@ -1,170 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,434 +0,0 @@
|
||||
package filters.route
|
||||
|
||||
import com.netflix.config.DynamicIntProperty
|
||||
import com.netflix.config.DynamicPropertyFactory
|
||||
import com.netflix.zuul.ZuulFilter
|
||||
import com.netflix.zuul.constants.ZuulConstants
|
||||
import com.netflix.zuul.context.Debug
|
||||
import com.netflix.zuul.context.RequestContext
|
||||
import com.netflix.zuul.util.HTTPRequestUtils
|
||||
import org.apache.http.Header
|
||||
import org.apache.http.HttpHost
|
||||
import org.apache.http.HttpRequest
|
||||
import org.apache.http.HttpResponse
|
||||
import org.apache.http.client.HttpClient
|
||||
import org.apache.http.client.methods.HttpPost
|
||||
import org.apache.http.client.methods.HttpPut
|
||||
import org.apache.http.client.params.ClientPNames
|
||||
import org.apache.http.conn.ClientConnectionManager
|
||||
import org.apache.http.conn.scheme.PlainSocketFactory
|
||||
import org.apache.http.conn.scheme.Scheme
|
||||
import org.apache.http.conn.scheme.SchemeRegistry
|
||||
import org.apache.http.conn.ssl.SSLSocketFactory
|
||||
import org.apache.http.entity.InputStreamEntity
|
||||
import org.apache.http.impl.client.DefaultHttpClient
|
||||
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler
|
||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager
|
||||
import org.apache.http.message.BasicHeader
|
||||
import org.apache.http.message.BasicHttpRequest
|
||||
import org.apache.http.params.CoreConnectionPNames
|
||||
import org.apache.http.params.HttpParams
|
||||
import org.apache.http.protocol.HttpContext
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import java.security.*
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
|
||||
public static final String CONTENT_ENCODING = "Content-Encoding";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SimpleHostRoutingFilter.class);
|
||||
private static final Runnable CLIENTLOADER = new Runnable() {
|
||||
@Override
|
||||
void run() {
|
||||
loadClient();
|
||||
}
|
||||
}
|
||||
|
||||
private static final DynamicIntProperty SOCKET_TIMEOUT = DynamicPropertyFactory.getInstance().getIntProperty(ZuulConstants.ZUUL_HOST_SOCKET_TIMEOUT_MILLIS, 10000)
|
||||
private static final DynamicIntProperty CONNECTION_TIMEOUT = DynamicPropertyFactory.getInstance().getIntProperty(ZuulConstants.ZUUL_HOST_CONNECT_TIMEOUT_MILLIS, 2000)
|
||||
|
||||
private static final AtomicReference<HttpClient> CLIENT = new AtomicReference<HttpClient>(newClient());
|
||||
|
||||
private static final Timer CONNECTION_MANAGER_TIMER = new Timer(true);
|
||||
|
||||
// cleans expired connections at an interval
|
||||
static {
|
||||
SOCKET_TIMEOUT.addCallback(CLIENTLOADER)
|
||||
CONNECTION_TIMEOUT.addCallback(CLIENTLOADER)
|
||||
CONNECTION_MANAGER_TIMER.schedule(new TimerTask() {
|
||||
@Override
|
||||
void run() {
|
||||
try {
|
||||
final HttpClient hc = CLIENT.get();
|
||||
if (hc == null) return;
|
||||
hc.getConnectionManager().closeExpiredConnections();
|
||||
} catch (Throwable t) {
|
||||
LOG.error("error closing expired connections", t);
|
||||
}
|
||||
}
|
||||
}, 30000, 5000)
|
||||
}
|
||||
|
||||
public SimpleHostRoutingFilter() {}
|
||||
|
||||
private static final ClientConnectionManager newConnectionManager() {
|
||||
|
||||
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
trustStore.load(null, null);
|
||||
|
||||
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
|
||||
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||
|
||||
SchemeRegistry registry = new SchemeRegistry();
|
||||
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
|
||||
registry.register(new Scheme("https", sf, 443));
|
||||
registry.register(new Scheme("https", sf, 8443));
|
||||
|
||||
ClientConnectionManager cm = new ThreadSafeClientConnManager(registry);
|
||||
cm.setMaxTotal(Integer.parseInt(System.getProperty("zuul.max.host.connections", "200")));
|
||||
cm.setDefaultMaxPerRoute(Integer.parseInt(System.getProperty("zuul.max.host.connections", "20")));
|
||||
return cm;
|
||||
}
|
||||
|
||||
@Override
|
||||
String filterType() {
|
||||
return 'route'
|
||||
}
|
||||
|
||||
@Override
|
||||
int filterOrder() {
|
||||
return 100
|
||||
}
|
||||
|
||||
boolean shouldFilter() {
|
||||
return RequestContext.currentContext.getRouteHost() != null && RequestContext.currentContext.sendZuulResponse()
|
||||
}
|
||||
|
||||
private static final void loadClient() {
|
||||
final HttpClient oldClient = CLIENT.get();
|
||||
CLIENT.set(newClient())
|
||||
if (oldClient != null) {
|
||||
CONNECTION_MANAGER_TIMER.schedule(new TimerTask() {
|
||||
@Override
|
||||
void run() {
|
||||
try {
|
||||
oldClient.getConnectionManager().shutdown();
|
||||
} catch (Throwable t) {
|
||||
LOG.error("error shutting down old connection manager", t);
|
||||
}
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final HttpClient newClient() {
|
||||
// I could statically cache the connection manager but we will probably want to make some of its properties
|
||||
// dynamic in the near future also
|
||||
HttpClient httpclient = new DefaultHttpClient(newConnectionManager());
|
||||
HttpParams httpParams = httpclient.getParams();
|
||||
httpParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SOCKET_TIMEOUT.get())
|
||||
httpParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, CONNECTION_TIMEOUT.get())
|
||||
httpclient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false))
|
||||
httpParams.setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.IGNORE_COOKIES);
|
||||
httpclient.setRedirectStrategy(new org.apache.http.client.RedirectStrategy() {
|
||||
@Override
|
||||
boolean isRedirected(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) {
|
||||
return false
|
||||
}
|
||||
|
||||
@Override
|
||||
org.apache.http.client.methods.HttpUriRequest getRedirect(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
return httpclient
|
||||
}
|
||||
|
||||
Object run() {
|
||||
HttpServletRequest request = RequestContext.currentContext.getRequest();
|
||||
Header[] headers = buildZuulRequestHeaders(request)
|
||||
String verb = getVerb(request);
|
||||
InputStream requestEntity = getRequestBody(request)
|
||||
HttpClient httpclient = CLIENT.get()
|
||||
|
||||
String uri = request.getRequestURI()
|
||||
if (RequestContext.currentContext.requestURI != null) {
|
||||
uri = RequestContext.currentContext.requestURI
|
||||
}
|
||||
|
||||
try {
|
||||
HttpResponse response = forward(httpclient, verb, uri, request, headers, requestEntity)
|
||||
setResponse(response)
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (Debug.debugRequest()) {
|
||||
Debug.addRequestDebug("ZUUL:: ERROR " + e.getMessage())
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
def InputStream debug(HttpClient httpclient, String verb, String uri, HttpServletRequest request, Header[] headers, InputStream requestEntity) {
|
||||
|
||||
if (Debug.debugRequest()) {
|
||||
|
||||
Debug.addRequestDebug("ZUUL:: host=${RequestContext.currentContext.getRouteHost()}")
|
||||
|
||||
headers.each {
|
||||
Debug.addRequestDebug("ZUUL::> ${it.name} ${it.value}")
|
||||
}
|
||||
String query = request.queryString
|
||||
|
||||
Debug.addRequestDebug("ZUUL:: > ${verb} ${uri}?${query} HTTP/1.1")
|
||||
if (requestEntity != null) {
|
||||
requestEntity = debugRequestEntity(requestEntity)
|
||||
}
|
||||
|
||||
}
|
||||
return requestEntity
|
||||
}
|
||||
|
||||
InputStream debugRequestEntity(InputStream inputStream) {
|
||||
if (Debug.debugRequestHeadersOnly()) return inputStream
|
||||
if (inputStream == null) return null
|
||||
String entity = inputStream.getText()
|
||||
Debug.addRequestDebug("ZUUL::> ${entity}")
|
||||
return new ByteArrayInputStream(entity.bytes)
|
||||
}
|
||||
|
||||
def HttpResponse forward(HttpClient httpclient, String verb, String uri, HttpServletRequest request, Header[] headers, InputStream requestEntity) {
|
||||
|
||||
requestEntity = debug(httpclient, verb, uri, request, headers, requestEntity)
|
||||
|
||||
org.apache.http.HttpHost httpHost
|
||||
|
||||
httpHost = getHttpHost()
|
||||
|
||||
org.apache.http.HttpRequest httpRequest;
|
||||
|
||||
switch (verb) {
|
||||
case 'POST':
|
||||
httpRequest = new HttpPost(uri + getQueryString())
|
||||
InputStreamEntity entity = new InputStreamEntity(requestEntity, request.getContentLength())
|
||||
httpRequest.setEntity(entity)
|
||||
break
|
||||
case 'PUT':
|
||||
httpRequest = new HttpPut(uri + getQueryString())
|
||||
InputStreamEntity entity = new InputStreamEntity(requestEntity, request.getContentLength())
|
||||
httpRequest.setEntity(entity)
|
||||
break;
|
||||
default:
|
||||
httpRequest = new BasicHttpRequest(verb, uri + getQueryString())
|
||||
LOG.debug(uri + getQueryString())
|
||||
}
|
||||
|
||||
try {
|
||||
httpRequest.setHeaders(headers)
|
||||
LOG.debug(httpHost.getHostName() + " " + httpHost.getPort() + " " + httpHost.getSchemeName())
|
||||
HttpResponse zuulResponse = forwardRequest(httpclient, httpHost, httpRequest)
|
||||
return zuulResponse
|
||||
} finally {
|
||||
// When HttpClient instance is no longer needed,
|
||||
// shut down the connection manager to ensure
|
||||
// immediate deallocation of all system resources
|
||||
// httpclient.getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HttpResponse forwardRequest(HttpClient httpclient, HttpHost httpHost, HttpRequest httpRequest) {
|
||||
return httpclient.execute(httpHost, httpRequest);
|
||||
}
|
||||
|
||||
String getQueryString() {
|
||||
HttpServletRequest request = RequestContext.currentContext.getRequest();
|
||||
String query = request.getQueryString()
|
||||
return (query != null) ? "?${query}" : "";
|
||||
}
|
||||
|
||||
HttpHost getHttpHost() {
|
||||
HttpHost httpHost
|
||||
URL host = RequestContext.currentContext.getRouteHost()
|
||||
|
||||
httpHost = new HttpHost(host.getHost(), host.getPort(), host.getProtocol())
|
||||
|
||||
return httpHost
|
||||
}
|
||||
|
||||
|
||||
def getRequestBody(HttpServletRequest request) {
|
||||
Object requestEntity = null;
|
||||
try {
|
||||
requestEntity = request.getInputStream();
|
||||
} catch (IOException e) {
|
||||
//no requestBody is ok.
|
||||
}
|
||||
return requestEntity
|
||||
}
|
||||
|
||||
boolean isValidHeader(String name) {
|
||||
if (name.toLowerCase().contains("content-length")) return false;
|
||||
if (!RequestContext.currentContext.responseGZipped) {
|
||||
if (name.toLowerCase().contains("accept-encoding")) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
def Header[] buildZuulRequestHeaders(HttpServletRequest request) {
|
||||
|
||||
def headers = new ArrayList()
|
||||
Enumeration headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String name = (String) headerNames.nextElement();
|
||||
String value = request.getHeader(name);
|
||||
if (isValidHeader(name)) headers.add(new BasicHeader(name, value))
|
||||
}
|
||||
|
||||
Map zuulRequestHeaders = RequestContext.getCurrentContext().getZuulRequestHeaders();
|
||||
|
||||
zuulRequestHeaders.keySet().each {
|
||||
String name = it.toLowerCase()
|
||||
BasicHeader h = headers.find { BasicHeader he -> he.name == name }
|
||||
if (h != null) {
|
||||
headers.remove(h)
|
||||
}
|
||||
headers.add(new BasicHeader((String) it, (String) zuulRequestHeaders[it]))
|
||||
}
|
||||
|
||||
if (RequestContext.currentContext.responseGZipped) {
|
||||
headers.add(new BasicHeader("accept-encoding", "deflate, gzip"))
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
|
||||
|
||||
String getVerb(HttpServletRequest request) {
|
||||
String sMethod = request.getMethod();
|
||||
return sMethod.toUpperCase();
|
||||
}
|
||||
|
||||
String getVerb(String sMethod) {
|
||||
if (sMethod == null) return "GET";
|
||||
sMethod = sMethod.toLowerCase();
|
||||
if (sMethod.equalsIgnoreCase("post")) return "POST"
|
||||
if (sMethod.equalsIgnoreCase("put")) return "PUT"
|
||||
if (sMethod.equalsIgnoreCase("delete")) return "DELETE"
|
||||
if (sMethod.equalsIgnoreCase("options")) return "OPTIONS"
|
||||
if (sMethod.equalsIgnoreCase("head")) return "HEAD"
|
||||
return "GET"
|
||||
}
|
||||
|
||||
void setResponse(HttpResponse response) {
|
||||
RequestContext context = RequestContext.getCurrentContext()
|
||||
|
||||
RequestContext.currentContext.set("hostZuulResponse", response)
|
||||
RequestContext.getCurrentContext().setResponseStatusCode(response.getStatusLine().statusCode)
|
||||
RequestContext.getCurrentContext().responseDataStream = response?.entity?.content
|
||||
|
||||
boolean isOriginResponseGzipped = false
|
||||
|
||||
for (Header h : response.getHeaders(CONTENT_ENCODING)) {
|
||||
if (HTTPRequestUtils.getInstance().isGzipped(h.value)) {
|
||||
isOriginResponseGzipped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
context.setResponseGZipped(isOriginResponseGzipped);
|
||||
|
||||
|
||||
if (Debug.debugRequest()) {
|
||||
response.getAllHeaders()?.each { Header header ->
|
||||
if (isValidHeader(header)) {
|
||||
RequestContext.getCurrentContext().addZuulResponseHeader(header.name, header.value);
|
||||
Debug.addRequestDebug("ORIGIN_RESPONSE:: < ${header.name}, ${header.value}")
|
||||
}
|
||||
}
|
||||
|
||||
if (context.responseDataStream) {
|
||||
byte[] origBytes = context.getResponseDataStream().bytes
|
||||
ByteArrayInputStream byteStream = new ByteArrayInputStream(origBytes)
|
||||
InputStream inputStream = byteStream
|
||||
if (RequestContext.currentContext.responseGZipped) {
|
||||
inputStream = new GZIPInputStream(byteStream);
|
||||
}
|
||||
|
||||
|
||||
context.setResponseDataStream(new ByteArrayInputStream(origBytes))
|
||||
}
|
||||
|
||||
} else {
|
||||
response.getAllHeaders()?.each { Header header ->
|
||||
RequestContext ctx = RequestContext.getCurrentContext()
|
||||
ctx.addOriginResponseHeader(header.name, header.value)
|
||||
|
||||
if (header.name.equalsIgnoreCase("content-length"))
|
||||
ctx.setOriginContentLength(header.value)
|
||||
|
||||
if (isValidHeader(header)) {
|
||||
ctx.addZuulResponseHeader(header.name, header.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean isValidHeader(Header header) {
|
||||
switch (header.name.toLowerCase()) {
|
||||
case "connection":
|
||||
case "content-length":
|
||||
case "content-encoding":
|
||||
case "server":
|
||||
case "transfer-encoding":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public static class MySSLSocketFactory extends SSLSocketFactory {
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
|
||||
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||
super(truststore);
|
||||
|
||||
TrustManager tm = new X509TrustManager() {
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
TrustManager[] tms = new TrustManager[1];
|
||||
tms[0] = tm;
|
||||
sslContext.init(null, tms, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
|
||||
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
return sslContext.getSocketFactory().createSocket();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user