Spring MVC 3.2 - Консолидация содержимого для страниц с ошибками?
Чтобы глобально обрабатывать ошибки (например, HTTP 404), которые могут возникать вне контроллера, у меня есть записи, похожие на следующие в моем web.xml:
<error-page>
<error-code>404</error-code>
<location>/errors/404</location>
</error-page>
В моем ErrorController у меня есть соответствующие методы, похожие на следующие:
@Controller
@RequestMapping("/errors")
public class ErrorController {
@RequestMapping(value = "/404", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<ErrorResponse> error404() {
ErrorResponse errorBody = new ErrorResponse(404, "Resource Not Found!");
return new ResponseEntity<ErrorResponse>(errorBody, HttpStatus.NOT_FOUND);
}
}
Проблема, с которой я сталкиваюсь, заключается в том, что сконфигурированные ContentNegotiationManager
и конвертеры сообщений, которые я сконфигурировал, в этом случае не используются. Я подозреваю, что, поскольку запрос перенаправляется на страницу с ошибкой, исходные атрибуты запроса, используемые в согласовании контента, теряются, и это рассматривается как полностью отдельный запрос. (т.е. исходный запрос для /mycontroller/badresource.json → /errors/404 (без расширения файла))
Есть ли какой-либо способ в обработчике ошибок, подобный этому, определять и/или отвечать соответствующим типом контента, как это запрошено в исходном запросе?
Ответы
Ответ 1
Я придумал немного для этого, но, похоже, он работает. В основном это связано с дополнительным переходом в обработке ошибок, чтобы определить расширение файла исходного запроса.
В моем web.xml у меня есть ошибка, перенаправленная на промежуточное действие:
<error-page>
<error-code>404</error-code>
<location>/errors/redirect</location>
</error-page>
Затем перед отправкой на действие, которое будет генерировать ответ об ошибке, выполняется проверка, чтобы проверить, существует ли расширение файла в исходном запросе. Если бы это было так, это обеспечило бы его добавление к прямому URI. Заголовки HTTP автоматически пересылаются, поэтому, если согласование содержимого, которое у вас установлено, включает только расширения файлов или HTTP-заголовок, это позволит эффективно разрешить "странице ошибки" возвращать ошибку в соответствующем типе контента.
@Controller
@RequestMapping("/errors")
public class ErrorController {
@RequestMapping(value = "/redirect", method = RequestMethod.GET)
public void errorRedirect(HttpServletRequest request, HttpServletResponse response) {
// Get original request URI
String uri = (String)request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE);
// Try to determine file extension
String filename = WebUtils.extractFullFilenameFromUrlPath(uri);
String extension = StringUtils.getFilenameExtension(filename);
extension = StringUtils.hasText(extension) ? "." + extension : "";
// Forward request to appropriate handler with original request file extension (i.e. /errors/404.json)
String forwardUri = "/errors/404" + extension);
request.getRequestDispatcher(forwardUri).forward(request, response);
}
@RequestMapping(value = "/404", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<ErrorResponse> error404() {
ErrorResponse errorBody = new ErrorResponse(404, "Resource Not Found!");
return new ResponseEntity<ErrorResponse>(errorBody, HttpStatus.NOT_FOUND);
}
}
Ответ 2
Да, действительно, коды ошибок Exceptions и HTTP Response - это две разные вещи.
Вы можете адаптировать код, как показано ниже, чтобы у вас был доступ к requestUri
.
Я думаю, вы можете найти тип контента на его основе. Я знаю его грубую, но я не думаю, что у нас есть альтернативное решение:
@RequestMapping(value = "/404", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<ErrorResponse> error404(HttpServletRequest request) {
ErrorResponse errorBody = new ErrorResponse(404, "Resource Not Found!");
String requestUri = request.getRequestURI();
return new ResponseEntity<ErrorResponse>(errorBody, HttpStatus.NOT_FOUND);
}
В примере я предполагаю, что ваше приложение является службой REST, тогда, вероятно, вы можете обратиться к этой ссылке о том, как обрабатывается 404, в REST full обслуживание.
Ответ 3
Spring MVC 3.2 теперь включает полезную аннотацию под названием @ControllerAdvice
. Вы можете добавить метод ExceptionHandler
, который будет глобально обрабатывать любое исключение, которое вы определили.
Для меня мне только два возможных типа контента возвращаются клиенту - application/json
или text/html
.
Вот как я его установил -
@ControllerAdvice
public class ExceptionControllerAdvice {
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private static final MediaType JSON_MEDIA_TYPE = new MediaType("application", "json", DEFAULT_CHARSET);
//I decided to handle all exceptions in this one method
@ExceptionHandler(Throwable.class)
public @ResponseBody String handleThrowable(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {
...
if(supportsJsonResponse(request.getHeader("Accept"))) {
//return response as JSON
response.setStatus(statusCode);
response.setContentType(JSON_MEDIA_TYPE.toString());
//TODO serialize your error in a JSON format
//return ...
} else {
//return as HTML
response.setContentType("text/html");
response.sendError(statusCode, exceptionMessage);
return null;
}
}
private boolean supportsJsonResponse(String acceptHeader) {
List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader);
for(MediaType mediaType : mediaTypes) {
if(JSON_MEDIA_TYPE.includes(mediaType)) {
return true;
}
}
return false;
}
}