diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index 8822cca2..297d5329 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -26,10 +26,6 @@ org.springframework.boot spring-boot-starter-webflux - - org.springframework.cloud - spring-cloud-gateway-mvc - org.springframework.cloud spring-cloud-starter-gateway diff --git a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java index c57735bc..8232830f 100644 --- a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java +++ b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java @@ -17,44 +17,55 @@ package org.springframework.cloud.gateway.sample; -import java.net.URI; -import java.util.function.Function; - -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.gateway.mvc.ProxyExchange; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.gateway.EnableGateway; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.Routes; +import org.springframework.context.annotation.Bean; + +import static org.springframework.cloud.gateway.filter.factory.WebFilterFactories.addResponseHeader; +import static org.springframework.cloud.gateway.handler.predicate.RoutePredicates.host; +import static org.springframework.cloud.gateway.handler.predicate.RoutePredicates.path; +import static org.springframework.tuple.TupleBuilder.tuple; /** * @author Spencer Gibb - * @author Dave Syer */ -@RestController -@SpringBootApplication +@SpringBootConfiguration +@EnableAutoConfiguration +@EnableGateway public class GatewaySampleApplication { - @Value("${remote.home}") - private URI home; - - @GetMapping(path="/test", headers="x-host=png.abc.org") - public ResponseEntity proxy(ProxyExchange proxy) throws Exception { - return proxy.uri(home.toString() + "/image/png") - .get(header("X-TestHeader", "foobar")); + @Bean + public RouteLocator customRouteLocator(ThrottleWebFilterFactory throttle) { + return Routes.locator() + .route("test") + .uri("http://httpbin.org:80") + .predicate(host("**.abc.org").and(path("/image/png"))) + .addResponseHeader("X-TestHeader", "foobar") + .and() + .route("test2") + .uri("http://httpbin.org:80") + .predicate(path("/image/webp")) + .add(addResponseHeader("X-AnotherHeader", "baz")) + .and() + .route("test3") + .order(-1) + .uri("http://httpbin.org:80") + .predicate(host("**.throttle.org").and(path("/get"))) + .add(throttle.apply(tuple().of("capacity", 1, + "refillTokens", 1, + "refillPeriod", 10, + "refillUnit", "SECONDS"))) + .and() + .build(); } - @GetMapping("/test2") - public ResponseEntity proxyFoos(ProxyExchange proxy) throws Exception { - return proxy.uri(home.toString() + "/image/webp").get(header("X-AnotherHeader", "baz")); - } - - private Function, ResponseEntity> header(String key, - String value) { - return response -> ResponseEntity.status(response.getStatusCode()) - .headers(response.getHeaders()).header(key, value) - .body(response.getBody()); + @Bean + public ThrottleWebFilterFactory throttleWebFilterFactory() { + return new ThrottleWebFilterFactory(); } public static void main(String[] args) { diff --git a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleWebFilterFactory.java b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleWebFilterFactory.java new file mode 100644 index 00000000..74e975b2 --- /dev/null +++ b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleWebFilterFactory.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2017 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.gateway.sample; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.isomorphism.util.TokenBucket; +import org.isomorphism.util.TokenBuckets; +import org.springframework.cloud.gateway.filter.factory.WebFilterFactory; +import org.springframework.http.HttpStatus; +import org.springframework.tuple.Tuple; +import org.springframework.web.server.WebFilter; + +import java.util.concurrent.TimeUnit; + +/** + * Sample throttling filter. + * See https://github.com/bbeck/token-bucket + */ +public class ThrottleWebFilterFactory implements WebFilterFactory { + private Log log = LogFactory.getLog(getClass()); + + @Override + public WebFilter apply(Tuple args) { + int capacity = args.getInt("capacity"); + int refillTokens = args.getInt("refillTokens"); + int refillPeriod = args.getInt("refillPeriod"); + TimeUnit refillUnit = TimeUnit.valueOf(args.getString("refillUnit")); + + final TokenBucket tokenBucket = TokenBuckets.builder() + .withCapacity(capacity) + .withFixedIntervalRefillStrategy(refillTokens, refillPeriod, refillUnit) + .build(); + + return (exchange, chain) -> { + //TODO: get a token bucket for a key + log.debug("TokenBucket capacity: " + tokenBucket.getCapacity()); + boolean consumed = tokenBucket.tryConsume(); + if (consumed) { + return chain.filter(exchange); + } + exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); + return exchange.getResponse().setComplete(); + }; + } +} diff --git a/spring-cloud-gateway-sample/src/main/resources/application.yml b/spring-cloud-gateway-sample/src/main/resources/application.yml index 0dd282e7..a0134647 100644 --- a/spring-cloud-gateway-sample/src/main/resources/application.yml +++ b/spring-cloud-gateway-sample/src/main/resources/application.yml @@ -1,5 +1,29 @@ +test: + hostport: httpbin.org:80 +# hostport: localhost:5000 + uri: http://${test.hostport} + +spring: + cloud: + gateway: + default-filters: + - AddResponseHeader=X-Response-Default-Foo, Default-Bar + + routes: + # ===================================== + - id: default_path_to_httpbin + uri: ${test.uri} + order: 10000 + predicates: + - Path=/** + +logging: + level: + org.springframework.cloud.gateway: TRACE + org.springframework.http.server.reactive: DEBUG + org.springframework.web.reactive: DEBUG + reactor.ipc.netty: DEBUG + management: - security: - enabled: false -remote: - home: http://httpbin.org + context-path: /admin +# port: 8081 diff --git a/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java b/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java index 40802f9a..c077fd0b 100644 --- a/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java +++ b/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java @@ -17,19 +17,20 @@ package org.springframework.cloud.gateway.sample; -import java.net.URI; - -import org.junit.Ignore; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.http.RequestEntity; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.time.Duration; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @@ -38,28 +39,34 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Spencer Gibb */ @RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = RANDOM_PORT) +@SpringBootTest(classes = GatewaySampleApplication.class, webEnvironment = RANDOM_PORT) public class GatewaySampleApplicationTests { @LocalServerPort protected int port = 0; - @Autowired - protected TestRestTemplate rest; + protected WebClient webClient; + protected String baseUri; - @Test - @Ignore - public void passthru() { - assertThat(rest.getForEntity("/test2", byte[].class).getStatusCode()) - .isEqualTo(HttpStatus.OK); + @Before + public void setup() { + baseUri = "http://localhost:" + port; + this.webClient = WebClient.create(baseUri); } @Test - @Ignore - public void header() throws Exception { - assertThat(rest - .exchange(RequestEntity.get(new URI("/test")) - .header("x-host", "png.abc.org").build(), byte[].class) - .getStatusCode()).isEqualTo(HttpStatus.OK); + public void contextLoads() { + Mono result = webClient.get() + .uri("/get") + .exchange(); + + StepVerifier.create(result) + .consumeNextWith( + response -> { + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK); + HttpHeaders httpHeaders = response.headers().asHttpHeaders(); + }) + .expectComplete() + .verify(Duration.ofSeconds(5)); } }