From d21e03938e0c67283ae85eb61df24a99e3540323 Mon Sep 17 00:00:00 2001 From: Viktor Ardelean Date: Mon, 3 Apr 2023 00:00:58 +0300 Subject: [PATCH] BAEL-6093 add spring boot 3 url matching code sample (#13744) --- .../spring-boot-3-url-matching/README.md | 1 + .../spring-boot-3-url-matching/pom.xml | 101 ++++++++++++++++++ .../sample/URLMatchingApplication.java | 12 +++ .../com/baeldung/sample/config/WebConfig.java | 31 ++++++ .../filters/TrailingSlashRedirectFilter.java | 52 +++++++++ .../TrailingSlashRedirectFilterReactive.java | 27 +++++ .../sample/resources/GreetingsController.java | 19 ++++ .../GreetingsControllerReactive.java | 20 ++++ .../src/main/resources/application.yml | 7 ++ ...GreetingsControllerApiIntegrationTest.java | 45 ++++++++ ...sControllerReactiveApiIntegrationTest.java | 46 ++++++++ 11 files changed, 361 insertions(+) create mode 100644 spring-boot-modules/spring-boot-3-url-matching/README.md create mode 100644 spring-boot-modules/spring-boot-3-url-matching/pom.xml create mode 100644 spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/URLMatchingApplication.java create mode 100644 spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/config/WebConfig.java create mode 100644 spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilter.java create mode 100644 spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilterReactive.java create mode 100644 spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsController.java create mode 100644 spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsControllerReactive.java create mode 100644 spring-boot-modules/spring-boot-3-url-matching/src/main/resources/application.yml create mode 100644 spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerApiIntegrationTest.java create mode 100644 spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerReactiveApiIntegrationTest.java diff --git a/spring-boot-modules/spring-boot-3-url-matching/README.md b/spring-boot-modules/spring-boot-3-url-matching/README.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/README.md @@ -0,0 +1 @@ + diff --git a/spring-boot-modules/spring-boot-3-url-matching/pom.xml b/spring-boot-modules/spring-boot-3-url-matching/pom.xml new file mode 100644 index 0000000000..ca8213e1cf --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + spring-boot-3 + 0.0.1-SNAPSHOT + spring-boot-3 + URL Matching in Spring Boot + + + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-hateoas + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + + + org.springframework + spring-test + 6.0.6 + test + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + org.springframework.boot + spring-boot-starter-webflux + + + io.projectreactor + reactor-core + 3.5.4 + + + io.projectreactor + reactor-test + 3.5.4 + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + 3.0.0-M7 + + + \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/URLMatchingApplication.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/URLMatchingApplication.java new file mode 100644 index 0000000000..914f2e1e53 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/URLMatchingApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.sample; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +public class URLMatchingApplication { + public static void main(String[] args) { + SpringApplication.run(URLMatchingApplication.class, args); + } +} diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/config/WebConfig.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/config/WebConfig.java new file mode 100644 index 0000000000..d4b70c63fa --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/config/WebConfig.java @@ -0,0 +1,31 @@ +package com.baeldung.sample.config; + +import com.baeldung.sample.filters.TrailingSlashRedirectFilterReactive; +import jakarta.servlet.Filter; +import org.springframework.web.server.WebFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.baeldung.sample.filters.TrailingSlashRedirectFilter; + + + +@Configuration +public class WebConfig { + @Bean + public WebFilter trailingSlashRedirectReactiveFilter() { + return new TrailingSlashRedirectFilterReactive(); + } + @Bean + public Filter trailingSlashRedirectFilter() { + return new TrailingSlashRedirectFilter(); + } + @Bean + public FilterRegistrationBean trailingSlashFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(trailingSlashRedirectFilter()); + registrationBean.addUrlPatterns("/*"); + return registrationBean; + } +} diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilter.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilter.java new file mode 100644 index 0000000000..6db83b73f2 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilter.java @@ -0,0 +1,52 @@ +package com.baeldung.sample.filters; + +import java.io.IOException; +import jakarta.servlet.http.HttpServletRequestWrapper; +import org.springframework.stereotype.Component; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; + +@Component +public class TrailingSlashRedirectFilter implements Filter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String path = httpRequest.getRequestURI(); + + if (path.endsWith("/")) { + String newPath = path.substring(0, path.length() - 1); + HttpServletRequest newRequest = new CustomHttpServletRequestWrapper(httpRequest, newPath); + chain.doFilter(newRequest, response); + } else { + chain.doFilter(request, response); + } + } + + private static class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { + + private final String newPath; + + public CustomHttpServletRequestWrapper(HttpServletRequest request, String newPath) { + super(request); + this.newPath = newPath; + } + + @Override + public String getRequestURI() { + return newPath; + } + + @Override + public StringBuffer getRequestURL() { + StringBuffer url = new StringBuffer(); + url.append(getScheme()).append("://").append(getServerName()).append(":").append(getServerPort()) + .append(newPath); + return url; + } + } +} diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilterReactive.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilterReactive.java new file mode 100644 index 0000000000..f983d81441 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/filters/TrailingSlashRedirectFilterReactive.java @@ -0,0 +1,27 @@ +package com.baeldung.sample.filters; + +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; + +import reactor.core.publisher.Mono; + +@Component +public class TrailingSlashRedirectFilterReactive implements WebFilter { + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String path = request.getPath().value(); + + if (path.endsWith("/")) { + String newPath = path.substring(0, path.length() - 1); + ServerHttpRequest newRequest = request.mutate().path(newPath).build(); + return chain.filter(exchange.mutate().request(newRequest).build()); + } + + return chain.filter(exchange); + } +} diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsController.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsController.java new file mode 100644 index 0000000000..5c98ed764e --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsController.java @@ -0,0 +1,19 @@ +package com.baeldung.sample.resources; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class GreetingsController { + + @GetMapping("/some/greeting") + public String greeting() { + return "Hello"; + } + + @GetMapping("/some/greeting/") + public String greetingTrailingSlash() { + return "Hello with slash"; + } + +} diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsControllerReactive.java b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsControllerReactive.java new file mode 100644 index 0000000000..fde353ac43 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/java/com/baeldung/sample/resources/GreetingsControllerReactive.java @@ -0,0 +1,20 @@ +package com.baeldung.sample.resources; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import reactor.core.publisher.Mono; + +@RestController +public class GreetingsControllerReactive { + + @GetMapping("/some/reactive/greeting") + public Mono greeting() { + return Mono.just("Hello reactive"); + } + + @GetMapping("/some/reactive/greeting/") + public Mono greetingTrailingSlash() { + return Mono.just("Hello with slash reactive"); + } +} diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/main/resources/application.yml b/spring-boot-modules/spring-boot-3-url-matching/src/main/resources/application.yml new file mode 100644 index 0000000000..8d047336f6 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/src/main/resources/application.yml @@ -0,0 +1,7 @@ +spring: + mvc: + throw-exception-if-no-handler-found: true + jackson: + deserialization: + FAIL_ON_UNKNOWN_PROPERTIES: true + property-naming-strategy: SNAKE_CASE diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerApiIntegrationTest.java b/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerApiIntegrationTest.java new file mode 100644 index 0000000000..754ef4aafe --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerApiIntegrationTest.java @@ -0,0 +1,45 @@ +package com.baeldung.sample.resources; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(controllers = GreetingsController.class) +class GreetingsControllerApiIntegrationTest { + + private static final String BASEURL = "/some"; + private static final String DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_JSON_VALUE; + + @Autowired + MockMvc mvc; + + @Test + public void testGreeting() throws Exception { + mvc.perform(get(BASEURL + "/greeting").accept(DEFAULT_MEDIA_TYPE)) + .andExpect(status().isOk()) + .andExpect(content().string("Hello")); + } + + @Test + @Disabled("Disabled while TrailingSlashRedirectFilter is in use.") + public void testGreetingTrailingSlash() throws Exception { + mvc.perform(get(BASEURL + "/greeting/").accept(DEFAULT_MEDIA_TYPE)) + .andExpect(status().isOk()) + .andExpect(content().string("Hello with slash")); + } + + @Test + public void testGreetingTrailingSlashWithFilter() throws Exception { + mvc.perform(get(BASEURL + "/greeting/").accept(DEFAULT_MEDIA_TYPE)) + .andExpect(status().isOk()) + .andExpect(content().string("Hello")); + } + +} diff --git a/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerReactiveApiIntegrationTest.java b/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerReactiveApiIntegrationTest.java new file mode 100644 index 0000000000..9d5e439a74 --- /dev/null +++ b/spring-boot-modules/spring-boot-3-url-matching/src/test/java/com/baeldung/sample/resources/GreetingsControllerReactiveApiIntegrationTest.java @@ -0,0 +1,46 @@ +package com.baeldung.sample.resources; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.test.web.reactive.server.WebTestClient; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@WebFluxTest +public class GreetingsControllerReactiveApiIntegrationTest { + private static final String BASEURL = "/some/reactive"; + @Autowired + private WebTestClient webClient; + @Test + public void testGreeting() { + webClient.get().uri( BASEURL + "/greeting") + .exchange() + .expectStatus().isOk() + .expectBody().consumeWith(result -> { + String responseBody = new String(result.getResponseBody()); + assertTrue(responseBody.contains("Hello reactive")); + }); + } + @Test + @Disabled("Disabled while TrailingSlashRedirectFilter is in use.") + public void testGreetingTrailingSlash() { + webClient.get().uri(BASEURL + "/greeting/") + .exchange() + .expectStatus().isOk() + .expectBody().consumeWith(result -> { + String responseBody = new String(result.getResponseBody()); + assertTrue(responseBody.contains("Hello with slash reactive")); + }); + } + @Test + public void testGreetingTrailingSlashWithFilter() { + webClient.get().uri(BASEURL + "/greeting/") + .exchange() + .expectStatus().isOk() + .expectBody().consumeWith(result -> { + String responseBody = new String(result.getResponseBody()); + assertTrue(responseBody.contains("Hello reactive")); + }); + } +}