diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-end-to-end-integration-tests.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-end-to-end-integration-tests.adoc index a2027b67ce..9b3d38ccff 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-end-to-end-integration-tests.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-end-to-end-integration-tests.adoc @@ -1,7 +1,7 @@ [[spring-mvc-test-vs-end-to-end-integration-tests]] = MockMvc vs End-to-End Tests -MockMVc is built on Servlet API mock implementations from the +MockMvc is built on Servlet API mock implementations from the `spring-test` module and does not rely on a running container. Therefore, there are some differences when compared to full end-to-end integration tests with an actual client and a live server running. diff --git a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-streaming-response.adoc b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-streaming-response.adoc index d7524499da..6a24b6ca7c 100644 --- a/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-streaming-response.adoc +++ b/framework-docs/modules/ROOT/pages/testing/spring-mvc-test-framework/vs-streaming-response.adoc @@ -1,38 +1,16 @@ [[spring-mvc-test-vs-streaming-response]] = Streaming Responses -The best way to test streaming responses such as Server-Sent Events is through the -<> which can be used as a test client to connect to a `MockMvc` instance -to perform tests on Spring MVC controllers without a running server. For example: +You can use `WebTestClient` to test xref:testing/webtestclient.adoc#webtestclient-stream[streaming responses] +such as Server-Sent Events. However, `MockMvcWebTestClient` doesn't support infinite +streams because there is no way to cancel the server stream from the client side. +To test infinite streams, you'll need to +xref:testing/webtestclient.adoc#webtestclient-server-config[bind to] a running server, +or when using Spring Boot, +{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test with a running server]. -[tabs] -====== -Java:: -+ -[source,java,indent=0,subs="verbatim,quotes",role="primary"] ----- - WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build(); - - FluxExchangeResult exchangeResult = client.get() - .uri("/persons") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType("text/event-stream") - .returnResult(Person.class); - - // Use StepVerifier from Project Reactor to test the streaming response - - StepVerifier.create(exchangeResult.getResponseBody()) - .expectNext(new Person("N0"), new Person("N1"), new Person("N2")) - .expectNextCount(4) - .consumeNextWith(person -> assertThat(person.getName()).endsWith("7")) - .thenCancel() - .verify(); ----- -====== - -`WebTestClient` can also connect to a live server and perform full end-to-end integration -tests. This is also supported in Spring Boot where you can -{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server]. +`MockMvcWebTestClient` does support asynchronous responses, and even streaming responses. +The limitation is that it can't influence the server to stop, and therefore the server +must finish writing the response on its own. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/interceptors.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/interceptors.adoc index 165673bec8..d5354b4b29 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/interceptors.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/interceptors.adoc @@ -5,14 +5,10 @@ You can register interceptors to apply to incoming requests, as the following ex include-code::./WebConfiguration[tag=snippet,indent=0] -NOTE: Interceptors are not ideally suited as a security layer due to the potential -for a mismatch with annotated controller path matching, which can also match trailing -slashes and path extensions transparently, along with other path matching options. Many -of these options have been deprecated but the potential for a mismatch remains. -Generally, we recommend using Spring Security which includes a dedicated -https://docs.spring.io/spring-security/reference/servlet/integrations/mvc.html#mvc-requestmatcher[MvcRequestMatcher] -to align with Spring MVC path matching and also has a security firewall that blocks many -unwanted characters in URL paths. +WARNING: Interceptors are not ideally suited as a security layer due to the potential for +a mismatch with annotated controller path matching. Generally, we recommend using Spring +Security, or alternatively a similar approach integrated with the Servlet filter chain, +and applied as early as possible. NOTE: The XML config declares interceptors as `MappedInterceptor` beans, and those are in turn detected by any `HandlerMapping` bean, including those from other frameworks. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/handlermapping-interceptor.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/handlermapping-interceptor.adoc index 95aa38fbd3..f153256002 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/handlermapping-interceptor.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-servlet/handlermapping-interceptor.adoc @@ -1,34 +1,29 @@ [[mvc-handlermapping-interceptor]] = Interception -All `HandlerMapping` implementations support handler interceptors that are useful when -you want to apply specific functionality to certain requests -- for example, checking for -a principal. Interceptors must implement `HandlerInterceptor` from the -`org.springframework.web.servlet` package with three methods that should provide enough -flexibility to do all kinds of pre-processing and post-processing: +All `HandlerMapping` implementations support handler interception which is useful when +you want to apply functionality across requests. A `HandlerInterceptor` can implement the +following: -* `preHandle(..)`: Before the actual handler is run -* `postHandle(..)`: After the handler is run -* `afterCompletion(..)`: After the complete request has finished +* `preHandle(..)` -- callback before the actual handler is run that returns a boolean. +If the method returns `true`, execution continues; if it returns `false`, the rest of the +execution chain is bypassed and the handler is not called. +* `postHandle(..)` -- callback after the handler is run. +* `afterCompletion(..)` -- callback after the complete request has finished. -The `preHandle(..)` method returns a boolean value. You can use this method to break or -continue the processing of the execution chain. When this method returns `true`, the -handler execution chain continues. When it returns false, the `DispatcherServlet` -assumes the interceptor itself has taken care of requests (and, for example, rendered an -appropriate view) and does not continue executing the other interceptors and the actual -handler in the execution chain. +NOTE: For `@ResponseBody` and `ResponseEntity` controller methods, the response is written +and committed within the `HandlerAdapter`, before `postHandle` is called. That means it is +too late to change the response, such as to add an extra header. You can implement +`ResponseBodyAdvice` and declare it as an +xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it +directly on `RequestMappingHandlerAdapter`. See xref:web/webmvc/mvc-config/interceptors.adoc[Interceptors] in the section on MVC configuration for examples of how to configure interceptors. You can also register them directly by using setters on individual `HandlerMapping` implementations. -`postHandle` method is less useful with `@ResponseBody` and `ResponseEntity` methods for -which the response is written and committed within the `HandlerAdapter` and before -`postHandle`. That means it is too late to make any changes to the response, such as adding -an extra header. For such scenarios, you can implement `ResponseBodyAdvice` and either -declare it as an xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it directly on -`RequestMappingHandlerAdapter`. - - - +WARNING: Interceptors are not ideally suited as a security layer due to the potential for +a mismatch with annotated controller path matching. Generally, we recommend using Spring +Security, or alternatively a similar approach integrated with the Servlet filter chain, +and applied as early as possible. diff --git a/spring-web/src/main/java/org/springframework/web/ErrorResponse.java b/spring-web/src/main/java/org/springframework/web/ErrorResponse.java index 04a1c294d0..c3d1f8fed4 100644 --- a/spring-web/src/main/java/org/springframework/web/ErrorResponse.java +++ b/spring-web/src/main/java/org/springframework/web/ErrorResponse.java @@ -61,6 +61,11 @@ public interface ErrorResponse { * Return the body for the response, formatted as an RFC 9457 * {@link ProblemDetail} whose {@link ProblemDetail#getStatus() status} * should match the response status. + *

Note: The returned {@code ProblemDetail} may be + * updated before the response is rendered, e.g. via + * {@link #updateAndGetBody(MessageSource, Locale)}. Therefore, implementing + * methods should use an instance field, and should not re-create the + * {@code ProblemDetail} on every call, nor use a static variable. */ ProblemDetail getBody(); diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java index 100dd59303..ea0e57f608 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/AsyncRequestTimeoutException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -37,6 +37,9 @@ import org.springframework.web.ErrorResponse; @SuppressWarnings("serial") public class AsyncRequestTimeoutException extends RuntimeException implements ErrorResponse { + private final ProblemDetail body = ProblemDetail.forStatus(getStatusCode()); + + @Override public HttpStatusCode getStatusCode() { return HttpStatus.SERVICE_UNAVAILABLE; @@ -44,7 +47,7 @@ public class AsyncRequestTimeoutException extends RuntimeException implements Er @Override public ProblemDetail getBody() { - return ProblemDetail.forStatus(getStatusCode()); + return this.body; } } diff --git a/spring-web/src/main/java/org/springframework/web/multipart/MaxUploadSizeExceededException.java b/spring-web/src/main/java/org/springframework/web/multipart/MaxUploadSizeExceededException.java index f781b7ef6e..5ccdf47278 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/MaxUploadSizeExceededException.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/MaxUploadSizeExceededException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * 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. @@ -33,10 +33,9 @@ import org.springframework.web.ErrorResponse; @SuppressWarnings("serial") public class MaxUploadSizeExceededException extends MultipartException implements ErrorResponse { - private static final ProblemDetail body = + private final ProblemDetail body = ProblemDetail.forStatusAndDetail(HttpStatus.PAYLOAD_TOO_LARGE, "Maximum upload size exceeded"); - private final long maxUploadSize; @@ -76,7 +75,7 @@ public class MaxUploadSizeExceededException extends MultipartException implement @Override public ProblemDetail getBody() { - return body; + return this.body; } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java index d904cb6d25..524e4505da 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -30,9 +30,9 @@ import org.springframework.web.method.HandlerMethod; * *

A HandlerInterceptor gets called before the appropriate HandlerAdapter * triggers the execution of the handler itself. This mechanism can be used - * for a large field of preprocessing aspects, e.g. for authorization checks, - * or common handler behavior like locale or theme changes. Its main purpose - * is to allow for factoring out repetitive handler code. + * for a large field of preprocessing aspects, or common handler behavior + * like locale or theme changes. Its main purpose is to allow for factoring + * out repetitive handler code. * *

In an asynchronous processing scenario, the handler may be executed in a * separate thread while the main thread exits without rendering or invoking the @@ -63,6 +63,12 @@ import org.springframework.web.method.HandlerMethod; * forms and GZIP compression. This typically shows when one needs to map the * filter to certain content types (e.g. images), or to all requests. * + *

Note: Interceptors are not ideally suited as a security + * layer due to the potential for a mismatch with annotated controller path matching. + * Generally, we recommend using Spring Security, or alternatively a similar + * approach integrated with the Servlet filter chain, and applied as early as + * possible. + * * @author Juergen Hoeller * @since 20.06.2003 * @see HandlerExecutionChain#getInterceptors