From c00508d6cf2408d06a0447ed193ad96466d0d7b4 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 19 Jun 2023 16:22:34 +0100 Subject: [PATCH] Raise and handle NoResourceFoundException See gh-29491 --- .../ResponseEntityExceptionHandler.java | 25 +++++- .../DefaultHandlerExceptionResolver.java | 28 +++++++ .../resource/NoResourceFoundException.java | 81 +++++++++++++++++++ .../resource/ResourceHttpRequestHandler.java | 25 +++--- .../web/servlet/config/MvcNamespaceTests.java | 6 +- .../ResponseEntityExceptionHandlerTests.java | 6 ++ .../DefaultHandlerExceptionResolverTests.java | 20 +++-- .../ResourceHttpRequestHandlerTests.java | 47 ++++++----- 8 files changed, 195 insertions(+), 43 deletions(-) create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/resource/NoResourceFoundException.java diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java index eb47bb6e04..489ee14344 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -50,6 +50,7 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.resource.NoResourceFoundException; import org.springframework.web.util.WebUtils; /** @@ -121,6 +122,7 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa ServletRequestBindingException.class, MethodArgumentNotValidException.class, NoHandlerFoundException.class, + NoResourceFoundException.class, AsyncRequestTimeoutException.class, ErrorResponseException.class, ConversionNotSupportedException.class, @@ -158,6 +160,9 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa else if (ex instanceof NoHandlerFoundException subEx) { return handleNoHandlerFoundException(subEx, subEx.getHeaders(), subEx.getStatusCode(), request); } + else if (ex instanceof NoResourceFoundException subEx) { + return handleNoResourceFoundException(subEx, subEx.getHeaders(), subEx.getStatusCode(), request); + } else if (ex instanceof AsyncRequestTimeoutException subEx) { return handleAsyncRequestTimeoutException(subEx, subEx.getHeaders(), subEx.getStatusCode(), request); } @@ -348,6 +353,24 @@ public abstract class ResponseEntityExceptionHandler implements MessageSourceAwa return handleExceptionInternal(ex, null, headers, status, request); } + /** + * Customize the handling of {@link NoResourceFoundException}. + *

This method delegates to {@link #handleExceptionInternal}. + * @param ex the exception to handle + * @param headers the headers to use for the response + * @param status the status code to use for the response + * @param request the current request + * @return a {@code ResponseEntity} for the response to use, possibly + * {@code null} when the response is already committed + * @since 6.1 + */ + @Nullable + protected ResponseEntity handleNoResourceFoundException( + NoResourceFoundException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + + return handleExceptionInternal(ex, null, headers, status, request); + } + /** * Customize the handling of {@link AsyncRequestTimeoutException}. *

This method delegates to {@link #handleExceptionInternal}. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java index dee6314ede..e919b23e07 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java @@ -50,6 +50,7 @@ import org.springframework.web.multipart.support.MissingServletRequestPartExcept import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; +import org.springframework.web.servlet.resource.NoResourceFoundException; import org.springframework.web.util.WebUtils; /** @@ -125,6 +126,10 @@ import org.springframework.web.util.WebUtils; *

NoHandlerFoundException

*

404 (SC_NOT_FOUND)

* + * + *

NoResourceFoundException

+ *

404 (SC_NOT_FOUND)

+ * * *

AsyncRequestTimeoutException

*

503 (SC_SERVICE_UNAVAILABLE)

@@ -198,6 +203,9 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes else if (ex instanceof NoHandlerFoundException theEx) { mav = handleNoHandlerFoundException(theEx, request, response, handler); } + else if (ex instanceof NoResourceFoundException theEx) { + mav = handleNoResourceFoundException(theEx, request, response, handler); + } else if (ex instanceof AsyncRequestTimeoutException theEx) { mav = handleAsyncRequestTimeoutException(theEx, request, response, handler); } @@ -413,6 +421,26 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes return null; } + /** + * Handle the case where no static resource was found. + *

The default implementation returns {@code null} in which case the + * exception is handled in {@link #handleErrorResponse}. + * @param ex the {@link NoResourceFoundException} to be handled + * @param request current HTTP request + * @param response current HTTP response + * @param handler the resource handler + * @return an empty {@code ModelAndView} indicating the exception was handled, or + * {@code null} indicating the exception should be handled in {@link #handleErrorResponse} + * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} + * @since 6.1 + */ + @Nullable + protected ModelAndView handleNoResourceFoundException(NoResourceFoundException ex, + HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException { + + return null; + } + /** * Handle the case where an async request timed out. *

The default implementation returns {@code null} in which case the diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/NoResourceFoundException.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/NoResourceFoundException.java new file mode 100644 index 0000000000..bbdce08f24 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/NoResourceFoundException.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2023 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.web.servlet.resource; + +import jakarta.servlet.ServletException; + +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ProblemDetail; +import org.springframework.web.ErrorResponse; + +/** + * Raised when {@link ResourceHttpRequestHandler} can not find a resource. + * + * @author Rossen Stoyanchev + * @since 6.1 + * @see org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler + * @see org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver + */ +@SuppressWarnings("serial") +public class NoResourceFoundException extends ServletException implements ErrorResponse { + + private final HttpMethod httpMethod; + + private final String resourcePath; + + private final ProblemDetail body; + + + /** + * Create an instance. + */ + public NoResourceFoundException(HttpMethod httpMethod, String resourcePath) { + super("No static resource " + resourcePath + "."); + this.httpMethod = httpMethod; + this.resourcePath = resourcePath; + this.body = ProblemDetail.forStatusAndDetail(getStatusCode(), getMessage()); + } + + + /** + * Return the HTTP method for the request. + */ + public HttpMethod getHttpMethod() { + return this.httpMethod; + } + + /** + * Return the path used to locate the resource. + * @see org.springframework.web.servlet.HandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + */ + public String getResourcePath() { + return this.resourcePath; + } + + @Override + public HttpStatusCode getStatusCode() { + return HttpStatus.NOT_FOUND; + } + + @Override + public ProblemDetail getBody() { + return this.body; + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index ab632ddfb6..1435c8b8be 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -537,8 +537,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator /** * Processes a resource request. - *

Checks for the existence of the requested resource in the configured list of locations. - * If the resource does not exist, a {@code 404} response will be returned to the client. + *

Finds the requested resource under one of the configured locations. + * If the resource does not exist, {@link NoResourceFoundException} is raised. * If the resource exists, the request will be checked for the presence of the * {@code Last-Modified} header, and its value will be compared against the last-modified * timestamp of the given resource, returning a {@code 304} status code if the @@ -555,8 +555,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator Resource resource = getResource(request); if (resource == null) { logger.debug("Resource not found"); - response.sendError(HttpServletResponse.SC_NOT_FOUND); - return; + throw new NoResourceFoundException(HttpMethod.valueOf(request.getMethod()), getPath(request)); } if (HttpMethod.OPTIONS.matches(request.getMethod())) { @@ -611,12 +610,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator @Nullable protected Resource getResource(HttpServletRequest request) throws IOException { - String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); - if (path == null) { - throw new IllegalStateException("Required request attribute '" + - HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set"); - } - + String path = getPath(request); path = processPath(path); if (!StringUtils.hasText(path) || isInvalidPath(path)) { return null; @@ -635,6 +629,15 @@ public class ResourceHttpRequestHandler extends WebContentGenerator return resource; } + private static String getPath(HttpServletRequest request) { + String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); + if (path == null) { + throw new IllegalStateException("Required request attribute '" + + HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set"); + } + return path; + } + /** * Process the given resource path. *

The default implementation replaces: diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index 9e8db46a02..65191bbbb6 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -114,6 +114,7 @@ import org.springframework.web.servlet.resource.CssLinkResourceTransformer; import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler; import org.springframework.web.servlet.resource.EncodedResourceResolver; import org.springframework.web.servlet.resource.FixedVersionStrategy; +import org.springframework.web.servlet.resource.NoResourceFoundException; import org.springframework.web.servlet.resource.PathResourceResolver; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import org.springframework.web.servlet.resource.ResourceResolver; @@ -147,6 +148,7 @@ import org.springframework.web.util.UrlPathHelper; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.InstanceOfAssertFactories.BOOLEAN; /** @@ -417,8 +419,8 @@ public class MvcNamespaceTests { for (HandlerInterceptor interceptor : chain.getInterceptorList()) { interceptor.preHandle(request, response, chain.getHandler()); } - ModelAndView mv = adapter.handle(request, response, chain.getHandler()); - assertThat((Object) mv).isNull(); + assertThatThrownBy(() -> adapter.handle(request, response, chain.getHandler())) + .isInstanceOf(NoResourceFoundException.class); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java index ea8beabd82..b765e931be 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java @@ -63,6 +63,7 @@ import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; +import org.springframework.web.servlet.resource.NoResourceFoundException; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockHttpServletResponse; import org.springframework.web.testfixture.servlet.MockServletConfig; @@ -265,6 +266,11 @@ public class ResponseEntityExceptionHandlerTests { assertThat(responseEntity.getHeaders()).isEmpty(); } + @Test + public void noResourceFoundException() { + testException(new NoResourceFoundException(HttpMethod.GET, "/resource")); + } + @Test public void asyncRequestTimeoutException() { testException(new AsyncRequestTimeoutException()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java index 9220b7ff5f..163fd5c064 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolverTests.java @@ -48,6 +48,7 @@ import org.springframework.web.multipart.support.MissingServletRequestPartExcept import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.resource.NoResourceFoundException; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockHttpServletResponse; @@ -182,7 +183,7 @@ public class DefaultHandlerExceptionResolverTests { } @Test - public void handleMissingServletRequestPartException() throws Exception { + public void handleMissingServletRequestPartException() { MissingServletRequestPartException ex = new MissingServletRequestPartException("name"); ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); assertThat(mav).as("No ModelAndView returned").isNotNull(); @@ -194,7 +195,7 @@ public class DefaultHandlerExceptionResolverTests { } @Test - public void handleBindException() throws Exception { + public void handleBindException() { BindException ex = new BindException(new Object(), "name"); ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); assertThat(mav).as("No ModelAndView returned").isNotNull(); @@ -203,7 +204,7 @@ public class DefaultHandlerExceptionResolverTests { } @Test - public void handleNoHandlerFoundException() throws Exception { + public void handleNoHandlerFoundException() { ServletServerHttpRequest req = new ServletServerHttpRequest( new MockHttpServletRequest("GET","/resource")); NoHandlerFoundException ex = new NoHandlerFoundException(req.getMethod().name(), @@ -215,7 +216,16 @@ public class DefaultHandlerExceptionResolverTests { } @Test - public void handleConversionNotSupportedException() throws Exception { + public void handleNoResourceFoundException() { + NoResourceFoundException ex = new NoResourceFoundException(HttpMethod.GET, "/resource"); + ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); + assertThat(mav).as("No ModelAndView returned").isNotNull(); + assertThat(mav.isEmpty()).as("No Empty ModelAndView returned").isTrue(); + assertThat(response.getStatus()).as("Invalid status code").isEqualTo(404); + } + + @Test + public void handleConversionNotSupportedException() { ConversionNotSupportedException ex = new ConversionNotSupportedException(new Object(), String.class, new Exception()); ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); @@ -228,7 +238,7 @@ public class DefaultHandlerExceptionResolverTests { } @Test // SPR-14669 - public void handleAsyncRequestTimeoutException() throws Exception { + public void handleAsyncRequestTimeoutException() { Exception ex = new AsyncRequestTimeoutException(); ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); assertThat(mav).as("No ModelAndView returned").isNotNull(); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java index a677cc685f..8b75309059 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java @@ -29,7 +29,6 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestMethodNotSupportedException; @@ -44,6 +43,7 @@ import org.springframework.web.testfixture.servlet.MockServletContext; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -367,11 +367,11 @@ class ResourceHttpRequestHandlerTests { testInvalidPath("%2F%2F%2E%2E%2F%2F%2E%2E" + secretPath, handler); } - private void testInvalidPath(String requestPath, ResourceHttpRequestHandler handler) throws Exception { + private void testInvalidPath(String requestPath, ResourceHttpRequestHandler handler) { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, requestPath); this.response = new MockHttpServletResponse(); - handler.handleRequest(this.request, this.response); - assertThat(this.response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); + assertThatThrownBy(() -> handler.handleRequest(this.request, this.response)) + .isInstanceOf(NoResourceFoundException.class); } @Test @@ -409,19 +409,17 @@ class ResourceHttpRequestHandlerTests { testResolvePathWithTraversal(location, "/ " + secretPath); } - private void testResolvePathWithTraversal(Resource location, String requestPath) throws Exception { + private void testResolvePathWithTraversal(Resource location, String requestPath) { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, requestPath); this.response = new MockHttpServletResponse(); - this.handler.handleRequest(this.request, this.response); - assertThat(this.response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); + assertNotFound(); } @Test - void ignoreInvalidEscapeSequence() throws Exception { + void ignoreInvalidEscapeSequence() { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/%foo%/bar.txt"); this.response = new MockHttpServletResponse(); - this.handler.handleRequest(this.request, this.response); - assertThat(this.response.getStatus()).isEqualTo(404); + assertNotFound(); } @Test @@ -506,32 +504,29 @@ class ResourceHttpRequestHandlerTests { @Test void directory() throws Exception { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "js/"); - this.handler.handleRequest(this.request, this.response); - assertThat(this.response.getStatus()).isEqualTo(404); + assertNotFound(); } @Test void directoryInJarFile() throws Exception { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "underscorejs/"); - this.handler.handleRequest(this.request, this.response); - assertThat(this.response.getStatus()).isEqualTo(404); + assertNotFound(); } @Test - void missingResourcePath() throws Exception { + void missingResourcePath() { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, ""); - this.handler.handleRequest(this.request, this.response); - assertThat(this.response.getStatus()).isEqualTo(404); + assertNotFound(); } @Test - void noPathWithinHandlerMappingAttribute() throws Exception { + void noPathWithinHandlerMappingAttribute() { assertThatIllegalStateException().isThrownBy(() -> this.handler.handleRequest(this.request, this.response)); } @Test - void unsupportedHttpMethod() throws Exception { + void unsupportedHttpMethod() { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css"); this.request.setMethod("POST"); assertThatExceptionOfType(HttpRequestMethodNotSupportedException.class).isThrownBy(() -> @@ -539,19 +534,18 @@ class ResourceHttpRequestHandlerTests { } @Test - void resourceNotFound() throws Exception { + void testResourceNotFound() { for (HttpMethod method : HttpMethod.values()) { this.request = new MockHttpServletRequest("GET", ""); this.response = new MockHttpServletResponse(); - resourceNotFound(method); + testResourceNotFound(method); } } - private void resourceNotFound(HttpMethod httpMethod) throws Exception { + private void testResourceNotFound(HttpMethod httpMethod) { this.request.setMethod(httpMethod.name()); this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "not-there.css"); - this.handler.handleRequest(this.request, this.response); - assertThat(this.response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); + assertNotFound(); } @Test @@ -756,6 +750,11 @@ class ResourceHttpRequestHandlerTests { } + private void assertNotFound() { + assertThatThrownBy(() -> this.handler.handleRequest(this.request, this.response)) + .isInstanceOf(NoResourceFoundException.class); + } + private long resourceLastModified(String resourceName) throws IOException { return new ClassPathResource(resourceName, getClass()).getFile().lastModified(); }