diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java index 4c0870cc48..4baff7978b 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java @@ -45,6 +45,7 @@ import org.springframework.context.i18n.LocaleContext; import org.springframework.core.OrderComparator; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.ui.context.ThemeSource; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -277,6 +278,9 @@ public class DispatcherServlet extends FrameworkServlet { /** Perform cleanup of request attributes after include request? */ private boolean cleanupAfterInclude = true; + /** Throw a NoHandlerFoundException if no Handler was found to process this request? **/ + private boolean throwExceptionIfNoHandlerFound = false; + /** MultipartResolver used by this servlet */ private MultipartResolver multipartResolver; @@ -408,6 +412,24 @@ public class DispatcherServlet extends FrameworkServlet { this.detectAllViewResolvers = detectAllViewResolvers; } + /** + * Set whether to throw a NoHandlerFoundException when no Handler was found for this request. + * This exception can then be caught with a HandlerExceptionResolver or an + * {@code @ExceptionHandler} controller method. + * + *

Note that if + * {@link org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler} + * is used, then requests will always be forwarded to the default servlet and + * a NoHandlerFoundException would never be thrown in that case. + * + *

Default is "false", meaning the DispatcherServlet sends a NOT_FOUND error + * through the Servlet response. + * @since 4.0 + */ + public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) { + this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound; + } + /** * Set whether to perform cleanup of request attributes after an include request, that is, * whether to reset the original state of all request attributes after the DispatcherServlet @@ -1097,7 +1119,13 @@ public class DispatcherServlet extends FrameworkServlet { pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri + "] in DispatcherServlet with name '" + getServletName() + "'"); } - response.sendError(HttpServletResponse.SC_NOT_FOUND); + if(throwExceptionIfNoHandlerFound) { + ServletServerHttpRequest req = new ServletServerHttpRequest(request); + throw new NoHandlerFoundException(req.getMethod().name(), + req.getServletRequest().getRequestURI(),req.getHeaders()); + } else { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } } /** diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java new file mode 100644 index 0000000000..5c8989b633 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/NoHandlerFoundException.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2013 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 + * + * http://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; + +import javax.servlet.ServletException; + +import org.springframework.http.HttpHeaders; + +/** + * Exception to be thrown if DispatcherServlet is unable to determine + * a corresponding handler for an incoming HTTP request. + * The DispatcherServlet throws this exception only if its + * throwExceptionIfNoHandlerFound property is set to "true". + * + * @author Brian Clozel + * @since 4.0 + * @see org.springframework.web.servlet.DispatcherServlet + */ +@SuppressWarnings("serial") +public class NoHandlerFoundException extends ServletException { + + private final String httpMethod; + + private final String requestURL; + + private final HttpHeaders headers; + + + /** + * Constructor for NoHandlerFoundException. + * @param httpMethod the HTTP method + * @param requestURL the HTTP request URL + * @param headers the HTTP request headers + */ + public NoHandlerFoundException(String httpMethod, String requestURL, HttpHeaders headers) { + super("No handler found for " + httpMethod + " " + requestURL + ", headers=" + headers); + this.httpMethod = httpMethod; + this.requestURL = requestURL; + this.headers = headers; + } + + public String getHttpMethod() { + return this.httpMethod; + } + + public String getRequestURL() { + return this.requestURL; + } + + public HttpHeaders getHeaders() { + return this.headers; + } + +} 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 8e00031dc2..9671767886 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-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -42,6 +42,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.multipart.support.MissingServletRequestPartException; +import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; /** @@ -102,7 +103,8 @@ public abstract class ResponseEntityExceptionHandler { HttpMessageNotWritableException.class, MethodArgumentNotValidException.class, MissingServletRequestPartException.class, - BindException.class + BindException.class, + NoHandlerFoundException.class }) public final ResponseEntity handleException(Exception ex, WebRequest request) { @@ -160,6 +162,10 @@ public abstract class ResponseEntityExceptionHandler { HttpStatus status = HttpStatus.BAD_REQUEST; return handleBindException((BindException) ex, headers, status, request); } + else if (ex instanceof NoHandlerFoundException) { + HttpStatus status = HttpStatus.NOT_FOUND; + return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request); + } else { logger.warn("Unknown exception type: " + ex.getClass().getName()); HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; @@ -398,4 +404,20 @@ public abstract class ResponseEntityExceptionHandler { return handleExceptionInternal(ex, null, headers, status, request); } + /** + * Customize the response for NoHandlerFoundException. + * This method delegates to {@link #handleExceptionInternal(Exception, Object, HttpHeaders, HttpStatus, WebRequest)}. + * @param ex the exception + * @param headers the headers to be written to the response + * @param status the selected response status + * @param request the current request + * @return a {@code ResponseEntity} instance + * @since 4.0 + */ + protected ResponseEntity handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, + HttpStatus status, WebRequest request) { + + return handleExceptionInternal(ex, null, headers, status, request); + } + } 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 3b88b5400d..82bdda4009 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -46,6 +46,7 @@ import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; @@ -147,6 +148,9 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes else if (ex instanceof BindException) { return handleBindException((BindException) ex, request, response, handler); } + else if (ex instanceof NoHandlerFoundException) { + return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler); + } } catch (Exception handlerException) { logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException); @@ -416,4 +420,24 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes return new ModelAndView(); } + /** + * Handle the case where no handler was found during the dispatch. + *

The default sends an HTTP 404 error, and returns + * an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen, + * or the NoHandlerFoundException could be rethrown as-is. + * @param ex the NoHandlerFoundException to be handled + * @param request current HTTP request + * @param response current HTTP response + * @param handler the executed handler, or {@code null} if none chosen + * at the time of the exception (for example, if multipart resolution failed) + * @return an empty ModelAndView indicating the exception was handled + * @throws IOException potentially thrown from response.sendError() + * @since 4.0 + */ + protected ModelAndView handleNoHandlerFoundException(NoHandlerFoundException ex, HttpServletRequest request, + HttpServletResponse response, Object handler) throws IOException { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + return new ModelAndView(); + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java index 8628ac92a4..93ee5a1662 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java @@ -540,6 +540,20 @@ public class DispatcherServletTests extends TestCase { } } + public void testThrowExceptionIfNoHandlerFound() throws ServletException, IOException { + DispatcherServlet complexDispatcherServlet = new DispatcherServlet(); + complexDispatcherServlet.setContextClass(SimpleWebApplicationContext.class); + complexDispatcherServlet.setNamespace("test"); + complexDispatcherServlet.setThrowExceptionIfNoHandlerFound(true); + complexDispatcherServlet.init(new MockServletConfig(getServletContext(), "complex")); + + MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/unknown"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + complexDispatcherServlet.service(request, response); + assertTrue("correct error code", response.getStatus() == HttpServletResponse.SC_NOT_FOUND); + } + public void testCleanupAfterIncludeWithRemove() throws ServletException, IOException { MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/main.do"); MockHttpServletResponse response = new MockHttpServletResponse(); 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 e658506aec..cd1b66c59a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.validation.BindException; @@ -52,6 +53,7 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.multipart.support.MissingServletRequestPartException; +import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; @@ -184,6 +186,15 @@ public class ResponseEntityExceptionHandlerTests { testException(ex); } + @Test + public void noHandlerFoundException() { + ServletServerHttpRequest req = new ServletServerHttpRequest( + new MockHttpServletRequest("GET","/resource")); + Exception ex = new NoHandlerFoundException(req.getMethod().toString(), + req.getServletRequest().getRequestURI(),req.getHeaders()); + testException(ex); + } + @Test public void controllerAdvice() throws Exception { StaticWebApplicationContext cxt = new StaticWebApplicationContext(); 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 fe492bab94..3ce1db1bbf 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 @@ -26,6 +26,7 @@ import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.springframework.beans.ConversionNotSupportedException; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.tests.sample.beans.TestBean; import org.springframework.beans.TypeMismatchException; import org.springframework.core.MethodParameter; @@ -43,6 +44,7 @@ import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; /** @author Arjen Poutsma */ @@ -171,6 +173,18 @@ public class DefaultHandlerExceptionResolverTests { assertEquals("Invalid status code", 400, response.getStatus()); } + @Test + public void handleNoHandlerFoundException() throws Exception { + ServletServerHttpRequest req = new ServletServerHttpRequest( + new MockHttpServletRequest("GET","/resource")); + NoHandlerFoundException ex = new NoHandlerFoundException(req.getMethod().name(), + req.getServletRequest().getRequestURI(),req.getHeaders()); + ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); + assertNotNull("No ModelAndView returned", mav); + assertTrue("No Empty ModelAndView returned", mav.isEmpty()); + assertEquals("Invalid status code", 404, response.getStatus()); + } + @Test public void handleConversionNotSupportedException() throws Exception { ConversionNotSupportedException ex = diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml index 135ddc6c14..1165ddb2eb 100644 --- a/src/reference/docbook/mvc.xml +++ b/src/reference/docbook/mvc.xml @@ -4244,6 +4244,12 @@ public class SimpleController { 400 (Bad Request) + + NoHandlerFoundException + + 404 (Not Found) + + NoSuchRequestHandlingMethodException