From 1b190a32bd9593acfd28a916a1dbcfb41f1d1105 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 9 Feb 2016 15:02:31 -0700 Subject: [PATCH] refactored zuul proxy tests --- .../filters/route/RibbonRoutingFilter.java | 4 + ...SampleZuulProxyAppTestsWithHttpClient.java | 355 ------------------ .../zuul/SampleZuulProxyApplicationTests.java | 305 +++------------ .../SampleZuulProxyWithHttpClientTests.java | 130 +++++++ .../cloud/netflix/zuul/ZuulProxyTestBase.java | 288 ++++++++++++++ 5 files changed, 473 insertions(+), 609 deletions(-) delete mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyAppTestsWithHttpClient.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyWithHttpClientTests.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ZuulProxyTestBase.java diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java index 18c28e89..bbef65c7 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java @@ -77,6 +77,10 @@ public class RibbonRoutingFilter extends ZuulFilter { setResponse(response); return response; } + catch (ZuulException ex) { + context.set("error.status_code", ex.nStatusCode); + context.set("error.exception", ex); + } catch (Exception ex) { context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyAppTestsWithHttpClient.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyAppTestsWithHttpClient.java deleted file mode 100644 index b23e53b4..00000000 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyAppTestsWithHttpClient.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * 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 java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.Before; -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.ribbon.RibbonClient; -import org.springframework.cloud.netflix.ribbon.RibbonClients; -import org.springframework.cloud.netflix.ribbon.SpringClientFactory; -import org.springframework.cloud.netflix.zuul.filters.Route; -import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; -import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; -import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory; -import org.springframework.context.annotation.Bean; -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.util.MultiValueMap; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = SampleHttpClientZuulProxyApplication.class) -@WebAppConfiguration -@IntegrationTest({ "server.port: 0", - "zuul.routes.other: /test/**=http://localhost:7777/local", - "zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**" }) -@DirtiesContext -public class SampleZuulProxyAppTestsWithHttpClient { - - @Value("${local.server.port}") - private int port; - - @Autowired - private DiscoveryClientRouteLocator routes; - - @Autowired - private RoutesEndpoint endpoint; - - @Autowired - private RibbonCommandFactory ribbonCommandFactory; - - @Before - public void setTestRequestcontext() { - RequestContext context = new RequestContext(); - RequestContext.testSetCurrentContext(context); - } - - private String getRoute(String path) { - for (Route route : this.routes.getRoutes()) { - if (path.equals(route.getFullPath())) { - return route.getLocation(); - } - } - return null; - } - - @Test - public void bindRouteUsingPhysicalRoute() { - assertEquals("http://localhost:7777/local", getRoute("/test/**")); - } - - @Test - public void bindRouteUsingOnlyPath() { - assertEquals("simple", getRoute("/simple/**")); - } - - @Test - public void getOnSelfViaRibbonRoutingFilter() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/simple/local/1", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Gotten 1!", result.getBody()); - } - - @Test - public void patchOnSelfViaRibbonRoutingFilter() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/simple/local/1", HttpMethod.PATCH, - new HttpEntity<>("TestPatch"), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Patched 1!", result.getBody()); - } - - @Test - public void postOnSelfViaRibbonRoutingFilter() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/simple/local/1", HttpMethod.POST, - new HttpEntity<>("TestPost"), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Posted 1!", result.getBody()); - } - - @Test - public void deleteOnSelfViaRibbonRoutingFilter() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/simple/local/1", HttpMethod.DELETE, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Deleted 1!", result.getBody()); - } - - @Test - public void deleteOnSelfViaSimpleHostRoutingFilter() { - this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/local"); - this.endpoint.reset(); - - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/self/1", HttpMethod.DELETE, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Deleted 1!", result.getBody()); - } - - @Test - public void patchOnSelfViaSimpleHostRoutingFilter() { - this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/local"); - this.endpoint.reset(); - - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/self/1", HttpMethod.PATCH, - new HttpEntity<>("TestPatch"), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Patched 1!", result.getBody()); - } - - @Test - public void stripPrefixFalseAppendsPath() { - this.routes.addRoute(new ZuulProperties.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 - public void testNotFound() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/myinvalidpath", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); - } - - @Test - public void getSecondLevel() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/another/twolevel/local/1", - HttpMethod.GET, new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Gotten 1!", result.getBody()); - } - - @Test - public void ribbonRouteWithSpace() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/simple/spa ce", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Hello space", result.getBody()); - } - - @Test - public void simpleHostRouteWithSpace() { - this.routes.addRoute("/self/**", "http://localhost:" + this.port); - this.endpoint.reset(); - - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/self/spa ce", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Hello space", result.getBody()); - } - - @Test - public void simpleHostRouteWithOriginalQString() { - this.routes.addRoute("/self/**", "http://localhost:" + this.port); - this.endpoint.reset(); - - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port - + "/self/qstring?original=value1&original=value2", - HttpMethod.GET, new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Received {original=[value1, value2]}", result.getBody()); - } - - @Test - public void simpleHostRouteWithOverriddenQString() { - this.routes.addRoute("/self/**", "http://localhost:" + this.port); - this.endpoint.reset(); - - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port - + "/self/qstring?override=true&different=key", - HttpMethod.GET, new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Received {key=[overridden]}", result.getBody()); - } - - @Test - public void ribbonCommandFactoryOverridden() { - assertTrue("ribbonCommandFactory not a MyRibbonCommandFactory", - this.ribbonCommandFactory instanceof HttpClientRibbonCommandFactory); - } - -} - -// Don't use @SpringBootApplication because we don't want to component scan -@Configuration -@EnableAutoConfiguration -@RestController -@EnableZuulProxy -@RibbonClients({ - @RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class), - @RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class) }) -class SampleHttpClientZuulProxyApplication { - - public static void main(final String[] args) { - SpringApplication.run(SampleZuulProxyApplication.class, args); - } - - @RequestMapping("/testing123") - public String testing123() { - throw new RuntimeException("myerror"); - } - - @RequestMapping("/local") - public String local() { - return "Hello local"; - } - - @RequestMapping(value = "/local/{id}", method = RequestMethod.DELETE) - public String delete(@PathVariable final String id) { - return "Deleted " + id + "!"; - } - - @RequestMapping(value = "/local/{id}", method = RequestMethod.PATCH) - public String patch(@PathVariable final String id, @RequestBody final String body) { - return "Patched " + id + "!"; - } - - @RequestMapping(value = "/local/{id}", method = RequestMethod.GET) - public String get(@PathVariable final String id) { - return "Gotten " + id + "!"; - } - - @RequestMapping(value = "/local/{id}", method = RequestMethod.POST) - public String post(@PathVariable final String id, @RequestBody final String body) { - return "Posted " + id + "!"; - } - - @RequestMapping(value = "/qstring") - public String qstring(@RequestParam final MultiValueMap params) { - return "Received " + params.toString(); - } - - @RequestMapping("/") - public String home() { - return "Hello world"; - } - - @RequestMapping("/spa ce") - public String space() { - return "Hello space"; - } - - @Bean - public RibbonCommandFactory ribbonCommandFactory( - final SpringClientFactory clientFactory) { - return new HttpClientRibbonCommandFactory(clientFactory); - } - - @Bean - public ZuulFilter sampleFilter() { - return new ZuulFilter() { - @Override - public String filterType() { - return "pre"; - } - - @Override - public boolean shouldFilter() { - return true; - } - - @Override - public Object run() { - if (RequestContext.getCurrentContext().getRequest().getParameterMap() - .containsKey("override")) { - Map> overridden = new HashMap<>(); - overridden.put("key", Arrays.asList("overridden")); - RequestContext.getCurrentContext().setRequestQueryParams(overridden); - } - - return null; - } - - @Override - public int filterOrder() { - return 0; - } - }; - } - -} 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 eb1357ff..3ddcbcd9 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 @@ -16,18 +16,15 @@ package org.springframework.cloud.netflix.zuul; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import java.util.UUID; import javax.servlet.http.HttpServletRequest; -import org.junit.Before; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; +import com.netflix.zuul.exception.ZuulException; + 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; @@ -37,10 +34,9 @@ import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.netflix.ribbon.RibbonClients; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.netflix.ribbon.StaticServerList; -import org.springframework.cloud.netflix.zuul.filters.Route; -import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute; -import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator; +import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommand; import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -51,21 +47,12 @@ 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.util.MultiValueMap; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.netflix.loadbalancer.Server; -import com.netflix.loadbalancer.ServerList; -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.context.RequestContext; +import lombok.SneakyThrows; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @RunWith(SpringJUnit4ClassRunner.class) @@ -73,140 +60,10 @@ import static org.junit.Assert.assertTrue; @WebAppConfiguration @IntegrationTest({ "server.port: 0", "zuul.routes.other: /test/**=http://localhost:7777/local", - "zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**" }) + "zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**", + "zuul.routes.badhost: /badhost/**" }) @DirtiesContext -public class SampleZuulProxyApplicationTests { - - @Value("${local.server.port}") - private int port; - - @Autowired - private DiscoveryClientRouteLocator routes; - - @Autowired - private RoutesEndpoint endpoint; - - @Autowired - private RibbonCommandFactory ribbonCommandFactory; - - @Before - public void setTestRequestcontext() { - RequestContext context = new RequestContext(); - RequestContext.testSetCurrentContext(context); - } - - private String getRoute(String path) { - for (Route route : this.routes.getRoutes()) { - if (path.equals(route.getFullPath())) { - return route.getLocation(); - } - } - return null; - } - - @Test - public void bindRouteUsingPhysicalRoute() { - assertEquals("http://localhost:7777/local", getRoute("/test/**")); - } - - @Test - public void bindRouteUsingOnlyPath() { - assertNotNull(getRoute("/simple/**")); - } - - @Test - public void getOnSelfViaRibbonRoutingFilter() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/simple/local/1", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Gotten 1!", result.getBody()); - } - - @Test - public void deleteOnSelfViaSimpleHostRoutingFilter() { - this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/local"); - this.endpoint.reset(); - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/self/1", HttpMethod.DELETE, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - 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 - public void testNotFound() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/myinvalidpath", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); - } - - @Test - public void getSecondLevel() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/another/twolevel/local/1", - HttpMethod.GET, new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Gotten 1!", result.getBody()); - } - - @Test - public void ribbonRouteWithSpace() { - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/simple/spa ce", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Hello space", result.getBody()); - } - - @Test - public void simpleHostRouteWithSpace() { - this.routes.addRoute("/self/**", "http://localhost:" + this.port); - this.endpoint.reset(); - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/self/spa ce", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Hello space", result.getBody()); - } - - @Test - public void simpleHostRouteWithOriginalQString() { - this.routes.addRoute("/self/**", "http://localhost:" + this.port); - this.endpoint.reset(); - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port - + "/self/qstring?original=value1&original=value2", - HttpMethod.GET, new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Received {original=[value1, value2]}", result.getBody()); - } - - @Test - public void simpleHostRouteWithOverriddenQString() { - this.routes.addRoute("/self/**", "http://localhost:" + this.port); - this.endpoint.reset(); - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port - + "/self/qstring?override=true&different=key", - HttpMethod.GET, new HttpEntity<>((Void) null), String.class); - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("Received {key=[overridden]}", result.getBody()); - } +public class SampleZuulProxyApplicationTests extends ZuulProxyTestBase { @Test public void simpleHostRouteWithTrailingSlash() { @@ -219,9 +76,26 @@ public class SampleZuulProxyApplicationTests { assertEquals("/trailing-slash", result.getBody()); } + @Test + public void ribbonCommandForbidden() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/simple/throwexception/403", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode()); + } + + @Test + public void ribbonCommandBadHost() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/badhost/1", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + } + @Test public void ribbonCommandFactoryOverridden() { - assertTrue("ribbonCommandFactory not a MyRibbonCommandFactory", + assertTrue( + "ribbonCommandFactory not a MyRibbonCommandFactory", this.ribbonCommandFactory instanceof SampleZuulProxyApplication.MyRibbonCommandFactory); } @@ -233,49 +107,10 @@ public class SampleZuulProxyApplicationTests { @RestController @EnableZuulProxy @RibbonClients({ + @RibbonClient(name = "badhost", configuration = SampleZuulProxyApplication.BadHostRibbonClientConfiguration.class), @RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class), @RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class) }) -class SampleZuulProxyApplication { - - @RequestMapping("/testing123") - public String testing123() { - throw new RuntimeException("myerror"); - } - - @RequestMapping("/local") - public String local() { - return "Hello local"; - } - - @RequestMapping(value = "/local/{id}", method = RequestMethod.DELETE) - public String delete(@PathVariable String id) { - return "Deleted " + id + "!"; - } - - @RequestMapping(value = "/local/{id}", method = RequestMethod.GET) - public String get(@PathVariable String id) { - return "Gotten " + id + "!"; - } - - @RequestMapping(value = "/local/{id}", method = RequestMethod.POST) - public String post(@PathVariable String id, @RequestBody String body) { - return "Posted " + id + "!"; - } - - @RequestMapping(value = "/qstring") - public String qstring(@RequestParam MultiValueMap params) { - return "Received " + params.toString(); - } - - @RequestMapping("/") - public String home() { - return "Hello world"; - } - - @RequestMapping("/spa ce") - public String space() { - return "Hello space"; - } +class SampleZuulProxyApplication extends ZuulProxyTestBase.AbstractZuulProxyApplication { @RequestMapping(value = "/trailing-slash") public String trailingSlash(HttpServletRequest request) { @@ -283,42 +118,10 @@ class SampleZuulProxyApplication { } @Bean - public RibbonCommandFactory ribbonCommandFactory( - SpringClientFactory clientFactory) { + public RibbonCommandFactory ribbonCommandFactory(SpringClientFactory clientFactory) { return new MyRibbonCommandFactory(clientFactory); } - @Bean - public ZuulFilter sampleFilter() { - return new ZuulFilter() { - @Override - public String filterType() { - return "pre"; - } - - @Override - public boolean shouldFilter() { - return true; - } - - @Override - public Object run() { - if (RequestContext.getCurrentContext().getRequest().getParameterMap() - .containsKey("override")) { - Map> overridden = new HashMap<>(); - overridden.put("key", Arrays.asList("overridden")); - RequestContext.getCurrentContext().setRequestQueryParams(overridden); - } - return null; - } - - @Override - public int filterOrder() { - return 0; - } - }; - } - public static void main(String[] args) { SpringApplication.run(SampleZuulProxyApplication.class, args); } @@ -328,33 +131,27 @@ class SampleZuulProxyApplication { public MyRibbonCommandFactory(SpringClientFactory clientFactory) { super(clientFactory); } + + @Override + @SneakyThrows + public RestClientRibbonCommand create(RibbonCommandContext context) { + String uri = context.getUri(); + if (uri.startsWith("/throwexception/")) { + String code = uri.replace("/throwexception/", ""); + throw new ZuulException(new RuntimeException(), Integer.parseInt(code), + "test error"); + } + return super.create(context); + } } -} + // Load balancer with fixed server list for "simple" pointing to localhost + @Configuration + static class BadHostRibbonClientConfiguration { + @Bean + public ServerList ribbonServerList() { + return new StaticServerList<>(new Server(UUID.randomUUID().toString(), 4322)); + } -// Load balancer with fixed server list for "simple" pointing to localhost -@Configuration -class SimpleRibbonClientConfiguration { - - @Value("${local.server.port}") - private int port; - - @Bean - public ServerList ribbonServerList() { - return new StaticServerList<>(new Server("localhost", this.port)); } - -} - -@Configuration -class AnotherRibbonClientConfiguration { - - @Value("${local.server.port}") - private int port; - - @Bean - public ServerList ribbonServerList() { - return new StaticServerList<>(new Server("localhost", this.port)); - } - } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyWithHttpClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyWithHttpClientTests.java new file mode 100644 index 00000000..7ce58489 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyWithHttpClientTests.java @@ -0,0 +1,130 @@ +/* + * 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.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.ribbon.RibbonClient; +import org.springframework.cloud.netflix.ribbon.RibbonClients; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory; +import org.springframework.context.annotation.Bean; +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.RequestBody; +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; +import static org.junit.Assert.assertTrue; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = SampleHttpClientZuulProxyApplication.class) +@WebAppConfiguration +@IntegrationTest({ "server.port: 0", + "zuul.routes.other: /test/**=http://localhost:7777/local", + "zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**" }) +@DirtiesContext +public class SampleZuulProxyWithHttpClientTests extends ZuulProxyTestBase { + + @Test + public void patchOnSelfViaRibbonRoutingFilter() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/simple/local/1", HttpMethod.PATCH, + new HttpEntity<>("TestPatch"), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Patched 1!", result.getBody()); + } + + @Test + public void postOnSelfViaRibbonRoutingFilter() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/simple/local/1", HttpMethod.POST, + new HttpEntity<>("TestPost"), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Posted 1!", result.getBody()); + } + + @Test + public void deleteOnSelfViaRibbonRoutingFilter() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/simple/local/1", HttpMethod.DELETE, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Deleted 1!", result.getBody()); + } + + @Test + public void patchOnSelfViaSimpleHostRoutingFilter() { + this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/local"); + this.endpoint.reset(); + + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/self/1", HttpMethod.PATCH, + new HttpEntity<>("TestPatch"), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Patched 1!", result.getBody()); + } + + @Test + public void ribbonCommandFactoryOverridden() { + assertTrue("ribbonCommandFactory not a MyRibbonCommandFactory", + this.ribbonCommandFactory instanceof HttpClientRibbonCommandFactory); + } + +} + +// Don't use @SpringBootApplication because we don't want to component scan +@Configuration +@EnableAutoConfiguration +@RestController +@EnableZuulProxy +@RibbonClients({ + @RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class), + @RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class) }) +class SampleHttpClientZuulProxyApplication extends ZuulProxyTestBase.AbstractZuulProxyApplication { + + public static void main(final String[] args) { + SpringApplication.run(SampleZuulProxyApplication.class, args); + } + + @RequestMapping(value = "/local/{id}", method = RequestMethod.PATCH) + public String patch(@PathVariable final String id, @RequestBody final String body) { + return "Patched " + id + "!"; + } + + @Bean + public RibbonCommandFactory ribbonCommandFactory( + final SpringClientFactory clientFactory) { + return new HttpClientRibbonCommandFactory(clientFactory); + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ZuulProxyTestBase.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ZuulProxyTestBase.java new file mode 100644 index 00000000..33f04e30 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ZuulProxyTestBase.java @@ -0,0 +1,288 @@ +package org.springframework.cloud.netflix.zuul; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.cloud.netflix.ribbon.StaticServerList; +import org.springframework.cloud.netflix.zuul.filters.Route; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; +import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.context.annotation.Bean; +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.util.MultiValueMap; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import static org.junit.Assert.assertEquals; + +/** + * @author Spencer Gibb + */ +public class ZuulProxyTestBase { + + @Value("${local.server.port}") + protected int port; + + @Autowired + protected DiscoveryClientRouteLocator routes; + + @Autowired + protected RoutesEndpoint endpoint; + + @Autowired + protected RibbonCommandFactory ribbonCommandFactory; + + @Before + public void setTestRequestcontext() { + RequestContext context = new RequestContext(); + RequestContext.testSetCurrentContext(context); + } + + protected String getRoute(String path) { + for (Route route : this.routes.getRoutes()) { + if (path.equals(route.getFullPath())) { + return route.getLocation(); + } + } + return null; + } + + @Test + public void bindRouteUsingPhysicalRoute() { + assertEquals("http://localhost:7777/local", getRoute("/test/**")); + } + + @Test + public void bindRouteUsingOnlyPath() { + assertEquals("simple", getRoute("/simple/**")); + } + + @Test + public void getOnSelfViaRibbonRoutingFilter() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/simple/local/1", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Gotten 1!", result.getBody()); + } + + @Test + public void deleteOnSelfViaSimpleHostRoutingFilter() { + this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/local"); + this.endpoint.reset(); + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/self/1", HttpMethod.DELETE, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Deleted 1!", result.getBody()); + } + + @Test + public void stripPrefixFalseAppendsPath() { + this.routes.addRoute(new ZuulProperties.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 + public void testNotFoundFromApp() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/simple/local/notfound", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); + } + + @Test + public void testNotFoundOnProxy() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/myinvalidpath", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); + } + + @Test + public void getSecondLevel() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/another/twolevel/local/1", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Gotten 1!", result.getBody()); + } + + @Test + public void ribbonRouteWithSpace() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/simple/spa ce", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Hello space", result.getBody()); + } + + @Test + public void simpleHostRouteWithSpace() { + this.routes.addRoute("/self/**", "http://localhost:" + this.port); + this.endpoint.reset(); + + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/self/spa ce", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Hello space", result.getBody()); + } + + @Test + public void simpleHostRouteWithOriginalQString() { + this.routes.addRoute("/self/**", "http://localhost:" + this.port); + this.endpoint.reset(); + + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + + "/self/qstring?original=value1&original=value2", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Received {original=[value1, value2]}", result.getBody()); + } + + @Test + public void simpleHostRouteWithOverriddenQString() { + this.routes.addRoute("/self/**", "http://localhost:" + this.port); + this.endpoint.reset(); + + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + + "/self/qstring?override=true&different=key", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Received {key=[overridden]}", result.getBody()); + } + + protected static abstract class AbstractZuulProxyApplication { + + @RequestMapping("/testing123") + public String testing123() { + throw new RuntimeException("myerror"); + } + + @RequestMapping("/local") + public String local() { + return "Hello local"; + } + + @RequestMapping(value = "/local/{id}", method = RequestMethod.DELETE) + public String delete(@PathVariable String id) { + return "Deleted " + id + "!"; + } + + @RequestMapping(value = "/local/{id}", method = RequestMethod.GET) + public ResponseEntity get(@PathVariable String id) { + if ("notfound".equalsIgnoreCase(id)) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok("Gotten " + id + "!"); + } + + @RequestMapping(value = "/local/{id}", method = RequestMethod.POST) + public String post(@PathVariable String id, @RequestBody String body) { + return "Posted " + id + "!"; + } + + @RequestMapping(value = "/qstring") + public String qstring(@RequestParam MultiValueMap params) { + return "Received " + params.toString(); + } + + @RequestMapping("/") + public String home() { + return "Hello world"; + } + + @RequestMapping("/spa ce") + public String space() { + return "Hello space"; + } + + @Bean + public ZuulFilter sampleFilter() { + return new ZuulFilter() { + @Override + public String filterType() { + return "pre"; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public Object run() { + if (RequestContext.getCurrentContext().getRequest().getParameterMap() + .containsKey("override")) { + Map> overridden = new HashMap<>(); + overridden.put("key", Arrays.asList("overridden")); + RequestContext.getCurrentContext().setRequestQueryParams(overridden); + } + return null; + } + + @Override + public int filterOrder() { + return 0; + } + }; + } + } +} + +// Load balancer with fixed server list for "simple" pointing to localhost +@Configuration +class SimpleRibbonClientConfiguration { + + @Value("${local.server.port}") + private int port; + + @Bean + public ServerList ribbonServerList() { + return new StaticServerList<>(new Server("localhost", this.port)); + } + +} + +@Configuration +class AnotherRibbonClientConfiguration { + + @Value("${local.server.port}") + private int port; + + @Bean + public ServerList ribbonServerList() { + return new StaticServerList<>(new Server("localhost", this.port)); + } + +} \ No newline at end of file