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 index d263dc2b..1045e32c 100644 --- 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 @@ -1,8 +1,10 @@ package org.springframework.platform.netflix.zuul; -import com.netflix.zuul.context.ContextLifecycleFilter; -import com.netflix.zuul.http.ZuulServlet; +import java.util.ArrayList; +import java.util.Collection; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.context.embedded.FilterRegistrationBean; @@ -11,14 +13,12 @@ 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; +import com.netflix.zuul.context.ContextLifecycleFilter; +import com.netflix.zuul.http.ZuulServlet; /** * @author Spencer Gibb @@ -31,6 +31,9 @@ public class ZuulProxyConfiguration { @Autowired private ZuulProxyProperties props; + + @Autowired(required=false) + private TraceRepository traces; @Bean public FilterRegistrationBean contextLifecycleFilter() { @@ -64,11 +67,6 @@ public class ZuulProxyConfiguration { return new DebugFilter(); } - @Bean - public DebugRequestFilter debugRequestFilter() { - return new DebugRequestFilter(); - } - @Bean public PreDecorationFilter preDecorationFilter() { return new PreDecorationFilter(); @@ -77,7 +75,11 @@ public class ZuulProxyConfiguration { // route filters @Bean public RibbonRoutingFilter ribbonRoutingFilter() { - return new RibbonRoutingFilter(); + RibbonRoutingFilter filter = new RibbonRoutingFilter(); + if (traces!=null) { + filter.setTraces(traces); + } + return filter; } // post filters @@ -86,9 +88,4 @@ public class ZuulProxyConfiguration { 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/filters/post/SendResponseFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/post/SendResponseFilter.java index 535565cd..53fa6f9f 100644 --- 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 @@ -1,5 +1,14 @@ package org.springframework.platform.netflix.zuul.filters.post; +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; + +import javax.servlet.http.HttpServletResponse; + import com.google.common.base.Throwables; import com.netflix.config.DynamicBooleanProperty; import com.netflix.config.DynamicIntProperty; @@ -8,175 +17,172 @@ 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 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 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); + static DynamicBooleanProperty SET_CONTENT_LENGTH = DynamicPropertyFactory + .getInstance().getBooleanProperty(ZuulConstants.ZUUL_SET_CONTENT_LENGTH, + false); - @Override - public String filterType() { - return "post"; - } + @Override + public String filterType() { + return "post"; + } - @Override - public int filterOrder() { - return 1000; - } + @Override + public int filterOrder() { + return 1000; + } - public boolean shouldFilter() { - return !RequestContext.getCurrentContext().getZuulResponseHeaders().isEmpty() || - RequestContext.getCurrentContext().getResponseDataStream() != null || - RequestContext.getCurrentContext().getResponseBody() != null; - } + 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; - } + public Object run() { + try { + addResponseHeaders(); + writeResponse(); + } + catch (Exception e) { + Throwables.propagate(e); + } + return null; + } - void writeResponse() throws Exception { - RequestContext context = RequestContext.getCurrentContext(); + void writeResponse() throws Exception { + RequestContext context = RequestContext.getCurrentContext(); - // there is no body to send - if (context.getResponseBody() == null && context.getResponseDataStream() == null) return; + // there is no body to send + if (context.getResponseBody() == null && context.getResponseDataStream() == null) + return; - HttpServletResponse servletResponse = context.getResponse(); - servletResponse.setCharacterEncoding("UTF-8"); + 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; - } + 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; + 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); + 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); - } - } + } + 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(); + } + finally { + try { + if (is != null) + is.close(); - outStream.flush(); - outStream.close(); - } catch (IOException e) { + 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)); -// } + 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(); - } + 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]; - } - } - } + // 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 = ""; + private void addResponseHeaders() { + RequestContext context = RequestContext.getCurrentContext(); + HttpServletResponse servletResponse = context.getResponse(); + List> zuulResponseHeaders = context.getZuulResponseHeaders(); - @SuppressWarnings("unchecked") - List rd = (List) RequestContext.getCurrentContext().get("routingDebug"); - for (String it : rd) { - debugHeader += "[[["+it+"]]]"; - } + @SuppressWarnings("unchecked") + List rd = (List) RequestContext.getCurrentContext().get( + "routingDebug"); + if (rd != null) { + StringBuilder debugHeader = new StringBuilder(); + for (String it : rd) { + debugHeader.append("[[[" + it + "]]]"); + } + if (INCLUDE_DEBUG_HEADER.get()) + servletResponse.addHeader("X-Zuul-Debug-Header", debugHeader.toString()); + } - /* - rd = (List) RequestContext.getCurrentContext().get("requestDebug"); - rd?.each { - debugHeader += "[[[REQUEST_DEBUG::${it}]]]"; - } - */ + if (zuulResponseHeaders != null) { + for (Pair it : zuulResponseHeaders) { + servletResponse.addHeader(it.first(), it.second()); + } + } - if (INCLUDE_DEBUG_HEADER.get()) servletResponse.addHeader("X-Zuul-Debug-Header", debugHeader); + RequestContext ctx = RequestContext.getCurrentContext(); + Integer contentLength = ctx.getOriginContentLength(); - 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); - } - } + // 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 deleted file mode 100644 index 5fc2a944..00000000 --- a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/post/StatsFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -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() { - @SuppressWarnings("unchecked") - List rd = (List) RequestContext.getCurrentContext().get("requestDebug"); - if (rd != null) { - for (String it : rd) { - System.out.println("REQUEST_DEBUG::" + it); - } - } - } - - public void dumpRoutingDebug() { - @SuppressWarnings("unchecked") - 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 index 45cade94..74f05ef1 100644 --- 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 @@ -12,9 +12,9 @@ import javax.servlet.http.HttpServletRequest; public class DebugFilter extends ZuulFilter { static final DynamicBooleanProperty routingDebug = DynamicPropertyFactory.getInstance() - .getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, true); + .getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, false); static final DynamicStringProperty debugParameter = DynamicPropertyFactory.getInstance() - .getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "d"); + .getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "debug"); @Override public String filterType() { 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 deleted file mode 100644 index 5724f3a9..00000000 --- a/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/pre/DebugRequestFilter.java +++ /dev/null @@ -1,62 +0,0 @@ -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/route/RibbonRoutingFilter.java b/spring-platform-netflix-core/src/main/java/org/springframework/platform/netflix/zuul/filters/route/RibbonRoutingFilter.java index b382440c..69f139c9 100644 --- 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 @@ -1,305 +1,340 @@ 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 static org.springframework.platform.netflix.feign.FeignConfigurer.setServiceListClassAndVIP; -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.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.zip.GZIPInputStream; +import java.util.Map.Entry; -import static com.netflix.client.http.HttpRequest.Verb; -import static org.springframework.platform.netflix.feign.FeignConfigurer.setServiceListClassAndVIP; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.actuate.trace.TraceRepository; +import org.springframework.platform.netflix.zuul.RibbonCommand; +import org.springframework.util.StringUtils; + +import com.netflix.client.ClientException; +import com.netflix.client.ClientFactory; +import com.netflix.client.http.HttpRequest.Verb; +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.RequestContext; +import com.netflix.zuul.exception.ZuulException; +import com.netflix.zuul.util.HTTPRequestUtils; +import com.sun.jersey.core.util.MultivaluedMapImpl; public class RibbonRoutingFilter extends ZuulFilter { - private static final Logger LOG = LoggerFactory.getLogger(RibbonRoutingFilter.class); + private static final Logger LOG = LoggerFactory.getLogger(RibbonRoutingFilter.class); - public static final String CONTENT_ENCODING = "Content-Encoding"; + public static final String CONTENT_ENCODING = "Content-Encoding"; - @Override - public String filterType() { - return "route"; - } + private TraceRepository traces; - @Override - public int filterOrder() { - return 10; - } + public void setTraces(TraceRepository traces) { + this.traces = traces; + } - public boolean shouldFilter() { - RequestContext ctx = RequestContext.getCurrentContext(); - return (ctx.getRouteHost() == null && ctx.get("serviceId") != null && ctx.sendZuulResponse()); - } + @Override + public String filterType() { + return "route"; + } - public Object run() { - RequestContext context = RequestContext.getCurrentContext(); - HttpServletRequest request = context.getRequest(); + @Override + public int filterOrder() { + return 10; + } - MultivaluedMap headers = buildZuulRequestHeaders(request); - MultivaluedMap params = buildZuulRequestQueryParams(request); - Verb verb = getVerb(request); - InputStream requestEntity = getRequestBody(request); + public boolean shouldFilter() { + RequestContext ctx = RequestContext.getCurrentContext(); + return (ctx.getRouteHost() == null && ctx.get("serviceId") != null && ctx + .sendZuulResponse()); + } - String serviceId = (String) context.get("serviceId"); + public Object run() { + RequestContext context = RequestContext.getCurrentContext(); + HttpServletRequest request = context.getRequest(); - //TODO: can this be set be default? or an implementation of an interface? - setServiceListClassAndVIP(serviceId); + MultivaluedMap headers = buildZuulRequestHeaders(request); + MultivaluedMap params = buildZuulRequestQueryParams(request); + Verb verb = getVerb(request); + InputStream requestEntity = getRequestBody(request); - RestClient restClient = (RestClient) ClientFactory.getNamedClient(serviceId); + String serviceId = (String) context.get("serviceId"); - String uri = request.getRequestURI(); - if (context.get("requestURI") != null) { - uri = (String) context.get("requestURI"); - } - //remove double slashes - uri = uri.replace("//", "/"); + // TODO: can this be set be default? or an implementation of an interface? + setServiceListClassAndVIP(serviceId); - try { - HttpResponse response = forward(restClient, verb, uri, headers, params, requestEntity); - setResponse(response); - return response; - } catch (Exception e) { - throw new RuntimeException(e); - } - } + RestClient restClient = (RestClient) ClientFactory.getNamedClient(serviceId); - private void debug(RestClient restClient, Verb verb, String uri, MultivaluedMap headers, - MultivaluedMap params, InputStream requestEntity) throws IOException { + String uri = request.getRequestURI(); + if (context.get("requestURI") != null) { + uri = (String) context.get("requestURI"); + } + // remove double slashes + uri = uri.replace("//", "/"); - if (Debug.debugRequest()) { + try { + HttpResponse response = forward(restClient, verb, uri, headers, params, + requestEntity); + setResponse(response); + return response; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } - 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("&"); - } - } + private Map debug(Verb verb, String uri, + MultivaluedMap headers, + MultivaluedMap params, InputStream requestEntity) + throws IOException { - 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()); - } - } - } - } + Map info = new LinkedHashMap(); + if (traces != null) { - private void debugRequestEntity(InputStream inputStream) throws IOException { - if (!Debug.debugRequestHeadersOnly()) { - String entity = IOUtils.toString(inputStream); - Debug.addRequestDebug("ZUUL:: > "+entity); - } - } + RequestContext context = RequestContext.getCurrentContext(); + info.put("remote", true); + info.put("servideId", context.get("serviceId")); + Map trace = new LinkedHashMap(); + Map input = new LinkedHashMap(); + trace.put("request", input); + info.put("headers", trace); + for (Entry> entry : headers.entrySet()) { + Collection collection = entry.getValue(); + Object value = collection; + if (collection.size() < 2) { + value = collection.isEmpty() ? "" : collection.iterator().next(); + } + input.put(entry.getKey(), value); + } + 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("&"); + } + } + info.put("method", verb.verb()); + info.put("uri", uri); + info.put("query", query.toString()); + RequestContext ctx = RequestContext.getCurrentContext(); + if (!ctx.isChunkedRequestBody()) { + if (requestEntity != null) { + debugRequestEntity(info, ctx.getRequest().getInputStream()); + } + } + traces.add(info); + return info; + } + return info; + } + private void debugRequestEntity(Map info, InputStream inputStream) + throws IOException { + String entity = IOUtils.toString(inputStream); + if (StringUtils.hasText(entity)) { + info.put("body", 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); + private HttpResponse forward(RestClient restClient, Verb verb, String uri, + MultivaluedMap headers, + MultivaluedMap params, InputStream requestEntity) + throws Exception { + + Map info = debug(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()); - } + RibbonCommand command = new RibbonCommand(restClient, verb, uri, headers, params, + requestEntity); + try { + HttpResponse response = command.execute(); + if (traces != null) { + @SuppressWarnings("unchecked") + Map trace = (Map) info.get("headers"); + Map output = new LinkedHashMap(); + trace.put("response", output); + info.put("status", response.getStatus()); + for (Entry> key : response.getHeaders() + .entrySet()) { + Collection collection = key.getValue(); + Object value = collection; + if (collection.size() < 2) { + value = collection.isEmpty() ? "" : collection.iterator().next(); + } + output.put(key.getKey(), value); + } + } + return response; + } + catch (HystrixRuntimeException e) { + info.put("status", 500); + 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); + } - 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; + } - return requestEntity; - } + private MultivaluedMap buildZuulRequestQueryParams( + HttpServletRequest request) { - private MultivaluedMap buildZuulRequestQueryParams(HttpServletRequest request) { + Map> map = HTTPRequestUtils.getInstance().getQueryParams(); - Map> map = HTTPRequestUtils.getInstance().getQueryParams(); + MultivaluedMap params = new MultivaluedMapImpl(); + if (map == null) + return params; - MultivaluedMap params = new MultivaluedMapImpl(); - if (map == null) return params; + for (String key : map.keySet()) { - for (String key : map.keySet()) { + for (String value : map.get(key)) { + params.add(key, value); + } + } + return params; + } - for (String value : map.get(key)) { - params.add(key, value); - } - } - return params; - } + private MultivaluedMap buildZuulRequestHeaders( + HttpServletRequest request) { - private MultivaluedMap buildZuulRequestHeaders(HttpServletRequest request) { + RequestContext context = RequestContext.getCurrentContext(); - 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(); - 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)); + } - for (String header : zuulRequestHeaders.keySet()) { - headers.putSingle(header, zuulRequestHeaders.get(header)); - } + headers.putSingle("accept-encoding", "deflate, gzip"); - headers.putSingle("accept-encoding", "deflate, gzip"); + if (headers.containsKey("transfer-encoding")) + headers.remove("transfer-encoding"); - if (headers.containsKey("transfer-encoding")) - headers.remove("transfer-encoding"); + if (headers.containsKey("host")) + headers.remove("host"); - if (headers.containsKey("host")) - headers.remove("host"); + return headers; + } - 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(); - Verb getVerb(HttpServletRequest request) { - String sMethod = request.getMethod(); - return getVerb(sMethod); - } + context.setResponseStatusCode(resp.getStatus()); + if (resp.hasEntity()) { + context.setResponseDataStream(resp.getInputStream()); + } - 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; - } + String contentEncoding = null; + Collection contentEncodingHeader = resp.getHeaders() + .get(CONTENT_ENCODING); + if (contentEncodingHeader != null && !contentEncodingHeader.isEmpty()) { + contentEncoding = contentEncodingHeader.iterator().next(); + } - void setResponse(HttpResponse resp) throws ClientException, IOException { - RequestContext context = RequestContext.getCurrentContext(); + if (contentEncoding != null + && HTTPRequestUtils.getInstance().isGzipped(contentEncoding)) { + context.setResponseGZipped(true); + } + else { + context.setResponseGZipped(false); + } - context.setResponseStatusCode(resp.getStatus()); - if (resp.hasEntity()) { - context.setResponseDataStream(resp.getInputStream()); - } + for (String key : resp.getHeaders().keySet()) { + boolean isValidHeader = isValidHeader(key); + Collection list = resp.getHeaders().get(key); + for (String header : list) { + context.addOriginResponseHeader(key, header); - String contentEncoding = null; - Collection contentEncodingHeader = resp.getHeaders().get(CONTENT_ENCODING); - if (contentEncodingHeader != null && !contentEncodingHeader.isEmpty()) { - contentEncoding = contentEncodingHeader.iterator().next(); - } + if (key.equalsIgnoreCase("content-length")) + context.setOriginContentLength(header); - if (contentEncoding != null && HTTPRequestUtils.getInstance().isGzipped(contentEncoding)) { - context.setResponseGZipped(true); - } else { - context.setResponseGZipped(false); - } + if (isValidHeader) { + context.addZuulResponseHeader(key, header); + } + } + } - 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; - } - } + 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 index e63e950a..3fc107b1 100644 --- 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 @@ -57,400 +57,453 @@ 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(); - } - } + 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; + httpPost.setEntity(new InputStreamEntity(requestEntity, request + .getContentLength())); + break; + case "PUT": + HttpPut httpPut = new HttpPut(uri + getQueryString()); + httpRequest = httpPut; + httpPut.setEntity(new InputStreamEntity(requestEntity, request + .getContentLength())); + 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