001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.mvc.support; 018 019import java.io.IOException; 020import java.util.List; 021import javax.servlet.http.HttpServletRequest; 022import javax.servlet.http.HttpServletResponse; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.beans.ConversionNotSupportedException; 028import org.springframework.beans.TypeMismatchException; 029import org.springframework.core.Ordered; 030import org.springframework.http.MediaType; 031import org.springframework.http.converter.HttpMessageNotReadableException; 032import org.springframework.http.converter.HttpMessageNotWritableException; 033import org.springframework.util.CollectionUtils; 034import org.springframework.util.StringUtils; 035import org.springframework.validation.BindException; 036import org.springframework.validation.BindingResult; 037import org.springframework.web.HttpMediaTypeNotAcceptableException; 038import org.springframework.web.HttpMediaTypeNotSupportedException; 039import org.springframework.web.HttpRequestMethodNotSupportedException; 040import org.springframework.web.bind.MethodArgumentNotValidException; 041import org.springframework.web.bind.MissingPathVariableException; 042import org.springframework.web.bind.MissingServletRequestParameterException; 043import org.springframework.web.bind.ServletRequestBindingException; 044import org.springframework.web.bind.annotation.ModelAttribute; 045import org.springframework.web.bind.annotation.RequestBody; 046import org.springframework.web.bind.annotation.RequestPart; 047import org.springframework.web.context.request.async.AsyncRequestTimeoutException; 048import org.springframework.web.multipart.MultipartFile; 049import org.springframework.web.multipart.support.MissingServletRequestPartException; 050import org.springframework.web.servlet.ModelAndView; 051import org.springframework.web.servlet.NoHandlerFoundException; 052import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; 053 054/** 055 * The default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver} 056 * interface, resolving standard Spring MVC exceptions and translating them to corresponding 057 * HTTP status codes. 058 * 059 * <p>This exception resolver is enabled by default in the common Spring 060 * {@link org.springframework.web.servlet.DispatcherServlet}. 061 * 062 * @author Arjen Poutsma 063 * @author Rossen Stoyanchev 064 * @author Juergen Hoeller 065 * @since 3.0 066 * @see org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler 067 * @see #handleNoSuchRequestHandlingMethod 068 * @see #handleHttpRequestMethodNotSupported 069 * @see #handleHttpMediaTypeNotSupported 070 * @see #handleMissingServletRequestParameter 071 * @see #handleServletRequestBindingException 072 * @see #handleTypeMismatch 073 * @see #handleHttpMessageNotReadable 074 * @see #handleHttpMessageNotWritable 075 * @see #handleMethodArgumentNotValidException 076 * @see #handleMissingServletRequestParameter 077 * @see #handleMissingServletRequestPartException 078 * @see #handleBindException 079 */ 080public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver { 081 082 /** 083 * Log category to use when no mapped handler is found for a request. 084 * @see #pageNotFoundLogger 085 */ 086 public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; 087 088 /** 089 * Additional logger to use when no mapped handler is found for a request. 090 * @see #PAGE_NOT_FOUND_LOG_CATEGORY 091 */ 092 protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); 093 094 095 /** 096 * Sets the {@linkplain #setOrder(int) order} to {@link #LOWEST_PRECEDENCE}. 097 */ 098 public DefaultHandlerExceptionResolver() { 099 setOrder(Ordered.LOWEST_PRECEDENCE); 100 setWarnLogCategory(getClass().getName()); 101 } 102 103 104 @Override 105 @SuppressWarnings("deprecation") 106 protected ModelAndView doResolveException( 107 HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { 108 109 try { 110 if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) { 111 return handleNoSuchRequestHandlingMethod( 112 (org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex, 113 request, response, handler); 114 } 115 else if (ex instanceof HttpRequestMethodNotSupportedException) { 116 return handleHttpRequestMethodNotSupported( 117 (HttpRequestMethodNotSupportedException) ex, request, response, handler); 118 } 119 else if (ex instanceof HttpMediaTypeNotSupportedException) { 120 return handleHttpMediaTypeNotSupported( 121 (HttpMediaTypeNotSupportedException) ex, request, response, handler); 122 } 123 else if (ex instanceof HttpMediaTypeNotAcceptableException) { 124 return handleHttpMediaTypeNotAcceptable( 125 (HttpMediaTypeNotAcceptableException) ex, request, response, handler); 126 } 127 else if (ex instanceof MissingPathVariableException) { 128 return handleMissingPathVariable( 129 (MissingPathVariableException) ex, request, response, handler); 130 } 131 else if (ex instanceof MissingServletRequestParameterException) { 132 return handleMissingServletRequestParameter( 133 (MissingServletRequestParameterException) ex, request, response, handler); 134 } 135 else if (ex instanceof ServletRequestBindingException) { 136 return handleServletRequestBindingException( 137 (ServletRequestBindingException) ex, request, response, handler); 138 } 139 else if (ex instanceof ConversionNotSupportedException) { 140 return handleConversionNotSupported( 141 (ConversionNotSupportedException) ex, request, response, handler); 142 } 143 else if (ex instanceof TypeMismatchException) { 144 return handleTypeMismatch( 145 (TypeMismatchException) ex, request, response, handler); 146 } 147 else if (ex instanceof HttpMessageNotReadableException) { 148 return handleHttpMessageNotReadable( 149 (HttpMessageNotReadableException) ex, request, response, handler); 150 } 151 else if (ex instanceof HttpMessageNotWritableException) { 152 return handleHttpMessageNotWritable( 153 (HttpMessageNotWritableException) ex, request, response, handler); 154 } 155 else if (ex instanceof MethodArgumentNotValidException) { 156 return handleMethodArgumentNotValidException( 157 (MethodArgumentNotValidException) ex, request, response, handler); 158 } 159 else if (ex instanceof MissingServletRequestPartException) { 160 return handleMissingServletRequestPartException( 161 (MissingServletRequestPartException) ex, request, response, handler); 162 } 163 else if (ex instanceof BindException) { 164 return handleBindException((BindException) ex, request, response, handler); 165 } 166 else if (ex instanceof NoHandlerFoundException) { 167 return handleNoHandlerFoundException( 168 (NoHandlerFoundException) ex, request, response, handler); 169 } 170 else if (ex instanceof AsyncRequestTimeoutException) { 171 return handleAsyncRequestTimeoutException( 172 (AsyncRequestTimeoutException) ex, request, response, handler); 173 } 174 } 175 catch (Exception handlerException) { 176 if (logger.isWarnEnabled()) { 177 logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in exception", handlerException); 178 } 179 } 180 return null; 181 } 182 183 /** 184 * Handle the case where no request handler method was found. 185 * <p>The default implementation logs a warning, sends an HTTP 404 error, and returns 186 * an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen, 187 * or the NoSuchRequestHandlingMethodException could be rethrown as-is. 188 * @param ex the NoSuchRequestHandlingMethodException to be handled 189 * @param request current HTTP request 190 * @param response current HTTP response 191 * @param handler the executed handler, or {@code null} if none chosen 192 * at the time of the exception (for example, if multipart resolution failed) 193 * @return an empty ModelAndView indicating the exception was handled 194 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 195 * @deprecated as of 4.3, along with {@link org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException} 196 */ 197 @Deprecated 198 protected ModelAndView handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex, 199 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 200 201 pageNotFoundLogger.warn(ex.getMessage()); 202 response.sendError(HttpServletResponse.SC_NOT_FOUND); 203 return new ModelAndView(); 204 } 205 206 /** 207 * Handle the case where no request handler method was found for the particular HTTP request method. 208 * <p>The default implementation logs a warning, sends an HTTP 405 error, sets the "Allow" header, 209 * and returns an empty {@code ModelAndView}. Alternatively, a fallback view could be chosen, 210 * or the HttpRequestMethodNotSupportedException could be rethrown as-is. 211 * @param ex the HttpRequestMethodNotSupportedException to be handled 212 * @param request current HTTP request 213 * @param response current HTTP response 214 * @param handler the executed handler, or {@code null} if none chosen 215 * at the time of the exception (for example, if multipart resolution failed) 216 * @return an empty ModelAndView indicating the exception was handled 217 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 218 */ 219 protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, 220 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 221 222 String[] supportedMethods = ex.getSupportedMethods(); 223 if (supportedMethods != null) { 224 response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", ")); 225 } 226 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage()); 227 return new ModelAndView(); 228 } 229 230 /** 231 * Handle the case where no {@linkplain org.springframework.http.converter.HttpMessageConverter message converters} 232 * were found for the PUT or POSTed content. 233 * <p>The default implementation sends an HTTP 415 error, sets the "Accept" header, 234 * and returns an empty {@code ModelAndView}. Alternatively, a fallback view could 235 * be chosen, or the HttpMediaTypeNotSupportedException could be rethrown as-is. 236 * @param ex the HttpMediaTypeNotSupportedException to be handled 237 * @param request current HTTP request 238 * @param response current HTTP response 239 * @param handler the executed handler 240 * @return an empty ModelAndView indicating the exception was handled 241 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 242 */ 243 protected ModelAndView handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, 244 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 245 246 response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE); 247 List<MediaType> mediaTypes = ex.getSupportedMediaTypes(); 248 if (!CollectionUtils.isEmpty(mediaTypes)) { 249 response.setHeader("Accept", MediaType.toString(mediaTypes)); 250 } 251 return new ModelAndView(); 252 } 253 254 /** 255 * Handle the case where no {@linkplain org.springframework.http.converter.HttpMessageConverter message converters} 256 * were found that were acceptable for the client (expressed via the {@code Accept} header. 257 * <p>The default implementation sends an HTTP 406 error and returns an empty {@code ModelAndView}. 258 * Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotAcceptableException 259 * could be rethrown as-is. 260 * @param ex the HttpMediaTypeNotAcceptableException to be handled 261 * @param request current HTTP request 262 * @param response current HTTP response 263 * @param handler the executed handler 264 * @return an empty ModelAndView indicating the exception was handled 265 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 266 */ 267 protected ModelAndView handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, 268 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 269 270 response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE); 271 return new ModelAndView(); 272 } 273 274 /** 275 * Handle the case when a declared path variable does not match any extracted URI variable. 276 * <p>The default implementation sends an HTTP 500 error, and returns an empty {@code ModelAndView}. 277 * Alternatively, a fallback view could be chosen, or the MissingPathVariableException 278 * could be rethrown as-is. 279 * @param ex the MissingPathVariableException to be handled 280 * @param request current HTTP request 281 * @param response current HTTP response 282 * @param handler the executed handler 283 * @return an empty ModelAndView indicating the exception was handled 284 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 285 * @since 4.2 286 */ 287 protected ModelAndView handleMissingPathVariable(MissingPathVariableException ex, 288 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 289 290 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); 291 return new ModelAndView(); 292 } 293 294 /** 295 * Handle the case when a required parameter is missing. 296 * <p>The default implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}. 297 * Alternatively, a fallback view could be chosen, or the MissingServletRequestParameterException 298 * could be rethrown as-is. 299 * @param ex the MissingServletRequestParameterException to be handled 300 * @param request current HTTP request 301 * @param response current HTTP response 302 * @param handler the executed handler 303 * @return an empty ModelAndView indicating the exception was handled 304 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 305 */ 306 protected ModelAndView handleMissingServletRequestParameter(MissingServletRequestParameterException ex, 307 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 308 309 response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage()); 310 return new ModelAndView(); 311 } 312 313 /** 314 * Handle the case when an unrecoverable binding exception occurs - e.g. required header, required cookie. 315 * <p>The default implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}. 316 * Alternatively, a fallback view could be chosen, or the exception could be rethrown as-is. 317 * @param ex the exception to be handled 318 * @param request current HTTP request 319 * @param response current HTTP response 320 * @param handler the executed handler 321 * @return an empty ModelAndView indicating the exception was handled 322 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 323 */ 324 protected ModelAndView handleServletRequestBindingException(ServletRequestBindingException ex, 325 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 326 327 response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage()); 328 return new ModelAndView(); 329 } 330 331 /** 332 * Handle the case when a {@link org.springframework.web.bind.WebDataBinder} conversion cannot occur. 333 * <p>The default implementation sends an HTTP 500 error, and returns an empty {@code ModelAndView}. 334 * Alternatively, a fallback view could be chosen, or the ConversionNotSupportedException could be 335 * rethrown as-is. 336 * @param ex the ConversionNotSupportedException to be handled 337 * @param request current HTTP request 338 * @param response current HTTP response 339 * @param handler the executed handler 340 * @return an empty ModelAndView indicating the exception was handled 341 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 342 */ 343 protected ModelAndView handleConversionNotSupported(ConversionNotSupportedException ex, 344 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 345 346 sendServerError(ex, request, response); 347 return new ModelAndView(); 348 } 349 350 /** 351 * Handle the case when a {@link org.springframework.web.bind.WebDataBinder} conversion error occurs. 352 * <p>The default implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}. 353 * Alternatively, a fallback view could be chosen, or the TypeMismatchException could be rethrown as-is. 354 * @param ex the TypeMismatchException to be handled 355 * @param request current HTTP request 356 * @param response current HTTP response 357 * @param handler the executed handler 358 * @return an empty ModelAndView indicating the exception was handled 359 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 360 */ 361 protected ModelAndView handleTypeMismatch(TypeMismatchException ex, 362 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 363 364 response.sendError(HttpServletResponse.SC_BAD_REQUEST); 365 return new ModelAndView(); 366 } 367 368 /** 369 * Handle the case where a {@linkplain org.springframework.http.converter.HttpMessageConverter message converter} 370 * cannot read from a HTTP request. 371 * <p>The default implementation sends an HTTP 400 error, and returns an empty {@code ModelAndView}. 372 * Alternatively, a fallback view could be chosen, or the HttpMessageNotReadableException could be 373 * rethrown as-is. 374 * @param ex the HttpMessageNotReadableException to be handled 375 * @param request current HTTP request 376 * @param response current HTTP response 377 * @param handler the executed handler 378 * @return an empty ModelAndView indicating the exception was handled 379 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 380 */ 381 protected ModelAndView handleHttpMessageNotReadable(HttpMessageNotReadableException ex, 382 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 383 384 response.sendError(HttpServletResponse.SC_BAD_REQUEST); 385 return new ModelAndView(); 386 } 387 388 /** 389 * Handle the case where a 390 * {@linkplain org.springframework.http.converter.HttpMessageConverter message converter} 391 * cannot write to a HTTP request. 392 * <p>The default implementation sends an HTTP 500 error, and returns an empty {@code ModelAndView}. 393 * Alternatively, a fallback view could be chosen, or the HttpMessageNotWritableException could 394 * be rethrown as-is. 395 * @param ex the HttpMessageNotWritableException to be handled 396 * @param request current HTTP request 397 * @param response current HTTP response 398 * @param handler the executed handler 399 * @return an empty ModelAndView indicating the exception was handled 400 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 401 */ 402 protected ModelAndView handleHttpMessageNotWritable(HttpMessageNotWritableException ex, 403 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 404 405 sendServerError(ex, request, response); 406 return new ModelAndView(); 407 } 408 409 /** 410 * Handle the case where an argument annotated with {@code @Valid} such as 411 * an {@link RequestBody} or {@link RequestPart} argument fails validation. 412 * <p>By default, an HTTP 400 error is sent back to the client. 413 * @param request current HTTP request 414 * @param response current HTTP response 415 * @param handler the executed handler 416 * @return an empty ModelAndView indicating the exception was handled 417 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 418 */ 419 protected ModelAndView handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, 420 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 421 422 response.sendError(HttpServletResponse.SC_BAD_REQUEST); 423 return new ModelAndView(); 424 } 425 426 /** 427 * Handle the case where an {@linkplain RequestPart @RequestPart}, a {@link MultipartFile}, 428 * or a {@code javax.servlet.http.Part} argument is required but is missing. 429 * <p>By default, an HTTP 400 error is sent back to the client. 430 * @param request current HTTP request 431 * @param response current HTTP response 432 * @param handler the executed handler 433 * @return an empty ModelAndView indicating the exception was handled 434 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 435 */ 436 protected ModelAndView handleMissingServletRequestPartException(MissingServletRequestPartException ex, 437 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 438 439 response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage()); 440 return new ModelAndView(); 441 } 442 443 /** 444 * Handle the case where an {@linkplain ModelAttribute @ModelAttribute} method 445 * argument has binding or validation errors and is not followed by another 446 * method argument of type {@link BindingResult}. 447 * <p>By default, an HTTP 400 error is sent back to the client. 448 * @param request current HTTP request 449 * @param response current HTTP response 450 * @param handler the executed handler 451 * @return an empty ModelAndView indicating the exception was handled 452 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 453 */ 454 protected ModelAndView handleBindException(BindException ex, HttpServletRequest request, 455 HttpServletResponse response, Object handler) throws IOException { 456 457 response.sendError(HttpServletResponse.SC_BAD_REQUEST); 458 return new ModelAndView(); 459 } 460 461 /** 462 * Handle the case where no handler was found during the dispatch. 463 * <p>The default implementation sends an HTTP 404 error and returns an empty 464 * {@code ModelAndView}. Alternatively, a fallback view could be chosen, 465 * or the NoHandlerFoundException could be rethrown as-is. 466 * @param ex the NoHandlerFoundException to be handled 467 * @param request current HTTP request 468 * @param response current HTTP response 469 * @param handler the executed handler, or {@code null} if none chosen 470 * at the time of the exception (for example, if multipart resolution failed) 471 * @return an empty ModelAndView indicating the exception was handled 472 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 473 * @since 4.0 474 */ 475 protected ModelAndView handleNoHandlerFoundException(NoHandlerFoundException ex, 476 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 477 478 response.sendError(HttpServletResponse.SC_NOT_FOUND); 479 return new ModelAndView(); 480 } 481 482 /** 483 * Handle the case where an async request timed out. 484 * <p>The default implementation sends an HTTP 503 error. 485 * @param ex the {@link AsyncRequestTimeoutException }to be handled 486 * @param request current HTTP request 487 * @param response current HTTP response 488 * @param handler the executed handler, or {@code null} if none chosen 489 * at the time of the exception (for example, if multipart resolution failed) 490 * @return an empty ModelAndView indicating the exception was handled 491 * @throws IOException potentially thrown from {@link HttpServletResponse#sendError} 492 * @since 4.2.8 493 */ 494 protected ModelAndView handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex, 495 HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { 496 497 if (!response.isCommitted()) { 498 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); 499 } 500 else { 501 logger.warn("Async request timed out"); 502 } 503 return new ModelAndView(); 504 } 505 506 /** 507 * Invoked to send a server error. Sets the status to 500 and also sets the 508 * request attribute "javax.servlet.error.exception" to the Exception. 509 */ 510 protected void sendServerError(Exception ex, HttpServletRequest request, HttpServletResponse response) 511 throws IOException { 512 513 request.setAttribute("javax.servlet.error.exception", ex); 514 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 515 } 516 517}