@EnableZuulProxy initial implementation
This commit is contained in:
@@ -81,6 +81,16 @@
|
||||
<artifactId>ribbon-eureka</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.ribbon</groupId>
|
||||
<artifactId>ribbon-httpclient</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.zuul</groupId>
|
||||
<artifactId>zuul-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -50,11 +50,11 @@ public class FeignConfigurer {
|
||||
|
||||
protected <T> T loadBalance(Feign.Builder builder, Class<T> 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?
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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<String, ZuulFilter> 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<String, ZuulFilter> 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);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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<String, Object> routeEntries = PropertySourceUtils.getSubProperties(propertySources, "zuul.route.");
|
||||
Map<String, Object> routeEntries = PropertySourceUtils.getSubProperties(propertySources, keyPrefix);
|
||||
for (Map.Entry<String, Object> entry : routeEntries.entrySet()) {
|
||||
String serviceId = entry.getKey();
|
||||
String route = entry.getValue().toString();
|
||||
@@ -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;
|
||||
@@ -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<String> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
|
||||
String debugHeader = "";
|
||||
|
||||
List<String> rd = (List<String>) RequestContext.getCurrentContext().get("routingDebug");
|
||||
for (String it : rd) {
|
||||
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 != null) {
|
||||
for (Pair<String, String> it : zuulResponseHeaders) {
|
||||
servletResponse.addHeader(it.first(), it.second());
|
||||
Debug.addRequestDebug("OUTBOUND: < " + it.first() + ":" + it.second());
|
||||
}
|
||||
} else if (zuulResponseHeaders != null) {
|
||||
for (Pair<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String> rd = (List<String>) RequestContext.getCurrentContext().get("requestDebug");
|
||||
if (rd != null) {
|
||||
for (String it : rd) {
|
||||
System.out.println("REQUEST_DEBUG::" + it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpRoutingDebug() {
|
||||
List<String> rd = (List<String>) RequestContext.getCurrentContext().get("routingDebug");
|
||||
if (rd != null) {
|
||||
for (String it : rd) {
|
||||
System.out.println("ZUUL_DEBUG::"+it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, String> routesMap = routes.getRoutes();
|
||||
|
||||
Optional<String> route = Iterables.tryFind(routesMap.keySet(), new Predicate<String>() {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -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<String, String> headers = buildZuulRequestHeaders(request);
|
||||
MultivaluedMap<String, String> 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<String, String> headers,
|
||||
MultivaluedMap<String, String> 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<String, String> headers,
|
||||
MultivaluedMap<String, String> 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<String, String> buildZuulRequestQueryParams(HttpServletRequest request) {
|
||||
|
||||
Map<String, List<String>> map = HTTPRequestUtils.getInstance().getQueryParams();
|
||||
|
||||
MultivaluedMap<String, String> 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<String, String> buildZuulRequestHeaders(HttpServletRequest request) {
|
||||
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
|
||||
MultivaluedMap<String, String> 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<String, String> 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<String> 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<String> 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<java.lang.String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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
|
||||
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<Header> 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<String, String> zuulRequestHeaders = RequestContext.getCurrentContext().getZuulRequestHeaders();
|
||||
|
||||
for (String it : zuulRequestHeaders.keySet()) {
|
||||
final String name = it.toLowerCase();
|
||||
Optional<Header> h = Iterables.tryFind(headers, new Predicate<Header>() {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user