Skip to content

Commit acccbbe

Browse files
committed
Document UrlHandler Servlet and reactive filters
Closes gh-33784
1 parent 3cc76ef commit acccbbe

File tree

6 files changed

+176
-0
lines changed

6 files changed

+176
-0
lines changed

Diff for: framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc

+18
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,24 @@ controllers. However, when you use it with Spring Security, we advise relying on
419419

420420
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.
421421

422+
[[filters.url-handler]]
423+
=== URL Handler
424+
[.small]#xref:web/webmvc/filters.adoc#filters.url-handler[See equivalent in the Servlet stack]#
425+
426+
You may want your controller endpoints to match routes with or without a trailing slash in the URL path.
427+
For example, both "GET /home" and "GET /home/" should be handled by a controller method annotated with `@GetMapping("/home")`.
428+
429+
Adding trailing slash variants to all mapping declarations is not the best way to handle this use case.
430+
The `UrlHandlerFilter` web filter has been designed for this purpose. It can be configured to:
431+
432+
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
433+
* mutate the request to act as if the request was sent without a trailing slash and continue the processing of the request.
434+
435+
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
436+
437+
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
438+
439+
422440

423441
[[webflux-exception-handler]]
424442
== Exceptions

Diff for: framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc

+22
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ The `spring-web` module provides some useful filters:
99
* xref:web/webmvc/filters.adoc#filters-forwarded-headers[Forwarded Headers]
1010
* xref:web/webmvc/filters.adoc#filters-shallow-etag[Shallow ETag]
1111
* xref:web/webmvc/filters.adoc#filters-cors[CORS]
12+
* xref:web/webmvc/filters.adoc#filters.url-handler[URL Handler]
1213

14+
Servlet filters can be configured in the `web.xml` configuration file or using Servlet annotations.
15+
If you are using Spring Boot, you can
16+
{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].
1317

1418

1519
[[filters-http-put]]
@@ -109,4 +113,22 @@ See the sections on xref:web/webmvc-cors.adoc[CORS] and the xref:web/webmvc-cors
109113

110114

111115

116+
[[filters.url-handler]]
117+
== URL Handler
118+
[.small]#xref:web/webflux/reactive-spring.adoc#filters.url-handler[See equivalent in the Reactive stack]#
119+
120+
In previous Spring Framework versions, Spring MVC could be configured to ignore trailing slashes in URL paths
121+
when mapping incoming requests on controller methods. This could be done by enabling the `setUseTrailingSlashMatch`
122+
option on the `PathMatchConfigurer`. This means that sending a "GET /home/" request would be handled by a controller
123+
method annotated with `@GetMapping("/home")`.
124+
125+
This option has been retired, but applications are still expected to handle such requests in a safe way.
126+
The `UrlHandlerFilter` Servlet filter has been designed for this purpose. It can be configured to:
127+
128+
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
129+
* wrap the request to act as if the request was sent without a trailing slash and continue the processing of the request.
130+
131+
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
132+
133+
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
112134

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.web.webflux.filters.urlhandler;
18+
19+
import org.springframework.http.HttpStatus;
20+
import org.springframework.web.filter.reactive.UrlHandlerFilter;
21+
22+
public class UrlHandlerFilterConfiguration {
23+
24+
public void configureUrlHandlerFilter() {
25+
// tag::config[]
26+
UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter
27+
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
28+
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
29+
// will mutate the request to "/admin/user/account/" and make it as "/admin/user/account"
30+
.trailingSlashHandler("/admin/**").mutateRequest()
31+
.build();
32+
// end::config[]
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.web.webmvc.filters.urlhandler;
18+
19+
import org.springframework.http.HttpStatus;
20+
import org.springframework.web.filter.UrlHandlerFilter;
21+
22+
public class UrlHandlerFilterConfiguration {
23+
24+
public void configureUrlHandlerFilter() {
25+
// tag::config[]
26+
UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter
27+
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
28+
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
29+
// will wrap the request to "/admin/user/account/" and make it as "/admin/user/account"
30+
.trailingSlashHandler("/admin/**").wrapRequest()
31+
.build();
32+
// end::config[]
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.web.webflux.filters.urlhandler
18+
19+
import org.springframework.http.HttpStatus
20+
import org.springframework.web.filter.reactive.UrlHandlerFilter
21+
22+
class UrlHandlerFilterConfiguration {
23+
24+
fun configureUrlHandlerFilter() {
25+
// tag::config[]
26+
val urlHandlerFilter = UrlHandlerFilter
27+
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
28+
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
29+
// will mutate the request to "/admin/user/account/" and make it as "/admin/user/account"
30+
.trailingSlashHandler("/admin/**").mutateRequest()
31+
.build()
32+
// end::config[]
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.docs.web.webmvc.filters.urlhandler
18+
19+
import org.springframework.http.HttpStatus
20+
import org.springframework.web.filter.UrlHandlerFilter
21+
22+
class UrlHandlerFilterConfiguration {
23+
24+
fun configureUrlHandlerFilter() {
25+
// tag::config[]
26+
val urlHandlerFilter = UrlHandlerFilter
27+
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
28+
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
29+
// will wrap the request to "/admin/user/account/" and make it as "/admin/user/account"
30+
.trailingSlashHandler("/admin/**").wrapRequest()
31+
.build()
32+
// end::config[]
33+
}
34+
}

0 commit comments

Comments
 (0)