Move debug logging to TRaceRepository

This commit is contained in:
Dave Syer
2014-08-22 14:46:28 +01:00
parent ea80ac1700
commit 403bd8d7a2
7 changed files with 891 additions and 913 deletions

View File

@@ -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();
}
}

View File

@@ -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<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
String debugHeader = "";
private void addResponseHeaders() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
@SuppressWarnings("unchecked")
List<String> rd = (List<String>) RequestContext.getCurrentContext().get("routingDebug");
for (String it : rd) {
debugHeader += "[[["+it+"]]]";
}
@SuppressWarnings("unchecked")
List<String> rd = (List<String>) 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<String>) RequestContext.getCurrentContext().get("requestDebug");
rd?.each {
debugHeader += "[[[REQUEST_DEBUG::${it}]]]";
}
*/
if (zuulResponseHeaders != null) {
for (Pair<String, String> 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<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);
}
}
// 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);
}
}
}

View File

@@ -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<String> rd = (List<String>) RequestContext.getCurrentContext().get("requestDebug");
if (rd != null) {
for (String it : rd) {
System.out.println("REQUEST_DEBUG::" + it);
}
}
}
public void dumpRoutingDebug() {
@SuppressWarnings("unchecked")
List<String> rd = (List<String>) RequestContext.getCurrentContext().get("routingDebug");
if (rd != null) {
for (String it : rd) {
System.out.println("ZUUL_DEBUG::"+it);
}
}
}
}

View File

@@ -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() {

View File

@@ -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<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;
}
}

View File

@@ -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<String, String> headers = buildZuulRequestHeaders(request);
MultivaluedMap<String, String> 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<String, String> headers = buildZuulRequestHeaders(request);
MultivaluedMap<String, String> 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<String, String> headers,
MultivaluedMap<String, String> 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<String, Object> debug(Verb verb, String uri,
MultivaluedMap<String, String> headers,
MultivaluedMap<String, String> 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<String, Object> info = new LinkedHashMap<String, Object>();
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<String, Object> trace = new LinkedHashMap<String, Object>();
Map<String, Object> input = new LinkedHashMap<String, Object>();
trace.put("request", input);
info.put("headers", trace);
for (Entry<String, List<String>> entry : headers.entrySet()) {
Collection<String> 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<String, Object> 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<String, String> headers,
MultivaluedMap<String, String> params, InputStream requestEntity) throws Exception {
debug(restClient, verb, uri, headers, params, requestEntity);
private HttpResponse forward(RestClient restClient, Verb verb, String uri,
MultivaluedMap<String, String> headers,
MultivaluedMap<String, String> params, InputStream requestEntity)
throws Exception {
Map<String, Object> 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<String, Object> trace = (Map<String, Object>) info.get("headers");
Map<String, Object> output = new LinkedHashMap<String, Object>();
trace.put("response", output);
info.put("status", response.getStatus());
for (Entry<String, Collection<String>> key : response.getHeaders()
.entrySet()) {
Collection<String> 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<String, String> buildZuulRequestQueryParams(
HttpServletRequest request) {
private MultivaluedMap<String, String> buildZuulRequestQueryParams(HttpServletRequest request) {
Map<String, List<String>> map = HTTPRequestUtils.getInstance().getQueryParams();
Map<String, List<String>> map = HTTPRequestUtils.getInstance().getQueryParams();
MultivaluedMap<String, String> params = new MultivaluedMapImpl();
if (map == null)
return params;
MultivaluedMap<String, String> 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<String, String> buildZuulRequestHeaders(
HttpServletRequest request) {
private MultivaluedMap<String, String> buildZuulRequestHeaders(HttpServletRequest request) {
RequestContext context = RequestContext.getCurrentContext();
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();
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));
}
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<String> 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<java.lang.String> list = resp.getHeaders().get(key);
for (String header : list) {
context.addOriginResponseHeader(key, header);
String contentEncoding = null;
Collection<String> 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<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;
}
}
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;
}
}
}

View File

@@ -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<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();
}
}
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;
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<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();
}
}
}