diff --git a/spring-cloud/spring-cloud-gateway/pom.xml b/spring-cloud/spring-cloud-gateway/pom.xml index dcad2b7da6..a352bbd4e4 100644 --- a/spring-cloud/spring-cloud-gateway/pom.xml +++ b/spring-cloud/spring-cloud-gateway/pom.xml @@ -177,5 +177,21 @@ + + + gateway-url-rewrite + + + + org.springframework.boot + spring-boot-maven-plugin + + com.baeldung.springcloudgateway.rewrite.URLRewriteGatewayApplication + -Dspring.profiles.active=url-rewrite + + + + + \ No newline at end of file diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplication.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplication.java new file mode 100644 index 0000000000..46541b4826 --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplication.java @@ -0,0 +1,25 @@ +/** + * + */ +package com.baeldung.springcloudgateway.rewrite; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.gateway.filter.GatewayFilter; + +import reactor.core.publisher.Mono; + +/** + * @author Baeldung + * + */ +@SpringBootApplication +public class URLRewriteGatewayApplication { + + public static void main(String[] args) { + new SpringApplicationBuilder(URLRewriteGatewayApplication.class) + .profiles("url-rewrite") + .run(args); + } + +} diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/routes/DynamicRewriteRoute.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/routes/DynamicRewriteRoute.java new file mode 100644 index 0000000000..29d40d7021 --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/rewrite/routes/DynamicRewriteRoute.java @@ -0,0 +1,43 @@ +package com.baeldung.springcloudgateway.rewrite.routes; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.http.server.reactive.ServerHttpRequest; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; + +import java.util.Random; + +@Configuration +@Profile("url-rewrite") +public class DynamicRewriteRoute { + + @Value("${rewrite.backend.uri}") + private String backendUri; + private static Random rnd = new Random(); + + @Bean + public RouteLocator dynamicZipCodeRoute(RouteLocatorBuilder builder) { + return builder.routes() + .route("dynamicRewrite", r -> + r.path("/v2/zip/**") + .filters(f -> f.filter((exchange, chain) -> { + ServerHttpRequest req = exchange.getRequest(); + addOriginalRequestUrl(exchange, req.getURI()); + String path = req.getURI().getRawPath(); + String newPath = path.replaceAll( + "/v2/zip/(?.*)", + "/api/zip/${zipcode}-" + String.format("%03d", rnd.nextInt(1000))); + ServerHttpRequest request = req.mutate().path(newPath).build(); + exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI()); + return chain.filter(exchange.mutate().request(request).build()); + })) + .uri(backendUri)) + .build(); + } +} diff --git a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java index 852e5cadba..9e212cc4bf 100644 --- a/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java +++ b/spring-cloud/spring-cloud-gateway/src/main/java/com/baeldung/springcloudgateway/webfilters/WebFilterGatewayApplication.java @@ -8,7 +8,7 @@ public class WebFilterGatewayApplication { public static void main(String[] args) { new SpringApplicationBuilder(WebFilterGatewayApplication.class) - .profiles("webfilters") + .profiles("url-rewrite") .run(args); } diff --git a/spring-cloud/spring-cloud-gateway/src/main/resources/application-nosecurity.yml b/spring-cloud/spring-cloud-gateway/src/main/resources/application-nosecurity.yml new file mode 100644 index 0000000000..40a52ded0f --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/main/resources/application-nosecurity.yml @@ -0,0 +1,8 @@ +# Enable this profile to disable security +spring: + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration + - org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfiguration + - org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration + - org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration \ No newline at end of file diff --git a/spring-cloud/spring-cloud-gateway/src/main/resources/application-url-rewrite.yml b/spring-cloud/spring-cloud-gateway/src/main/resources/application-url-rewrite.yml new file mode 100644 index 0000000000..b2656151db --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/main/resources/application-url-rewrite.yml @@ -0,0 +1,11 @@ +spring: + cloud: + gateway: + routes: + - id: rewrite_v1 + uri: ${rewrite.backend.uri:http://example.com} + predicates: + - Path=/v1/customer/** + filters: + - RewritePath=/v1/customer/(?.*),/api/$\{segment} + \ No newline at end of file diff --git a/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplicationLiveTest.java b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplicationLiveTest.java new file mode 100644 index 0000000000..41fe37045c --- /dev/null +++ b/spring-cloud/spring-cloud-gateway/src/test/java/com/baeldung/springcloudgateway/rewrite/URLRewriteGatewayApplicationLiveTest.java @@ -0,0 +1,109 @@ +package com.baeldung.springcloudgateway.rewrite; + +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.OutputStream; +import java.net.InetSocketAddress; + +import org.junit.AfterClass; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; + +import com.sun.net.httpserver.HttpServer; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@ActiveProfiles({ "nosecurity", "url-rewrite" }) +class URLRewriteGatewayApplicationLiveTest { + + // NOTE for Eclipse users: By default, Eclipse will complain about com.sun.** classes. + // To solve this issue, follow instructions available at the : + // https://stackoverflow.com/questions/13155734/eclipse-cant-recognize-com-sun-net-httpserver-httpserver-package + private static HttpServer mockServer; + private static Logger log = LoggerFactory.getLogger(URLRewriteGatewayApplicationLiveTest.class); + + // Create a running HttpServer that echoes back the request URL. + private static HttpServer startTestServer() { + + try { + log.info("[I26] Starting mock server"); + mockServer = HttpServer.create(); + mockServer.bind(new InetSocketAddress(0), 0); + mockServer.createContext("/api", (xchg) -> { + String uri = xchg.getRequestURI() + .toString(); + log.info("[I23] Backend called: uri={}", uri); + xchg.getResponseHeaders() + .add("Content-Type", "text/plain"); + xchg.sendResponseHeaders(200, 0); + OutputStream os = xchg.getResponseBody(); + os.write(uri.getBytes()); + os.flush(); + os.close(); + }); + + mockServer.start(); + InetSocketAddress localAddr = mockServer.getAddress(); + log.info("[I36] mock server started: local address={}", localAddr); + + return mockServer; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + } + + // TIP: https://www.baeldung.com/spring-dynamicpropertysource + @DynamicPropertySource + static void registerBackendServer(DynamicPropertyRegistry registry) { + registry.add("rewrite.backend.uri", () -> { + HttpServer s = startTestServer(); + return "http://localhost:" + s.getAddress().getPort(); + }); + } + + @AfterClass + public static void stopMockBackend() throws Exception { + log.info("[I40] Shutdown mock http server"); + mockServer.stop(5); + } + + @LocalServerPort + private int localPort; + + @Test + void testWhenApiCall_thenRewriteSuccess(@Autowired WebTestClient webClient) { + webClient.get() + .uri("http://localhost:" + localPort + "/v1/customer/customer1") + .exchange() + .expectBody() + .consumeWith((result) -> { + String body = new String(result.getResponseBody()); + log.info("[I99] body={}", body); + assertEquals("/api/customer1", body); + }); + } + + @Test + void testWhenDslCall_thenRewriteSuccess(@Autowired WebTestClient webClient) { + webClient.get() + .uri("http://localhost:" + localPort + "/v2/zip/123456") + .exchange() + .expectBody() + .consumeWith((result) -> { + String body = new String(result.getResponseBody()); + log.info("[I99] body={}", body); + assertTrue(body.matches("/api/zip/123456-\\d{3}")); + }); + } + +}