diff --git a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc index ec038f2366f..6e980e5197e 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc @@ -419,6 +419,24 @@ controllers. However, when you use it with Spring Security, we advise relying on See the section on xref:web/webflux-cors.adoc[CORS] and the xref:web/webflux-cors.adoc#webflux-cors-webfilter[CORS `WebFilter`] for more details. +[[filters.url-handler]] +=== URL Handler +[.small]#xref:web/webmvc/filters.adoc#filters.url-handler[See equivalent in the Servlet stack]# + +You may want your controller endpoints to match routes with or without a trailing slash in the URL path. +For example, both "GET /home" and "GET /home/" should be handled by a controller method annotated with `@GetMapping("/home")`. + +Adding trailing slash variants to all mapping declarations is not the best way to handle this use case. +The `UrlHandlerFilter` web filter has been designed for this purpose. It can be configured to: + +* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant. +* mutate the request to act as if the request was sent without a trailing slash and continue the processing of the request. + +Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application: + +include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0] + + [[webflux-exception-handler]] == Exceptions diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc index 823171a3696..69eafde6d5f 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc @@ -9,7 +9,11 @@ The `spring-web` module provides some useful filters: * xref:web/webmvc/filters.adoc#filters-forwarded-headers[Forwarded Headers] * xref:web/webmvc/filters.adoc#filters-shallow-etag[Shallow ETag] * xref:web/webmvc/filters.adoc#filters-cors[CORS] +* xref:web/webmvc/filters.adoc#filters.url-handler[URL Handler] +Servlet filters can be configured in the `web.xml` configuration file or using Servlet annotations. +If you are using Spring Boot, you can +{spring-boot-docs}/how-to/webserver.html#howto.webserver.add-servlet-filter-listener.spring-bean[declare them as beans and configure them as part of your application]. [[filters-http-put]] @@ -109,4 +113,22 @@ See the sections on xref:web/webmvc-cors.adoc[CORS] and the xref:web/webmvc-cors +[[filters.url-handler]] +== URL Handler +[.small]#xref:web/webflux/reactive-spring.adoc#filters.url-handler[See equivalent in the Reactive stack]# + +In previous Spring Framework versions, Spring MVC could be configured to ignore trailing slashes in URL paths +when mapping incoming requests on controller methods. This could be done by enabling the `setUseTrailingSlashMatch` +option on the `PathMatchConfigurer`. This means that sending a "GET /home/" request would be handled by a controller +method annotated with `@GetMapping("/home")`. + +This option has been retired, but applications are still expected to handle such requests in a safe way. +The `UrlHandlerFilter` Servlet filter has been designed for this purpose. It can be configured to: + +* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant. +* wrap the request to act as if the request was sent without a trailing slash and continue the processing of the request. + +Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application: + +include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0] diff --git a/framework-docs/src/main/java/org/springframework/docs/web/webflux/filters/urlhandler/UrlHandlerFilterConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/web/webflux/filters/urlhandler/UrlHandlerFilterConfiguration.java new file mode 100644 index 00000000000..d8fbd686498 --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/web/webflux/filters/urlhandler/UrlHandlerFilterConfiguration.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2024 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 + * + * https://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.docs.web.webflux.filters.urlhandler; + +import org.springframework.http.HttpStatus; +import org.springframework.web.filter.reactive.UrlHandlerFilter; + +public class UrlHandlerFilterConfiguration { + + public void configureUrlHandlerFilter() { + // tag::config[] + UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter + // will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post" + .trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT) + // will mutate the request to "/admin/user/account/" and make it as "/admin/user/account" + .trailingSlashHandler("/admin/**").mutateRequest() + .build(); + // end::config[] + } +} diff --git a/framework-docs/src/main/java/org/springframework/docs/web/webmvc/filters/urlhandler/UrlHandlerFilterConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/web/webmvc/filters/urlhandler/UrlHandlerFilterConfiguration.java new file mode 100644 index 00000000000..ba1a4408d9c --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/web/webmvc/filters/urlhandler/UrlHandlerFilterConfiguration.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2024 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 + * + * https://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.docs.web.webmvc.filters.urlhandler; + +import org.springframework.http.HttpStatus; +import org.springframework.web.filter.UrlHandlerFilter; + +public class UrlHandlerFilterConfiguration { + + public void configureUrlHandlerFilter() { + // tag::config[] + UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter + // will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post" + .trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT) + // will wrap the request to "/admin/user/account/" and make it as "/admin/user/account" + .trailingSlashHandler("/admin/**").wrapRequest() + .build(); + // end::config[] + } +} diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/web/webflux/filters/urlhandler/UrlHandlerFilterConfiguration.kt b/framework-docs/src/main/kotlin/org/springframework/docs/web/webflux/filters/urlhandler/UrlHandlerFilterConfiguration.kt new file mode 100644 index 00000000000..aee84a78229 --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/web/webflux/filters/urlhandler/UrlHandlerFilterConfiguration.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2024 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 + * + * https://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.docs.web.webflux.filters.urlhandler + +import org.springframework.http.HttpStatus +import org.springframework.web.filter.reactive.UrlHandlerFilter + +class UrlHandlerFilterConfiguration { + + fun configureUrlHandlerFilter() { + // tag::config[] + val urlHandlerFilter = UrlHandlerFilter + // will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post" + .trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT) + // will mutate the request to "/admin/user/account/" and make it as "/admin/user/account" + .trailingSlashHandler("/admin/**").mutateRequest() + .build() + // end::config[] + } +} \ No newline at end of file diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/filters/urlhandler/UrlHandlerFilterConfiguration.kt b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/filters/urlhandler/UrlHandlerFilterConfiguration.kt new file mode 100644 index 00000000000..323ef32dc14 --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/filters/urlhandler/UrlHandlerFilterConfiguration.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2024 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 + * + * https://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.docs.web.webmvc.filters.urlhandler + +import org.springframework.http.HttpStatus +import org.springframework.web.filter.UrlHandlerFilter + +class UrlHandlerFilterConfiguration { + + fun configureUrlHandlerFilter() { + // tag::config[] + val urlHandlerFilter = UrlHandlerFilter + // will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post" + .trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT) + // will wrap the request to "/admin/user/account/" and make it as "/admin/user/account" + .trailingSlashHandler("/admin/**").wrapRequest() + .build() + // end::config[] + } +} \ No newline at end of file