Commit 065d91fd by Dave Syer

Allow user to map physical routes in zuul.route

E.g. zuul: route: testclient: /testing123/** # service mapping http://localhost:8081: /stores/** # physical mapping
parent ce1fcb15
...@@ -2,9 +2,6 @@ package org.springframework.cloud.netflix.zuul; ...@@ -2,9 +2,6 @@ package org.springframework.cloud.netflix.zuul;
import java.util.Map; import java.util.Map;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.http.ZuulServlet;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
...@@ -17,9 +14,13 @@ import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter; ...@@ -17,9 +14,13 @@ import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter; import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter; import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter; import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;
import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.http.ZuulServlet;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
*/ */
...@@ -69,7 +70,7 @@ public class ZuulConfiguration { ...@@ -69,7 +70,7 @@ public class ZuulConfiguration {
@Bean @Bean
public PreDecorationFilter preDecorationFilter() { public PreDecorationFilter preDecorationFilter() {
return new PreDecorationFilter(); return new PreDecorationFilter(routes(), zuulProperties);
} }
@Bean @Bean
...@@ -87,6 +88,11 @@ public class ZuulConfiguration { ...@@ -87,6 +88,11 @@ public class ZuulConfiguration {
return filter; return filter;
} }
@Bean
public SimpleHostRoutingFilter simpleHostRoutingFilter() {
return new SimpleHostRoutingFilter();
}
// post filters // post filters
@Bean @Bean
public SendResponseFilter sendResponseFilter() { public SendResponseFilter sendResponseFilter() {
......
package org.springframework.cloud.netflix.zuul.filters.pre; package org.springframework.cloud.netflix.zuul.filters.pre;
import com.google.common.base.Optional; import static com.google.common.collect.Iterables.tryFind;
import com.google.common.base.Predicate;
import com.netflix.zuul.ZuulFilter; import java.net.MalformedURLException;
import com.netflix.zuul.context.RequestContext; import java.net.URL;
import java.util.Map;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.RouteLocator; import org.springframework.cloud.netflix.zuul.RouteLocator;
import org.springframework.cloud.netflix.zuul.ZuulProperties; import org.springframework.cloud.netflix.zuul.ZuulProperties;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import javax.annotation.Nullable; import com.google.common.base.Optional;
import javax.servlet.http.HttpServletResponse; import com.google.common.base.Predicate;
import java.util.Map; import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import static com.google.common.collect.Iterables.*;
public class PreDecorationFilter extends ZuulFilter { public class PreDecorationFilter extends ZuulFilter {
private static Logger LOG = LoggerFactory.getLogger(PreDecorationFilter.class); private static Logger LOG = LoggerFactory.getLogger(PreDecorationFilter.class);
@Autowired private RouteLocator routeLocator;
private RouteLocator routeLocator;
private ZuulProperties properties;
@Autowired
private ZuulProperties properties; private PathMatcher pathMatcher = new AntPathMatcher();
private PathMatcher pathMatcher = new AntPathMatcher(); public PreDecorationFilter(RouteLocator routeLocator, ZuulProperties properties) {
this.routeLocator = routeLocator;
@Override this.properties = properties;
public int filterOrder() { }
return 5;
} @Override
public int filterOrder() {
@Override return 5;
public String filterType() { }
return "pre";
} @Override
public String filterType() {
@Override return "pre";
public boolean shouldFilter() { }
return true;
} @Override
public boolean shouldFilter() {
@Override return true;
public Object run() { }
RequestContext ctx = RequestContext.getCurrentContext();
@Override
String requestURI = ctx.getRequest().getRequestURI(); public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String proxyMapping = properties.getMapping();
String requestURI = ctx.getRequest().getRequestURI();
final String uriPart;
if (properties.isStripMapping()) { String proxyMapping = properties.getMapping();
uriPart = requestURI.replace(proxyMapping, ""); //TODO: better strategy?
} else { final String uriPart;
uriPart = requestURI; if (StringUtils.hasText(proxyMapping) && properties.isStripMapping()
} && requestURI.startsWith(proxyMapping)) {
ctx.put("requestURI", uriPart); // TODO: better strategy?
uriPart = requestURI.substring(proxyMapping.length());
Map<String, String> routesMap = routeLocator.getRoutes(); }
else {
Optional<String> route = tryFind(routesMap.keySet(), new Predicate<String>() { uriPart = requestURI;
@Override }
public boolean apply(@Nullable String path) { ctx.put("requestURI", uriPart);
return pathMatcher.match(path, uriPart);
} Map<String, String> routesMap = routeLocator.getRoutes();
});
Optional<String> route = tryFind(routesMap.keySet(), new Predicate<String>() {
if (route.isPresent()) { @Override
String serviceId = routesMap.get(route.get()); public boolean apply(@Nullable String path) {
return pathMatcher.match(path, uriPart);
if (serviceId != null) { }
// set serviceId for use in filters.route.RibbonRequest });
ctx.set("serviceId", serviceId);
ctx.setRouteHost(null); if (route.isPresent()) {
ctx.addOriginResponseHeader("X-Zuul-ServiceId", serviceId); String target = routesMap.get(route.get());
if (properties.isAddProxyHeaders()) { if (target != null) {
ctx.addZuulRequestHeader("X-Forwarded-Host", ctx.getRequest().getServerName() + ":" + String.valueOf(ctx.getRequest().getServerPort()));
} if (target.startsWith("http:") || target.startsWith("https:")) {
} ctx.setRouteHost(getUrl(target));
} else { ctx.addOriginResponseHeader("X-Zuul-Service", target);
LOG.warn("No route found for uri: "+requestURI); }
ctx.set("error.status_code", HttpServletResponse.SC_NOT_FOUND); else {
} // set serviceId for use in filters.route.RibbonRequest
return null; ctx.set("serviceId", target);
} ctx.setRouteHost(null);
ctx.addOriginResponseHeader("X-Zuul-ServiceId", target);
}
if (properties.isAddProxyHeaders()) {
ctx.addZuulRequestHeader(
"X-Forwarded-Host",
ctx.getRequest().getServerName() + ":"
+ String.valueOf(ctx.getRequest().getServerPort()));
}
}
}
else {
LOG.warn("No route found for uri: " + requestURI);
ctx.set("error.status_code", HttpServletResponse.SC_NOT_FOUND);
}
return null;
}
private URL getUrl(String target) {
try {
return new URL(target);
}
catch (MalformedURLException e) {
throw new IllegalStateException("Target URL is malformed", e);
}
}
} }
package org.springframework.cloud.netflix.zuul.filters.route; package org.springframework.cloud.netflix.zuul.filters.route;
import com.google.common.base.Optional; import java.io.ByteArrayInputStream;
import com.google.common.base.Predicate; import java.io.IOException;
import com.google.common.collect.Iterables; import java.io.InputStream;
import com.netflix.config.DynamicIntProperty; import java.net.Socket;
import com.netflix.config.DynamicPropertyFactory; import java.net.URL;
import com.netflix.zuul.ZuulFilter; import java.net.UnknownHostException;
import com.netflix.zuul.constants.ZuulConstants; import java.security.KeyManagementException;
import com.netflix.zuul.context.Debug; import java.security.KeyStore;
import com.netflix.zuul.context.RequestContext; import java.security.KeyStoreException;
import com.netflix.zuul.util.HTTPRequestUtils; import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;
import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.ClientPNames;
...@@ -37,23 +54,16 @@ import org.apache.http.protocol.HttpContext; ...@@ -37,23 +54,16 @@ import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import com.google.common.base.Optional;
import javax.net.ssl.SSLContext; import com.google.common.base.Predicate;
import javax.net.ssl.TrustManager; import com.google.common.collect.Iterables;
import javax.net.ssl.X509TrustManager; import com.netflix.config.DynamicIntProperty;
import javax.servlet.http.HttpServletRequest; import com.netflix.config.DynamicPropertyFactory;
import java.io.ByteArrayInputStream; import com.netflix.zuul.ZuulFilter;
import java.io.IOException; import com.netflix.zuul.constants.ZuulConstants;
import java.io.InputStream; import com.netflix.zuul.context.Debug;
import java.net.Socket; import com.netflix.zuul.context.RequestContext;
import java.net.URL; import com.netflix.zuul.util.HTTPRequestUtils;
import java.net.UnknownHostException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;
public class SimpleHostRoutingFilter extends ZuulFilter { public class SimpleHostRoutingFilter extends ZuulFilter {
...@@ -100,9 +110,6 @@ public class SimpleHostRoutingFilter extends ZuulFilter { ...@@ -100,9 +110,6 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
}, 30000, 5000); }, 30000, 5000);
} }
public SimpleHostRoutingFilter() {
}
private static final ClientConnectionManager newConnectionManager() throws Exception { private static final ClientConnectionManager newConnectionManager() throws Exception {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
...@@ -195,15 +202,16 @@ public class SimpleHostRoutingFilter extends ZuulFilter { ...@@ -195,15 +202,16 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
} }
public Object run() { public Object run() {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
Header[] headers = buildZuulRequestHeaders(request); Header[] headers = buildZuulRequestHeaders(request);
String verb = getVerb(request); String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request); InputStream requestEntity = getRequestBody(request);
HttpClient httpclient = CLIENT.get(); HttpClient httpclient = CLIENT.get();
String uri = request.getRequestURI(); String uri = request.getRequestURI();
if (RequestContext.getCurrentContext().get("requestURI") != null) { if (context.get("requestURI") != null) {
uri = (String) RequestContext.getCurrentContext().get("requestURI"); uri = (String) context.get("requestURI");
} }
try { try {
...@@ -212,10 +220,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter { ...@@ -212,10 +220,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
setResponse(response); setResponse(response);
} }
catch (Exception e) { catch (Exception e) {
if (Debug.debugRequest()) { context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
Debug.addRequestDebug("ZUUL:: ERROR " + e.getMessage()); context.set("error.exception", e);
}
throw new RuntimeException(e);
} }
return null; return null;
} }
......
...@@ -41,28 +41,66 @@ public class RouteLocatorTests { ...@@ -41,28 +41,66 @@ public class RouteLocatorTests {
public void testGetRoutes() { public void testGetRoutes() {
ZuulProperties properties = new ZuulProperties(); ZuulProperties properties = new ZuulProperties();
RouteLocator routeLocator = new RouteLocator(this.discovery, properties); RouteLocator routeLocator = new RouteLocator(this.discovery, properties);
properties.setIgnoredServices(Lists.newArrayList(IGNOREDSERVICE));
properties.getRoute().put(ASERVICE, "/"+ASERVICE + "/**"); properties.getRoute().put(ASERVICE, "/"+ASERVICE + "/**");
when(discovery.getServices()).thenReturn(
Lists.newArrayList(MYSERVICE, IGNOREDSERVICE));
Map<String, String> routesMap = routeLocator.getRoutes(); Map<String, String> routesMap = routeLocator.getRoutes();
assertNotNull("routesMap was null", routesMap); assertNotNull("routesMap was null", routesMap);
assertFalse("routesMap was empty", routesMap.isEmpty()); assertFalse("routesMap was empty", routesMap.isEmpty());
assertMapping(routesMap, MYSERVICE);
assertMapping(routesMap, ASERVICE); assertMapping(routesMap, ASERVICE);
}
@Test
public void testGetPhysicalRoutes() {
ZuulProperties properties = new ZuulProperties();
RouteLocator routeLocator = new RouteLocator(this.discovery, properties);
properties.getRoute().put("http://" + ASERVICE, "/"+ASERVICE + "/**");
Map<String, String> routesMap = routeLocator.getRoutes();
assertNotNull("routesMap was null", routesMap);
assertFalse("routesMap was empty", routesMap.isEmpty());
assertMapping(routesMap, "http://" + ASERVICE, ASERVICE);
}
@Test
public void testIgnoreRoutes() {
ZuulProperties properties = new ZuulProperties();
RouteLocator routeLocator = new RouteLocator(this.discovery, properties);
properties.setIgnoredServices(Lists.newArrayList(IGNOREDSERVICE));
when(discovery.getServices()).thenReturn(
Lists.newArrayList(IGNOREDSERVICE));
Map<String, String> routesMap = routeLocator.getRoutes();
String serviceId = routesMap.get(getMapping(IGNOREDSERVICE)); String serviceId = routesMap.get(getMapping(IGNOREDSERVICE));
assertNull("routes did not ignore " + IGNOREDSERVICE, serviceId); assertNull("routes did not ignore " + IGNOREDSERVICE, serviceId);
} }
protected void assertMapping(Map<String, String> routesMap, String expectedServiceId) { @Test
String mapping = getMapping(expectedServiceId); public void testAutoRoutes() {
String serviceId = routesMap.get(mapping); ZuulProperties properties = new ZuulProperties();
assertEquals("routesMap had wrong value for " + mapping, expectedServiceId, RouteLocator routeLocator = new RouteLocator(this.discovery, properties);
serviceId);
when(discovery.getServices()).thenReturn(
Lists.newArrayList(MYSERVICE));
Map<String, String> routesMap = routeLocator.getRoutes();
assertNotNull("routesMap was null", routesMap);
assertFalse("routesMap was empty", routesMap.isEmpty());
assertMapping(routesMap, MYSERVICE);
}
protected void assertMapping(Map<String, String> routesMap, String serviceId) {
assertMapping(routesMap, serviceId, serviceId);
}
protected void assertMapping(Map<String, String> routesMap, String expectedRoute, String key) {
String mapping = getMapping(key);
String route = routesMap.get(mapping);
assertEquals("routesMap had wrong value for " + mapping, expectedRoute,
route);
} }
private String getMapping(String serviceId) { private String getMapping(String serviceId) {
......
...@@ -17,5 +17,5 @@ zuul: ...@@ -17,5 +17,5 @@ zuul:
#mapping: /api #mapping: /api
#strip-mapping: true #strip-mapping: true
route: route:
testclient: /testing123 testclient: /testing123/**
#stores: / http://localhost:8081: /stores/**
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment