diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java index 15f9d039..c6dc481a 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.event.HeartbeatEvent; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; @@ -59,16 +60,21 @@ public class ZuulProxyConfiguration extends ZuulConfiguration { @Autowired private ZuulProperties zuulProperties; + @Autowired + private ServerProperties server; + @Bean @Override public ProxyRouteLocator routeLocator() { - return new ProxyRouteLocator(this.discovery, this.zuulProperties); + return new ProxyRouteLocator(this.server.getServletPrefix(), this.discovery, + this.zuulProperties); } // pre filters @Bean public PreDecorationFilter preDecorationFilter() { - return new PreDecorationFilter(routeLocator(), this.zuulProperties.isAddProxyHeaders()); + return new PreDecorationFilter(routeLocator(), + this.zuulProperties.isAddProxyHeaders()); } // route filters diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRouteLocator.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRouteLocator.java index 7319dbcf..5a377ce1 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRouteLocator.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRouteLocator.java @@ -52,7 +52,11 @@ public class ProxyRouteLocator implements RouteLocator { private Map staticRoutes = new LinkedHashMap<>(); - public ProxyRouteLocator(DiscoveryClient discovery, ZuulProperties properties) { + private String servletPath; + + public ProxyRouteLocator(String servletPath, DiscoveryClient discovery, + ZuulProperties properties) { + this.servletPath = servletPath; this.discovery = discovery; this.properties = properties; } @@ -89,6 +93,10 @@ public class ProxyRouteLocator implements RouteLocator { String targetPath = null; String id = null; String prefix = this.properties.getPrefix(); + if (StringUtils.hasText(this.servletPath) && !this.servletPath.equals("/") + && path.startsWith(this.servletPath)) { + path = path.substring(this.servletPath.length()); + } Boolean retryable = this.properties.getRetryable(); for (Entry entry : this.routes.get().entrySet()) { String pattern = entry.getKey(); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java index 1d655c98..50f127c2 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java @@ -30,6 +30,7 @@ import org.springframework.boot.test.TestRestTemplate; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.netflix.ribbon.RibbonClients; import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpEntity; @@ -39,6 +40,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -86,7 +88,7 @@ public class SampleZuulProxyApplicationTests { "http://localhost:" + this.port + "/simple/local/1", HttpMethod.GET, new HttpEntity<>((Void) null), String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Gotten!", result.getBody()); + assertEquals("Gotten 1!", result.getBody()); } @Test @@ -97,7 +99,20 @@ public class SampleZuulProxyApplicationTests { "http://localhost:" + this.port + "/self/1", HttpMethod.DELETE, new HttpEntity<>((Void) null), String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Deleted!", result.getBody()); + assertEquals("Deleted 1!", result.getBody()); + } + + @Test + public void stripPrefixFalseAppendsPath() { + this.routes.addRoute(new ZuulRoute("strip", "/strip/**", "strip", + "http://localhost:" + this.port + "/local", false, false)); + this.endpoint.reset(); + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/strip", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + // Prefix not stripped to it goes to /local/strip + assertEquals("Gotten strip!", result.getBody()); } @Test @@ -114,7 +129,7 @@ public class SampleZuulProxyApplicationTests { "http://localhost:" + this.port + "/another/twolevel/local/1", HttpMethod.GET, new HttpEntity<>((Void) null), String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Gotten!", result.getBody()); + assertEquals("Gotten 1!", result.getBody()); } } @@ -140,13 +155,13 @@ class SampleZuulProxyApplication { } @RequestMapping(value = "/local/{id}", method = RequestMethod.DELETE) - public String delete() { - return "Deleted!"; + public String delete(@PathVariable String id) { + return "Deleted " + id + "!"; } @RequestMapping(value = "/local/{id}", method = RequestMethod.GET) - public String get() { - return "Gotten!"; + public String get(@PathVariable String id) { + return "Gotten " + id + "!"; } @RequestMapping("/") diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ServletPathZuulProxyApplicationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ServletPathZuulProxyApplicationTests.java new file mode 100644 index 00000000..996f3ce6 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ServletPathZuulProxyApplicationTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.zuul; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import static org.junit.Assert.assertEquals; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = ServletPathZuulProxyApplication.class) +@WebAppConfiguration +@IntegrationTest({ "server.port: 0", "server.servletPath: /app" }) +@DirtiesContext +public class ServletPathZuulProxyApplicationTests { + + @Value("${local.server.port}") + private int port; + + @Autowired + private ProxyRouteLocator routes; + + @Autowired + private RoutesEndpoint endpoint; + + @Test + public void getOnSelfViaSimpleHostRoutingFilter() { + this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/app/local"); + this.endpoint.reset(); + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/app/self/1", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Gotten 1!", result.getBody()); + } + + @Test + public void stripPrefixFalseAppendsPath() { + this.routes.addRoute(new ZuulRoute("strip", "/strip/**", "strip", + "http://localhost:" + this.port + "/app/local", false, false)); + this.endpoint.reset(); + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/app/strip", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + // Prefix not stripped to it goes to /local/strip + assertEquals("Gotten strip!", result.getBody()); + } + +} + +// Don't use @SpringBootApplication because we don't want to component scan +@Configuration +@EnableAutoConfiguration +@RestController +@EnableZuulProxy +class ServletPathZuulProxyApplication { + + @RequestMapping(value = "/local/{id}", method = RequestMethod.GET) + public String get(@PathVariable String id) { + return "Gotten " + id + "!"; + } + + public static void main(String[] args) { + SpringApplication.run(SampleZuulProxyApplication.class, args); + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRouteLocatorTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRouteLocatorTests.java index 316f7146..835272e4 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRouteLocatorTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRouteLocatorTests.java @@ -61,7 +61,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetMatchingPath() throws Exception { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); this.properties.init(); @@ -73,7 +73,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetMatchingPathWithPrefix() throws Exception { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); this.properties.setPrefix("/proxy"); @@ -84,9 +84,21 @@ public class ProxyRouteLocatorTests { assertEquals("/1", route.getPath()); } + @Test + public void testGetMatchingPathWithServletPath() throws Exception { + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/app", this.discovery, + this.properties); + this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); + this.properties.init(); + routeLocator.getRoutes(); // force refresh + ProxyRouteSpec route = routeLocator.getMatchingRoute("/app/foo/1"); + assertEquals("foo", route.getLocation()); + assertEquals("/1", route.getPath()); + } + @Test public void testGetMatchingPathWithNoPrefixStripping() throws Exception { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put("foo", new ZuulRoute("foo", "/foo/**", "foo", null, false, null)); @@ -100,7 +112,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetMatchingPathWithLocalPrefixStripping() throws Exception { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**", "foo")); this.properties.setStripPrefix(false); @@ -113,7 +125,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetMatchingPathWithGlobalPrefixStripping() throws Exception { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put("foo", new ZuulRoute("foo", "/foo/**", "foo", null, false, null)); @@ -126,7 +138,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetMatchingPathWithRoutePrefixStripping() throws Exception { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); ZuulRoute zuulRoute = new ZuulRoute("/foo/**"); zuulRoute.setStripPrefix(true); @@ -140,7 +152,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetRoutes() { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/" + ASERVICE + "/**")); this.properties.init(); @@ -152,7 +164,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetRoutesWithMapping() { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/" + ASERVICE + "/**", ASERVICE)); @@ -164,7 +176,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetPhysicalRoutes() { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/" + ASERVICE + "/**", "http://" + ASERVICE)); @@ -176,7 +188,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetDefaultRoute() { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/**", ASERVICE)); Map routesMap = routeLocator.getRoutes(); @@ -187,7 +199,7 @@ public class ProxyRouteLocatorTests { @Test public void testGetDefaultPhysicalRoute() { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.getRoutes().put(ASERVICE, new ZuulRoute("/**", "http://" + ASERVICE)); @@ -199,7 +211,7 @@ public class ProxyRouteLocatorTests { @Test public void testIgnoreRoutes() { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.setIgnoredServices(Collections.singletonList(IGNOREDSERVICE)); given(this.discovery.getServices()).willReturn( @@ -211,7 +223,7 @@ public class ProxyRouteLocatorTests { @Test public void testIgnoreRoutesWithPattern() { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.setIgnoredServices(Collections.singletonList("ignore*")); given(this.discovery.getServices()).willReturn( @@ -223,7 +235,7 @@ public class ProxyRouteLocatorTests { @Test public void testIgnoreAllRoutes() { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.setIgnoredServices(Collections.singletonList("*")); given(this.discovery.getServices()).willReturn( @@ -236,7 +248,7 @@ public class ProxyRouteLocatorTests { @Test public void testIgnoredRouteIncludedIfConfiguredAndDiscovered() { this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.setIgnoredServices(Collections.singletonList("*")); given(this.discovery.getServices()).willReturn(Collections.singletonList("foo")); @@ -249,7 +261,7 @@ public class ProxyRouteLocatorTests { public void testIgnoredRouteIncludedIfConfiguredAndNotDiscovered() { this.properties.getRoutes() .put("foo", new ZuulRoute("/foo/**", "http://foo.com")); - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); this.properties.setIgnoredServices(Collections.singletonList("*")); given(this.discovery.getServices()).willReturn(Collections.singletonList("bar")); @@ -260,7 +272,7 @@ public class ProxyRouteLocatorTests { @Test public void testAutoRoutes() { - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); given(this.discovery.getServices()).willReturn( Collections.singletonList(MYSERVICE)); @@ -275,7 +287,7 @@ public class ProxyRouteLocatorTests { ZuulRoute route = new ZuulRoute("/" + MYSERVICE + "/**", "http://example.com/" + MYSERVICE); this.properties.getRoutes().put(MYSERVICE, route); - ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, + ProxyRouteLocator routeLocator = new ProxyRouteLocator("/", this.discovery, this.properties); given(this.discovery.getServices()).willReturn( Collections.singletonList(MYSERVICE)); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java index e2ecab87..5e6ea699 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java @@ -16,14 +16,12 @@ package org.springframework.cloud.netflix.zuul.filters.pre; -import static org.junit.Assert.assertEquals; -import static org.mockito.MockitoAnnotations.initMocks; - import java.util.List; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; +import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; @@ -33,6 +31,9 @@ import org.springframework.mock.web.MockHttpServletRequest; import com.netflix.util.Pair; import com.netflix.zuul.context.RequestContext; +import static org.junit.Assert.assertEquals; +import static org.mockito.MockitoAnnotations.initMocks; + /** * @author Dave Syer */ @@ -49,10 +50,13 @@ public class PreDecorationFilterTests { private MockHttpServletRequest request = new MockHttpServletRequest(); + private ServerProperties server = new ServerProperties(); + @Before public void init() { initMocks(this); - this.routeLocator = new ProxyRouteLocator(this.discovery, this.properties); + this.routeLocator = new ProxyRouteLocator("/", this.discovery, + this.properties); this.filter = new PreDecorationFilter(this.routeLocator, true); RequestContext ctx = RequestContext.getCurrentContext(); ctx.setRequest(this.request); @@ -70,7 +74,8 @@ public class PreDecorationFilterTests { this.properties.setPrefix("/api"); this.properties.setStripPrefix(true); this.request.setRequestURI("/api/foo/1"); - this.routeLocator.addRoute(new ZuulRoute("foo", "/foo/**", "foo", null, false, null)); + this.routeLocator.addRoute(new ZuulRoute("foo", "/foo/**", "foo", null, false, + null)); this.filter.run(); RequestContext ctx = RequestContext.getCurrentContext(); assertEquals("/foo/1", ctx.get("requestURI")); diff --git a/spring-cloud-netflix-core/src/test/resources/application.yml b/spring-cloud-netflix-core/src/test/resources/application.yml index 76137511..7c8e58f6 100644 --- a/spring-cloud-netflix-core/src/test/resources/application.yml +++ b/spring-cloud-netflix-core/src/test/resources/application.yml @@ -2,7 +2,7 @@ server: port: 9999 logging: level: -# org.springframework.web: DEBUG + org.springframework.web: DEBUG spring: application: name: testclient