diff --git a/spring-platform-netflix-core/pom.xml b/spring-platform-netflix-core/pom.xml
index 0031b704..6b27c259 100644
--- a/spring-platform-netflix-core/pom.xml
+++ b/spring-platform-netflix-core/pom.xml
@@ -81,6 +81,16 @@
ribbon-eureka
true
+
+ com.netflix.ribbon
+ ribbon-httpclient
+ true
+
+
+ com.netflix.zuul
+ zuul-core
+ true
+
org.projectlombok
lombok
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/eureka/EurekaInstanceConfigBean.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/eureka/EurekaInstanceConfigBean.java
index 62ecf112..01a6ce98 100644
--- a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/eureka/EurekaInstanceConfigBean.java
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/eureka/EurekaInstanceConfigBean.java
@@ -66,7 +66,7 @@ public class EurekaInstanceConfigBean implements EurekaInstanceConfig {
private int leaseExpirationDurationInSeconds = 90;
- @Value("${spring.application.name:unknown}.mydomain.net")
+ @Value("${spring.application.name:unknown}") //TODO: why is .mydomain.net here?
private String virtualHostName;
private String secureVirtualHostName;
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/feign/FeignConfigurer.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/feign/FeignConfigurer.java
index 98907b09..943e7918 100644
--- a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/feign/FeignConfigurer.java
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/feign/FeignConfigurer.java
@@ -50,11 +50,11 @@ public class FeignConfigurer {
protected T loadBalance(Feign.Builder builder, Class type, String schemeName) {
String name = URI.create(schemeName).getHost();
- setServiceListClass(name);
+ setServiceListClassAndVIP(name);
return builder.target(LoadBalancingTarget.create(type, schemeName));
}
- public static void setServiceListClass(String serviceId) {
+ public static void setServiceListClassAndVIP(String serviceId) {
setProp(serviceId, "NIWSServerListClassName", DiscoveryEnabledNIWSServerList.class.getName());
setProp(serviceId, "DeploymentContextBasedVipAddresses", serviceId); //FIXME: what should this be?
}
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/EnableZuulProxy.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/EnableZuulProxy.java
new file mode 100644
index 00000000..a938b665
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/EnableZuulProxy.java
@@ -0,0 +1,15 @@
+package org.springframework.platform.netflix.zuul;
+
+import java.lang.annotation.*;
+
+import org.springframework.context.annotation.Import;
+
+/**
+ * @author Spencer Gibb
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(ZuulProxyConfiguration.class)
+public @interface EnableZuulProxy {
+}
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/FilterIntializer.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/FilterIntializer.java
new file mode 100644
index 00000000..4bcbe67d
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/FilterIntializer.java
@@ -0,0 +1,72 @@
+package org.springframework.platform.netflix.zuul;
+
+import com.netflix.zuul.FilterFileManager;
+import com.netflix.zuul.FilterLoader;
+import com.netflix.zuul.ZuulFilter;
+import com.netflix.zuul.filters.FilterRegistry;
+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;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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
+ private Map filters;
+
+ @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();
+
+ FilterRegistry registry = FilterRegistry.instance();
+
+ for (Map.Entry entry : filters.entrySet()) {
+ registry.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ LOGGER.info("Stopping filter initializer context listener");
+ }
+
+ /*private void initGroovyFilterManager() {
+
+ //TODO: support groovy filters loaded from filesystem in proxy
+ 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);
+ }
+ }*/
+}
diff --git a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/RibbonCommand.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/RibbonCommand.java
similarity index 98%
rename from spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/RibbonCommand.java
rename to spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/RibbonCommand.java
index acdcbc77..1d30d43a 100644
--- a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/RibbonCommand.java
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/RibbonCommand.java
@@ -1,4 +1,4 @@
-package io.spring.platform.netflix.zuul;
+package org.springframework.platform.netflix.zuul;
import com.netflix.client.http.HttpRequest;
import com.netflix.client.http.HttpResponse;
diff --git a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/Routes.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/Routes.java
similarity index 88%
rename from spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/Routes.java
rename to spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/Routes.java
index ae532151..41c83f50 100644
--- a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/Routes.java
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/Routes.java
@@ -1,4 +1,4 @@
-package io.spring.platform.netflix.zuul;
+package org.springframework.platform.netflix.zuul;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,9 +22,20 @@ public class Routes {
@Autowired
ConfigurableEnvironment env;
- private final Field propertySourcesField;
+ private Field propertySourcesField;
+ private String keyPrefix;
public Routes() {
+ keyPrefix = "zuul.route.";
+ initField();
+ }
+
+ public Routes(String keyPrefix) {
+ this.keyPrefix = keyPrefix;
+ initField();
+ }
+
+ private void initField() {
propertySourcesField = ReflectionUtils.findField(CompositePropertySource.class, "propertySources");
propertySourcesField.setAccessible(true);
}
@@ -63,7 +74,7 @@ public class Routes {
//EnumerablePropertySource enumerable = (EnumerablePropertySource) propertySource;
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(propertySource);
- Map routeEntries = PropertySourceUtils.getSubProperties(propertySources, "zuul.route.");
+ Map routeEntries = PropertySourceUtils.getSubProperties(propertySources, keyPrefix);
for (Map.Entry entry : routeEntries.entrySet()) {
String serviceId = entry.getKey();
String route = entry.getValue().toString();
diff --git a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/SpringFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/SpringFilter.java
similarity index 96%
rename from spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/SpringFilter.java
rename to spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/SpringFilter.java
index db42a98d..bba3a258 100644
--- a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/SpringFilter.java
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/SpringFilter.java
@@ -1,4 +1,4 @@
-package io.spring.platform.netflix.zuul;
+package org.springframework.platform.netflix.zuul;
import com.netflix.zuul.ZuulFilter;
import org.springframework.web.context.WebApplicationContext;
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/ZuulProxyConfiguration.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/ZuulProxyConfiguration.java
new file mode 100644
index 00000000..d263dc2b
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/ZuulProxyConfiguration.java
@@ -0,0 +1,94 @@
+package org.springframework.platform.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.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.platform.netflix.zuul.filters.post.SendResponseFilter;
+import org.springframework.platform.netflix.zuul.filters.post.StatsFilter;
+import org.springframework.platform.netflix.zuul.filters.pre.DebugFilter;
+import org.springframework.platform.netflix.zuul.filters.pre.DebugRequestFilter;
+import org.springframework.platform.netflix.zuul.filters.pre.PreDecorationFilter;
+import org.springframework.platform.netflix.zuul.filters.route.RibbonRoutingFilter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author Spencer Gibb
+ */
+@Configuration
+@EnableConfigurationProperties(ZuulProxyProperties.class)
+@ConditionalOnClass(ZuulServlet.class)
+@ConditionalOnExpression("${zuul.proxy.enabled:true}")
+public class ZuulProxyConfiguration {
+
+ @Autowired
+ private ZuulProxyProperties props;
+
+ @Bean
+ public FilterRegistrationBean contextLifecycleFilter() {
+ Collection urlPatterns = new ArrayList<>();
+ urlPatterns.add(props.getMapping()+"/*");
+
+ FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new ContextLifecycleFilter());
+ filterRegistrationBean.setUrlPatterns(urlPatterns);
+
+ return filterRegistrationBean;
+ }
+
+ @Bean
+ public ServletRegistrationBean zuulServlet() {
+ return new ServletRegistrationBean(new ZuulServlet(), props.getMapping()+"/*");
+ }
+
+ @Bean
+ Routes routes() {
+ return new Routes("zuul.proxy.route.");
+ }
+
+ @Bean
+ FilterIntializer filterIntializer() {
+ return new FilterIntializer();
+ }
+
+ // pre filters
+ @Bean
+ public DebugFilter debugFilter() {
+ return new DebugFilter();
+ }
+
+ @Bean
+ public DebugRequestFilter debugRequestFilter() {
+ return new DebugRequestFilter();
+ }
+
+ @Bean
+ public PreDecorationFilter preDecorationFilter() {
+ return new PreDecorationFilter();
+ }
+
+ // route filters
+ @Bean
+ public RibbonRoutingFilter ribbonRoutingFilter() {
+ return new RibbonRoutingFilter();
+ }
+
+ // post filters
+ @Bean
+ public SendResponseFilter sendResponseFilter() {
+ return new SendResponseFilter();
+ }
+
+ @Bean
+ public StatsFilter statsFilter() {
+ return new StatsFilter();
+ }
+
+}
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/ZuulProxyProperties.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/ZuulProxyProperties.java
new file mode 100644
index 00000000..9f62b76a
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/ZuulProxyProperties.java
@@ -0,0 +1,13 @@
+package org.springframework.platform.netflix.zuul;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Created by sgibb on 8/5/14.
+ */
+@Data
+@ConfigurationProperties("zuul.proxy")
+public class ZuulProxyProperties {
+ private String mapping = "/proxy";
+}
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/post/SendResponseFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/post/SendResponseFilter.java
new file mode 100644
index 00000000..b2ab31f7
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/post/SendResponseFilter.java
@@ -0,0 +1,181 @@
+package org.springframework.platform.netflix.zuul.filters.post;
+
+import com.google.common.base.Throwables;
+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.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+
+public 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
+ public String filterType() {
+ return "post";
+ }
+
+ @Override
+ public int filterOrder() {
+ return 1000;
+ }
+
+ public boolean shouldFilter() {
+ return !RequestContext.getCurrentContext().getZuulResponseHeaders().isEmpty() ||
+ RequestContext.getCurrentContext().getResponseDataStream() != null ||
+ RequestContext.getCurrentContext().getResponseBody() != null;
+ }
+
+ public Object run() {
+ try {
+ addResponseHeaders();
+ writeResponse();
+ } catch (Exception e) {
+ Throwables.propagate(e);
+ }
+ return null;
+ }
+
+ void writeResponse() throws Exception {
+ RequestContext context = RequestContext.getCurrentContext();
+
+ // 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.getCurrentContext().getResponseBody() != null) {
+ String body = RequestContext.getCurrentContext().getResponseBody();
+ writeResponse(new ByteArrayInputStream(body.getBytes()), 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) {
+ System.out.println("gzip expected but not received assuming unencoded response" +
+ RequestContext.getCurrentContext().getRequest().getRequestURL().toString());
+ inputStream = is;
+ }
+ else if (context.getResponseGZipped() && isGzipRequested)
+ servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING, "gzip");
+ writeResponse(inputStream, outStream);
+ }
+ }
+
+ } finally {
+ try {
+ if (is != null)
+ is.close();
+
+ outStream.flush();
+ outStream.close();
+ } catch (IOException e) {
+
+ }
+ }
+ }
+
+ private void writeResponse(InputStream zin, OutputStream out) throws Exception {
+ 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> zuulResponseHeaders = context.getZuulResponseHeaders();
+ String debugHeader = "";
+
+ List rd = (List) RequestContext.getCurrentContext().get("routingDebug");
+ for (String it : rd) {
+ debugHeader += "[[["+it+"]]]";
+ }
+
+ /*
+ rd = (List) 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 != null) {
+ for (Pair it : zuulResponseHeaders) {
+ servletResponse.addHeader(it.first(), it.second());
+ Debug.addRequestDebug("OUTBOUND: < " + it.first() + ":" + it.second());
+ }
+ } else if (zuulResponseHeaders != null) {
+ for (Pair it : zuulResponseHeaders) {
+ 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
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/post/StatsFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/post/StatsFilter.java
new file mode 100644
index 00000000..d3959000
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/post/StatsFilter.java
@@ -0,0 +1,50 @@
+package org.springframework.platform.netflix.zuul.filters.post;
+
+import com.netflix.zuul.ZuulFilter;
+
+import com.netflix.zuul.context.RequestContext;
+
+import java.util.List;
+
+public class StatsFilter extends ZuulFilter {
+ @Override
+ public String filterType() {
+ return "post";
+ }
+
+ @Override
+ public int filterOrder() {
+ return 2000;
+ }
+
+ @Override
+ public boolean shouldFilter() {
+ return true;
+ }
+
+ @Override
+ public Object run() {
+ dumpRoutingDebug();
+ dumpRequestDebug();
+ return null;
+ }
+
+ public void dumpRequestDebug() {
+ List rd = (List) RequestContext.getCurrentContext().get("requestDebug");
+ if (rd != null) {
+ for (String it : rd) {
+ System.out.println("REQUEST_DEBUG::" + it);
+ }
+ }
+ }
+
+ public void dumpRoutingDebug() {
+ List rd = (List) RequestContext.getCurrentContext().get("routingDebug");
+ if (rd != null) {
+ for (String it : rd) {
+ System.out.println("ZUUL_DEBUG::"+it);
+ }
+ }
+ }
+
+}
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/DebugFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/DebugFilter.java
new file mode 100644
index 00000000..45cade94
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/DebugFilter.java
@@ -0,0 +1,48 @@
+package org.springframework.platform.netflix.zuul.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;
+
+import javax.servlet.http.HttpServletRequest;
+
+public 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
+ public String filterType() {
+ return "pre";
+ }
+
+ @Override
+ public int filterOrder() {
+ return 1;
+ }
+
+ public boolean shouldFilter() {
+ HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
+ if ("true".equals(request.getParameter(debugParameter.get())))
+ return true;
+
+ return routingDebug.get();
+ }
+
+ public Object run() {
+ RequestContext ctx = RequestContext.getCurrentContext();
+ ctx.setDebugRouting(true);
+ ctx.setDebugRequest(true);
+ return null;
+ }
+
+
+}
+
+
+
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/DebugRequestFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/DebugRequestFilter.java
new file mode 100644
index 00000000..5724f3a9
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/DebugRequestFilter.java
@@ -0,0 +1,62 @@
+package org.springframework.platform.netflix.zuul.filters.pre;
+
+import com.netflix.zuul.ZuulFilter;
+
+import com.netflix.zuul.context.Debug;
+import com.netflix.zuul.context.RequestContext;
+import org.apache.commons.io.IOUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+
+public class DebugRequestFilter extends ZuulFilter {
+ @Override
+ public String filterType() {
+ return "pre";
+ }
+
+ @Override
+ public int filterOrder() {
+ return 10000;
+ }
+
+ @Override
+ public boolean shouldFilter() {
+ return Debug.debugRequest();
+ }
+
+ @Override
+ public Object run() {
+ HttpServletRequest req = RequestContext.getCurrentContext().getRequest();
+
+ Debug.addRequestDebug("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort());
+
+ Debug.addRequestDebug("REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + " " + req.getProtocol());
+
+ Enumeration headerIt = req.getHeaderNames();
+ while (headerIt.hasMoreElements()) {
+ String name = headerIt.nextElement();
+ String value = req.getHeader(name);
+ Debug.addRequestDebug("REQUEST:: > " + name + ":" + value);
+
+ }
+
+ final RequestContext ctx = RequestContext.getCurrentContext();
+ if (!ctx.isChunkedRequestBody()) {
+ try {
+ InputStream inp = ctx.getRequest().getInputStream();
+ if (inp != null) {
+ String body = IOUtils.toString(inp);
+ Debug.addRequestDebug("REQUEST:: > " + body);
+
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/PreDecorationFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/PreDecorationFilter.java
new file mode 100644
index 00000000..c2e1bdaf
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/PreDecorationFilter.java
@@ -0,0 +1,76 @@
+package org.springframework.platform.netflix.zuul.filters.pre;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.netflix.zuul.ZuulFilter;
+import com.netflix.zuul.context.RequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.platform.netflix.zuul.Routes;
+import org.springframework.platform.netflix.zuul.ZuulProxyProperties;
+
+import javax.annotation.Nullable;
+import java.util.LinkedHashMap;
+
+public class PreDecorationFilter extends ZuulFilter {
+ private static Logger LOG = LoggerFactory.getLogger(PreDecorationFilter.class);
+
+ @Autowired
+ private Routes routes;
+
+ @Autowired
+ private ZuulProxyProperties properties;
+
+ @Override
+ public int filterOrder() {
+ return 5;
+ }
+
+ @Override
+ public String filterType() {
+ return "pre";
+ }
+
+ @Override
+ public boolean shouldFilter() {
+ return true;
+ }
+
+ @Override
+ public Object run() {
+ RequestContext ctx = RequestContext.getCurrentContext();
+
+ String requestURI = ctx.getRequest().getRequestURI();
+
+ //remove proxy prefix TODO: only if embedded proxy
+ String proxyMapping = properties.getMapping();
+ final String uriPart = requestURI.replace(proxyMapping, ""); //TODO: better strategy?
+ ctx.put("requestURI", uriPart);
+
+ LinkedHashMap routesMap = routes.getRoutes();
+
+ Optional route = Iterables.tryFind(routesMap.keySet(), new Predicate() {
+ @Override
+ public boolean apply(@Nullable String path) {
+ return uriPart.startsWith(path);
+ }
+ });
+
+ if (route.isPresent()) {
+ String serviceId = routesMap.get(route.get());
+
+ if (serviceId != null) {
+ // set serviceId for use in filters.route.RibbonRequest
+ ctx.set("serviceId", serviceId);
+ ctx.setRouteHost(null);
+ ctx.addOriginResponseHeader("X-Zuul-ServiceId", serviceId);
+ }
+ } else {
+ LOG.warn("No route found for uri: "+requestURI);
+ //TODO: 404
+ }
+ return null;
+ }
+}
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/route/RibbonRoutingFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/route/RibbonRoutingFilter.java
new file mode 100644
index 00000000..49067d65
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/route/RibbonRoutingFilter.java
@@ -0,0 +1,302 @@
+package org.springframework.platform.netflix.zuul.filters.route;
+
+import com.netflix.client.ClientException;
+import com.netflix.client.ClientFactory;
+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.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.platform.netflix.zuul.RibbonCommand;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.MultivaluedMap;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+
+import static com.netflix.client.http.HttpRequest.Verb;
+import static org.springframework.platform.netflix.feign.FeignConfigurer.setServiceListClassAndVIP;
+
+public class RibbonRoutingFilter extends ZuulFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RibbonRoutingFilter.class);
+
+ public static final String CONTENT_ENCODING = "Content-Encoding";
+
+ @Override
+ public String filterType() {
+ return "route";
+ }
+
+ @Override
+ public int filterOrder() {
+ return 10;
+ }
+
+ public boolean shouldFilter() {
+ RequestContext ctx = RequestContext.getCurrentContext();
+ return (ctx.getRouteHost() == null && ctx.get("serviceId") != null && ctx.sendZuulResponse());
+ }
+
+ public Object run() {
+ RequestContext context = RequestContext.getCurrentContext();
+ HttpServletRequest request = context.getRequest();
+
+ MultivaluedMap headers = buildZuulRequestHeaders(request);
+ MultivaluedMap params = buildZuulRequestQueryParams(request);
+ Verb verb = getVerb(request);
+ InputStream requestEntity = getRequestBody(request);
+
+ String serviceId = (String) context.get("serviceId");
+
+ //TODO: can this be set be default? or an implementation of an interface?
+ setServiceListClassAndVIP(serviceId);
+
+ RestClient restClient = (RestClient) ClientFactory.getNamedClient(serviceId);
+
+ String uri = request.getRequestURI();
+ if (context.get("requestURI") != null) {
+ uri = (String) context.get("requestURI");
+ }
+ //remove double slashes
+ uri = uri.replace("//", "/");
+
+ try {
+ HttpResponse response = forward(restClient, verb, uri, headers, params, requestEntity);
+ setResponse(response);
+ return response;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void debug(RestClient restClient, Verb verb, String uri, MultivaluedMap headers,
+ MultivaluedMap params, InputStream requestEntity) throws IOException {
+
+ if (Debug.debugRequest()) {
+
+ for (String header: headers.keySet()) {
+ Debug.addRequestDebug(String.format("ZUUL:: > %s %s", header, headers.getFirst(header)));
+ }
+ StringBuilder query = new StringBuilder();
+ for (String param : params.keySet()) {
+ for (String value : params.get(param)) {
+ query.append(param);
+ query.append("=");
+ query.append(value);
+ query.append("&");
+ }
+ }
+
+ Debug.addRequestDebug(String.format("ZUUL:: > %s %s?%s HTTP/1.1", verb.verb(), uri, query.toString()));
+ RequestContext ctx = RequestContext.getCurrentContext();
+ if (!ctx.isChunkedRequestBody()) {
+ if (requestEntity != null) {
+ debugRequestEntity(ctx.getRequest().getInputStream());
+ }
+ }
+ }
+ }
+
+ private void debugRequestEntity(InputStream inputStream) throws IOException {
+ if (!Debug.debugRequestHeadersOnly()) {
+ String entity = IOUtils.toString(inputStream);
+ Debug.addRequestDebug("ZUUL:: > "+entity);
+ }
+ }
+
+
+
+ private HttpResponse forward(RestClient restClient, Verb verb, String uri, MultivaluedMap headers,
+ MultivaluedMap params, InputStream requestEntity) throws Exception {
+ 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.getFallbackException() != null &&
+ e.getFallbackException().getCause() != null &&
+ e.getFallbackException().getCause() instanceof ClientException) {
+ ClientException ex = (ClientException) e.getFallbackException().getCause();
+ throw new ZuulException(ex, "Forwarding error", 500, ex.getErrorType().toString());
+ }
+ throw new ZuulException(e, "Forwarding error", 500, e.getFailureType().toString());
+ }
+
+ }
+
+
+ private InputStream getRequestBody(HttpServletRequest request) {
+ InputStream requestEntity = null;
+ try {
+ requestEntity = (InputStream) RequestContext.getCurrentContext().get("requestEntity");
+ if (requestEntity == null) {
+ requestEntity = request.getInputStream();
+ }
+ } catch (IOException e) {
+ LOG.error("Error during getRequestBody", e);
+ }
+
+ return requestEntity;
+ }
+
+ private MultivaluedMap buildZuulRequestQueryParams(HttpServletRequest request) {
+
+ Map> map = HTTPRequestUtils.getInstance().getQueryParams();
+
+ MultivaluedMap params = new MultivaluedMapImpl();
+ if (map == null) return params;
+
+ for (String key : params.keySet()) {
+
+ for (String value : params.get(key)) {
+ params.add(key, value);
+ }
+ }
+ return params;
+ }
+
+ private MultivaluedMap buildZuulRequestHeaders(HttpServletRequest request) {
+
+ RequestContext context = RequestContext.getCurrentContext();
+
+ MultivaluedMap headers = new MultivaluedMapImpl();
+ Enumeration headerNames = request.getHeaderNames();
+ if (headerNames != null) {
+ 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();
+
+ for (String header : zuulRequestHeaders.keySet()) {
+ headers.putSingle(header, zuulRequestHeaders.get(header));
+ }
+
+ 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) throws ClientException, IOException {
+ RequestContext context = RequestContext.getCurrentContext();
+
+ context.setResponseStatusCode(resp.getStatus());
+ if (resp.hasEntity()) {
+ context.setResponseDataStream(resp.getInputStream());
+ }
+
+ String contentEncoding = null;
+ Collection contentEncodingHeader = resp.getHeaders().get(CONTENT_ENCODING);
+ if (contentEncodingHeader != null && !contentEncodingHeader.isEmpty()) {
+ contentEncoding = contentEncodingHeader.iterator().next();
+ }
+
+ if (contentEncoding != null && HTTPRequestUtils.getInstance().isGzipped(contentEncoding)) {
+ context.setResponseGZipped(true);
+ } else {
+ context.setResponseGZipped(false);
+ }
+
+ if (Debug.debugRequest()) {
+ for (String key : resp.getHeaders().keySet()) {
+ boolean isValidHeader = isValidHeader(key);
+
+ Collection list = resp.getHeaders().get(key);
+ for (String header : list) {
+ context.addOriginResponseHeader(key, header);
+
+ if (key.equalsIgnoreCase("content-length"))
+ context.setOriginContentLength(header);
+
+ if (isValidHeader) {
+ context.addZuulResponseHeader(key, header);
+ Debug.addRequestDebug(String.format("ORIGIN_RESPONSE:: < %s %s", key, header));
+ }
+ }
+ }
+
+ if (context.getResponseDataStream() != null) {
+ byte[] origBytes = IOUtils.toByteArray(context.getResponseDataStream());
+ InputStream inStream = new ByteArrayInputStream(origBytes);
+ if (context.getResponseGZipped())
+ inStream = new GZIPInputStream(inStream);
+ String responseEntity = IOUtils.toString(inStream);
+ Debug.addRequestDebug("ORIGIN_RESPONSE:: < "+responseEntity);
+ context.setResponseDataStream(new ByteArrayInputStream(origBytes));
+ }
+
+ } else {
+ for (String key : resp.getHeaders().keySet()) {
+ boolean isValidHeader = isValidHeader(key);
+ Collection list = resp.getHeaders().get(key);
+ for (String header : list) {
+ 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;
+ }
+ }
+
+}
+
+
diff --git a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/route/SimpleHostRoutingFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/route/SimpleHostRoutingFilter.java
new file mode 100644
index 00000000..e63e950a
--- /dev/null
+++ b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/route/SimpleHostRoutingFilter.java
@@ -0,0 +1,456 @@
+package org.springframework.platform.netflix.zuul.filters.route;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+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.commons.io.IOUtils;
+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.HttpEntityEnclosingRequestBase;
+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.annotation.Nullable;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.servlet.http.HttpServletRequest;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.GZIPInputStream;
+
+public 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
+ public 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 CLIENT = new AtomicReference(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
+ public 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() throws Exception {
+
+ 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));
+
+ ThreadSafeClientConnManager 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
+ public String filterType() {
+ return "route";
+ }
+
+ @Override
+ public int filterOrder() {
+ return 100;
+ }
+
+ public boolean shouldFilter() {
+ return RequestContext.getCurrentContext().getRouteHost() != null &&
+ RequestContext.getCurrentContext().sendZuulResponse();
+ }
+
+ private static final void loadClient() {
+ final HttpClient oldClient = CLIENT.get();
+ CLIENT.set(newClient());
+ if (oldClient != null) {
+ CONNECTION_MANAGER_TIMER.schedule(new TimerTask() {
+ @Override
+ public 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
+ try {
+ DefaultHttpClient 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
+ public boolean isRedirected(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) {
+ return false;
+ }
+
+ @Override
+ public org.apache.http.client.methods.HttpUriRequest getRedirect(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) {
+ return null;
+ }
+ });
+ return httpclient;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Object run() {
+ HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
+ Header[] headers = buildZuulRequestHeaders(request);
+ String verb = getVerb(request);
+ InputStream requestEntity = getRequestBody(request);
+ HttpClient httpclient = CLIENT.get();
+
+ String uri = request.getRequestURI();
+ if (RequestContext.getCurrentContext().get("requestURI") != null) {
+ uri = (String) RequestContext.getCurrentContext().get("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 new RuntimeException(e);
+ }
+ return null;
+ }
+
+ private InputStream debug(HttpClient httpclient, String verb, String uri, HttpServletRequest request, Header[] headers, InputStream requestEntity) throws Exception {
+
+ if (Debug.debugRequest()) {
+
+ Debug.addRequestDebug("ZUUL:: host="+RequestContext.getCurrentContext().getRouteHost());
+
+ for (Header header : headers) {
+ Debug.addRequestDebug(String.format("ZUUL::> %s %s", header.getName(), header.getValue()));
+ }
+
+ Debug.addRequestDebug(String.format("ZUUL:: > ${verb} ${uri}?${query} HTTP/1.1", verb, uri, request.getQueryString()));
+ if (requestEntity != null) {
+ requestEntity = debugRequestEntity(requestEntity);
+ }
+
+ }
+ return requestEntity;
+ }
+
+ private InputStream debugRequestEntity(InputStream inputStream) throws Exception {
+ if (Debug.debugRequestHeadersOnly()) return inputStream;
+ if (inputStream == null) return null;
+ String entity = IOUtils.toString(inputStream);
+ Debug.addRequestDebug("ZUUL::> "+entity);
+ return new ByteArrayInputStream(entity.getBytes());
+ }
+
+ private HttpResponse forward(HttpClient httpclient, String verb, String uri, HttpServletRequest request, Header[] headers, InputStream requestEntity) throws Exception {
+
+ requestEntity = debug(httpclient, verb, uri, request, headers, requestEntity);
+
+ HttpHost httpHost = getHttpHost();
+
+ HttpRequest httpRequest;
+
+ switch (verb) {
+ case "POST":
+ HttpPost httpPost = new HttpPost(uri + getQueryString());
+ httpRequest = httpPost;
+ InputStreamEntity entity = new InputStreamEntity(requestEntity, request.getContentLength());
+ httpPost.setEntity(entity);
+ break;
+ case "PUT":
+ HttpPut httpPut = new HttpPut(uri + getQueryString());
+ httpRequest = httpPut;
+ InputStreamEntity entity2 = new InputStreamEntity(requestEntity, request.getContentLength());
+ httpPut.setEntity(entity2);
+ 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();
+ }
+
+ }
+
+ private HttpResponse forwardRequest(HttpClient httpclient, HttpHost httpHost, HttpRequest httpRequest) throws IOException {
+ return httpclient.execute(httpHost, httpRequest);
+ }
+
+ String getQueryString() {
+ HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
+ String query = request.getQueryString();
+ return (query != null) ? "?"+query : "";
+ }
+
+ HttpHost getHttpHost() {
+ URL host = RequestContext.getCurrentContext().getRouteHost();
+
+ HttpHost httpHost = new HttpHost(host.getHost(), host.getPort(), host.getProtocol());
+
+ return httpHost;
+ }
+
+
+ private InputStream getRequestBody(HttpServletRequest request) {
+ InputStream 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.getCurrentContext().getResponseGZipped()) {
+ if (name.toLowerCase().contains("accept-encoding")) return false;
+ }
+ return true;
+ }
+
+ private Header[] buildZuulRequestHeaders(HttpServletRequest request) {
+
+ ArrayList 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();
+
+ for (String it : zuulRequestHeaders.keySet()) {
+ final String name = it.toLowerCase();
+ Optional h = Iterables.tryFind(headers, new Predicate() {
+ @Override
+ public boolean apply(@Nullable Header input) {
+ return input.getName().equals(name);
+ }
+ });
+ if (h.isPresent()) {
+ headers.remove(h);
+ }
+ headers.add(new BasicHeader(it, zuulRequestHeaders.get(it)));
+ }
+
+ if (RequestContext.getCurrentContext().getResponseGZipped()) {
+ headers.add(new BasicHeader("accept-encoding", "deflate, gzip"));
+ }
+ return headers.toArray(new Header[0]);
+ }
+
+ private String getVerb(HttpServletRequest request) {
+ String sMethod = request.getMethod();
+ return sMethod.toUpperCase();
+ }
+
+ private 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";
+ }
+
+ private void setResponse(HttpResponse response) throws IOException {
+ RequestContext context = RequestContext.getCurrentContext();
+
+ RequestContext.getCurrentContext().set("hostZuulResponse", response);
+ RequestContext.getCurrentContext().setResponseStatusCode(response.getStatusLine().getStatusCode());
+ if (response.getEntity() != null) {
+ RequestContext.getCurrentContext().setResponseDataStream(response.getEntity().getContent());
+ }
+
+ boolean isOriginResponseGzipped = false;
+
+ for (Header h : response.getHeaders(CONTENT_ENCODING)) {
+ if (HTTPRequestUtils.getInstance().isGzipped(h.getValue())) {
+ isOriginResponseGzipped = true;
+ break;
+ }
+ }
+ context.setResponseGZipped(isOriginResponseGzipped);
+
+ if (Debug.debugRequest()) {
+ for (Header header : response.getAllHeaders()) {
+ if (isValidHeader(header)) {
+ RequestContext.getCurrentContext().addZuulResponseHeader(header.getName(), header.getValue());
+ Debug.addRequestDebug("ORIGIN_RESPONSE:: < " + header.getName() +","+ header.getValue());
+ }
+ }
+
+ if (context.getResponseDataStream() != null) {
+ byte[] origBytes = IOUtils.toByteArray(context.getResponseDataStream());
+ ByteArrayInputStream byteStream = new ByteArrayInputStream(origBytes);
+ InputStream inputStream = byteStream;
+ if (RequestContext.getCurrentContext().getResponseGZipped()) {
+ inputStream = new GZIPInputStream(byteStream);
+ }
+
+
+ context.setResponseDataStream(new ByteArrayInputStream(origBytes));
+ }
+
+ } else {
+ for (Header header : response.getAllHeaders()) {
+ RequestContext ctx = RequestContext.getCurrentContext();
+ ctx.addOriginResponseHeader(header.getName(), header.getValue());
+
+ if (header.getName().equalsIgnoreCase("content-length"))
+ ctx.setOriginContentLength(header.getValue());
+
+ if (isValidHeader(header)) {
+ ctx.addZuulResponseHeader(header.getName(), header.getValue());
+ }
+ }
+ }
+
+ }
+
+ boolean isValidHeader(Header header) {
+ switch (header.getName().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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-platform-netflix-core/src/test/java/org/springframework/platform/netflix/zuul/sample/Application.java b/spring-platform-netflix-core/src/test/java/org/springframework/platform/netflix/zuul/sample/Application.java
new file mode 100644
index 00000000..322c1b6a
--- /dev/null
+++ b/spring-platform-netflix-core/src/test/java/org/springframework/platform/netflix/zuul/sample/Application.java
@@ -0,0 +1,34 @@
+package org.springframework.platform.netflix.zuul.sample;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.platform.netflix.eureka.EnableEurekaClient;
+import org.springframework.platform.netflix.zuul.EnableZuulProxy;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Configuration
+@ComponentScan
+@EnableAutoConfiguration
+@RestController
+@EnableZuulProxy
+@EnableEurekaClient
+public class Application {
+
+ @RequestMapping("/testing123")
+ public String testing123() {
+ return "testing123";
+ }
+
+ @RequestMapping("/")
+ public String home() {
+ return "Hello world";
+ }
+
+ public static void main(String[] args) {
+ new SpringApplicationBuilder(Application.class).web(true).run(args);
+ }
+
+}
diff --git a/spring-platform-netflix-core/src/test/java/org/springframework/platform/netflix/zuul/sample/ApplicationTests.java b/spring-platform-netflix-core/src/test/java/org/springframework/platform/netflix/zuul/sample/ApplicationTests.java
new file mode 100644
index 00000000..b6302d97
--- /dev/null
+++ b/spring-platform-netflix-core/src/test/java/org/springframework/platform/netflix/zuul/sample/ApplicationTests.java
@@ -0,0 +1,18 @@
+package org.springframework.platform.netflix.zuul.sample;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = Application.class)
+@WebAppConfiguration
+public class ApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/spring-platform-netflix-core/src/test/resources/application.yml b/spring-platform-netflix-core/src/test/resources/application.yml
index 4ddb02df..139ea8cf 100644
--- a/spring-platform-netflix-core/src/test/resources/application.yml
+++ b/spring-platform-netflix-core/src/test/resources/application.yml
@@ -1,12 +1,14 @@
server:
- port: 9000
+ port: 9999
spring:
application:
- name: client
+ name: testclient
eureka:
server:
enabled: false
- client:
- serviceUrl:
- defaultZone: http://localhost:8080/v2/
- default.defaultZone: http://localhost:8080/v2/
+
+zuul:
+ proxy:
+ route:
+ testclient: /testing123
+ stores: /stores
diff --git a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/Application.java b/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/Application.java
index d7fd7838..46f5c6c7 100644
--- a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/Application.java
+++ b/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/Application.java
@@ -8,6 +8,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.platform.netflix.endpoint.HystrixStreamEndpoint;
+import org.springframework.platform.netflix.zuul.Routes;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.ArrayList;
diff --git a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/FilterIntializer.java b/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/FilterIntializer.java
index 1e772df3..7470fda2 100644
--- a/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/FilterIntializer.java
+++ b/spring-platform-netflix-zuul/src/main/java/io/spring/platform/netflix/zuul/FilterIntializer.java
@@ -11,7 +11,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
-//import javax.servlet.http.HttpSessionEvent;
/**
* User: spencergibb
@@ -19,7 +18,7 @@ import javax.servlet.ServletContextListener;
* Time: 9:23 PM
* TODO: .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
*/
-public class FilterIntializer implements ServletContextListener/*, HttpSessionListener*/ {
+public class FilterIntializer implements ServletContextListener {
private static final Logger LOGGER = LoggerFactory.getLogger(FilterIntializer.class);
@@ -27,9 +26,6 @@ public class FilterIntializer implements ServletContextListener/*, HttpSessionLi
ZuulProperties props;
@Override
- /*public void sessionCreated(HttpSessionEvent se) {
- contextInitialized(null);
- }*/
public void contextInitialized(ServletContextEvent sce) {
LOGGER.info("Starting filter initialzer context listener");
@@ -42,9 +38,6 @@ public class FilterIntializer implements ServletContextListener/*, HttpSessionLi
}
@Override
- /*public void sessionDestroyed(HttpSessionEvent se) {
- contextDestroyed(null);
- }*/
public void contextDestroyed(ServletContextEvent sce) {
LOGGER.info("Stopping filter initializer context listener");
}
diff --git a/spring-platform-netflix-zuul/src/main/resources/filters/pre/PreDecorationFilter.groovy b/spring-platform-netflix-zuul/src/main/resources/filters/pre/PreDecorationFilter.groovy
index 307b33b6..ed905eab 100644
--- a/spring-platform-netflix-zuul/src/main/resources/filters/pre/PreDecorationFilter.groovy
+++ b/spring-platform-netflix-zuul/src/main/resources/filters/pre/PreDecorationFilter.groovy
@@ -3,8 +3,8 @@ package filters.pre
import com.netflix.zuul.context.RequestContext
import org.slf4j.Logger
import org.slf4j.LoggerFactory
-import io.spring.platform.netflix.zuul.Routes
-import io.spring.platform.netflix.zuul.SpringFilter
+import org.springframework.platform.netflix.zuul.Routes
+import org.springframework.platform.netflix.zuul.SpringFilter
class PreDecorationFilter extends SpringFilter {
private static Logger LOG = LoggerFactory.getLogger(PreDecorationFilter.class);
diff --git a/spring-platform-netflix-zuul/src/main/resources/filters/route/RibbonRequest.groovy b/spring-platform-netflix-zuul/src/main/resources/filters/route/RibbonRequest.groovy
index bddecebe..7509d029 100644
--- a/spring-platform-netflix-zuul/src/main/resources/filters/route/RibbonRequest.groovy
+++ b/spring-platform-netflix-zuul/src/main/resources/filters/route/RibbonRequest.groovy
@@ -15,14 +15,14 @@ import com.netflix.zuul.util.HTTPRequestUtils
import com.sun.jersey.core.util.MultivaluedMapImpl
import org.slf4j.Logger
import org.slf4j.LoggerFactory
-import io.spring.platform.netflix.zuul.RibbonCommand
+import org.springframework.platform.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.platform.netflix.feign.FeignConfigurer.setServiceListClass
+import static org.springframework.platform.netflix.feign.FeignConfigurer.setServiceListClassAndVIP
class RibbonRequest extends ZuulFilter {
@@ -57,7 +57,7 @@ class RibbonRequest extends ZuulFilter {
def serviceId = context.get("serviceId")
//TODO: can this be set be default? or an implementation of an interface?
- setServiceListClass(serviceId)
+ setServiceListClassAndVIP(serviceId)
IClient restClient = ClientFactory.getNamedClient(serviceId);