Allow user to map physical routes in zuul.route
E.g.
zuul:
route:
testclient: /testing123/** # service mapping
http://localhost:8081: /stores/** # physical mapping
This commit is contained in:
@@ -2,9 +2,6 @@ package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
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.boot.actuate.trace.TraceRepository;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@@ -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.Servlet30WrapperFilter;
|
||||
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.Configuration;
|
||||
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.http.ZuulServlet;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@@ -69,7 +70,7 @@ public class ZuulConfiguration {
|
||||
|
||||
@Bean
|
||||
public PreDecorationFilter preDecorationFilter() {
|
||||
return new PreDecorationFilter();
|
||||
return new PreDecorationFilter(routes(), zuulProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -87,6 +88,11 @@ public class ZuulConfiguration {
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SimpleHostRoutingFilter simpleHostRoutingFilter() {
|
||||
return new SimpleHostRoutingFilter();
|
||||
}
|
||||
|
||||
// post filters
|
||||
@Bean
|
||||
public SendResponseFilter sendResponseFilter() {
|
||||
|
||||
@@ -1,91 +1,121 @@
|
||||
package org.springframework.cloud.netflix.zuul.filters.pre;
|
||||
|
||||
import static com.google.common.collect.Iterables.tryFind;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.netflix.zuul.RouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.ZuulProperties;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.netflix.zuul.RouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.ZuulProperties;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.collect.Iterables.*;
|
||||
|
||||
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;
|
||||
|
||||
@Autowired
|
||||
private ZuulProperties properties;
|
||||
private ZuulProperties properties;
|
||||
|
||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 5;
|
||||
}
|
||||
public PreDecorationFilter(RouteLocator routeLocator, ZuulProperties properties) {
|
||||
this.routeLocator = routeLocator;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
String requestURI = ctx.getRequest().getRequestURI();
|
||||
@Override
|
||||
public Object run() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
|
||||
String proxyMapping = properties.getMapping();
|
||||
String requestURI = ctx.getRequest().getRequestURI();
|
||||
|
||||
final String uriPart;
|
||||
if (properties.isStripMapping()) {
|
||||
uriPart = requestURI.replace(proxyMapping, ""); //TODO: better strategy?
|
||||
} else {
|
||||
uriPart = requestURI;
|
||||
}
|
||||
ctx.put("requestURI", uriPart);
|
||||
String proxyMapping = properties.getMapping();
|
||||
|
||||
Map<String, String> routesMap = routeLocator.getRoutes();
|
||||
final String uriPart;
|
||||
if (StringUtils.hasText(proxyMapping) && properties.isStripMapping()
|
||||
&& requestURI.startsWith(proxyMapping)) {
|
||||
// TODO: better strategy?
|
||||
uriPart = requestURI.substring(proxyMapping.length());
|
||||
}
|
||||
else {
|
||||
uriPart = requestURI;
|
||||
}
|
||||
ctx.put("requestURI", uriPart);
|
||||
|
||||
Optional<String> route = tryFind(routesMap.keySet(), new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable String path) {
|
||||
return pathMatcher.match(path, uriPart);
|
||||
}
|
||||
});
|
||||
Map<String, String> routesMap = routeLocator.getRoutes();
|
||||
|
||||
if (route.isPresent()) {
|
||||
String serviceId = routesMap.get(route.get());
|
||||
Optional<String> route = tryFind(routesMap.keySet(), new Predicate<String>() {
|
||||
@Override
|
||||
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);
|
||||
ctx.addOriginResponseHeader("X-Zuul-ServiceId", serviceId);
|
||||
if (route.isPresent()) {
|
||||
String target = routesMap.get(route.get());
|
||||
|
||||
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;
|
||||
}
|
||||
if (target != null) {
|
||||
|
||||
if (target.startsWith("http:") || target.startsWith("https:")) {
|
||||
ctx.setRouteHost(getUrl(target));
|
||||
ctx.addOriginResponseHeader("X-Zuul-Service", target);
|
||||
}
|
||||
else {
|
||||
// set serviceId for use in filters.route.RibbonRequest
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,39 @@
|
||||
package org.springframework.cloud.netflix.zuul.filters.route;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.netflix.config.DynamicIntProperty;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.constants.ZuulConstants;
|
||||
import com.netflix.zuul.context.Debug;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.util.HTTPRequestUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
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.http.Header;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.client.params.ClientPNames;
|
||||
@@ -37,23 +54,16 @@ import org.apache.http.protocol.HttpContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.*;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.netflix.config.DynamicIntProperty;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.constants.ZuulConstants;
|
||||
import com.netflix.zuul.context.Debug;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.util.HTTPRequestUtils;
|
||||
|
||||
public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
|
||||
@@ -100,9 +110,6 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
}, 30000, 5000);
|
||||
}
|
||||
|
||||
public SimpleHostRoutingFilter() {
|
||||
}
|
||||
|
||||
private static final ClientConnectionManager newConnectionManager() throws Exception {
|
||||
|
||||
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
@@ -195,15 +202,16 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
}
|
||||
|
||||
public Object run() {
|
||||
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
HttpServletRequest request = context.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");
|
||||
if (context.get("requestURI") != null) {
|
||||
uri = (String) context.get("requestURI");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -212,10 +220,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
setResponse(response);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (Debug.debugRequest()) {
|
||||
Debug.addRequestDebug("ZUUL:: ERROR " + e.getMessage());
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
context.set("error.exception", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -41,28 +41,66 @@ public class RouteLocatorTests {
|
||||
public void testGetRoutes() {
|
||||
ZuulProperties properties = new ZuulProperties();
|
||||
RouteLocator routeLocator = new RouteLocator(this.discovery, properties);
|
||||
properties.setIgnoredServices(Lists.newArrayList(IGNOREDSERVICE));
|
||||
properties.getRoute().put(ASERVICE, "/"+ASERVICE + "/**");
|
||||
|
||||
Map<String, String> routesMap = routeLocator.getRoutes();
|
||||
|
||||
assertNotNull("routesMap was null", routesMap);
|
||||
assertFalse("routesMap was empty", routesMap.isEmpty());
|
||||
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(MYSERVICE, IGNOREDSERVICE));
|
||||
Lists.newArrayList(IGNOREDSERVICE));
|
||||
|
||||
Map<String, String> routesMap = routeLocator.getRoutes();
|
||||
String serviceId = routesMap.get(getMapping(IGNOREDSERVICE));
|
||||
assertNull("routes did not ignore " + IGNOREDSERVICE, serviceId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoRoutes() {
|
||||
ZuulProperties properties = new ZuulProperties();
|
||||
RouteLocator routeLocator = new RouteLocator(this.discovery, properties);
|
||||
|
||||
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);
|
||||
assertMapping(routesMap, ASERVICE);
|
||||
|
||||
String serviceId = routesMap.get(getMapping(IGNOREDSERVICE));
|
||||
assertNull("routes did not ignore " + IGNOREDSERVICE, serviceId);
|
||||
}
|
||||
|
||||
protected void assertMapping(Map<String, String> routesMap, String expectedServiceId) {
|
||||
String mapping = getMapping(expectedServiceId);
|
||||
String serviceId = routesMap.get(mapping);
|
||||
assertEquals("routesMap had wrong value for " + mapping, expectedServiceId,
|
||||
serviceId);
|
||||
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) {
|
||||
|
||||
@@ -17,5 +17,5 @@ zuul:
|
||||
#mapping: /api
|
||||
#strip-mapping: true
|
||||
route:
|
||||
testclient: /testing123
|
||||
#stores: /
|
||||
testclient: /testing123/**
|
||||
http://localhost:8081: /stores/**
|
||||
|
||||
Reference in New Issue
Block a user