diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java index 1ab4ce72..a811728c 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulConfiguration.java @@ -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() { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java index 217fb392..dba24d39 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java @@ -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 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 route = tryFind(routesMap.keySet(), new Predicate() { - @Override - public boolean apply(@Nullable String path) { - return pathMatcher.match(path, uriPart); - } - }); + Map routesMap = routeLocator.getRoutes(); - if (route.isPresent()) { - String serviceId = routesMap.get(route.get()); + Optional route = tryFind(routesMap.keySet(), new Predicate() { + @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); + } + } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java index 29290ea6..b96bad22 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java @@ -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; } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/RouteLocatorTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/RouteLocatorTests.java index ef94e3a9..196755ba 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/RouteLocatorTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/RouteLocatorTests.java @@ -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 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 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 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 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 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 routesMap, String serviceId) { + assertMapping(routesMap, serviceId, serviceId); + } + + protected void assertMapping(Map 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) { diff --git a/spring-cloud-netflix-core/src/test/resources/application.yml b/spring-cloud-netflix-core/src/test/resources/application.yml index 39d6078b..50365afe 100644 --- a/spring-cloud-netflix-core/src/test/resources/application.yml +++ b/spring-cloud-netflix-core/src/test/resources/application.yml @@ -17,5 +17,5 @@ zuul: #mapping: /api #strip-mapping: true route: - testclient: /testing123 - #stores: / + testclient: /testing123/** + http://localhost:8081: /stores/**