java - responsebody - spring mvc español
Cómo manejar las excepciones en Spring MVC de manera diferente para las solicitudes HTML y JSON (7)
Como no encontré ninguna solución para esto, escribí un código que verifica manualmente el encabezado de accept
de la solicitud para determinar el formato. Luego verifico si el usuario está conectado y envía la pila completa si es o un breve mensaje de error.
Yo uso ResponseEntity para poder devolver tanto JSON como HTML here .
Código:
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleExceptions(Exception ex, HttpServletRequest request) throws Exception {
final HttpHeaders headers = new HttpHeaders();
Object answer; // String if HTML, any object if JSON
if(jsonHasPriority(request.getHeader("accept"))) {
logger.info("Returning exception to client as json object");
headers.setContentType(MediaType.APPLICATION_JSON);
answer = errorJson(ex, isUserLoggedIn());
} else {
logger.info("Returning exception to client as html page");
headers.setContentType(MediaType.TEXT_HTML);
answer = errorHtml(ex, isUserLoggedIn());
}
final HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return new ResponseEntity<>(answer, headers, status);
}
private String errorHtml(Exception e, boolean isUserLoggedIn) {
String error = // html code with exception information here
return error;
}
private Object errorJson(Exception e, boolean isUserLoggedIn) {
// return error wrapper object which will be converted to json
return null;
}
/**
* @param acceptString - HTTP accept header field, format according to HTTP spec:
* "mime1;quality1,mime2;quality2,mime3,mime4,..." (quality is optional)
* @return true only if json is the MIME type with highest quality of all specified MIME types.
*/
private boolean jsonHasPriority(String acceptString) {
if (acceptString != null) {
final String[] mimes = acceptString.split(",");
Arrays.sort(mimes, new MimeQualityComparator());
final String firstMime = mimes[0].split(";")[0];
return firstMime.equals("application/json");
}
return false;
}
private static class MimeQualityComparator implements Comparator<String> {
@Override
public int compare(String mime1, String mime2) {
final double m1Quality = getQualityofMime(mime1);
final double m2Quality = getQualityofMime(mime2);
return Double.compare(m1Quality, m2Quality) * -1;
}
}
/**
* @param mimeAndQuality - "mime;quality" pair from the accept header of a HTTP request,
* according to HTTP spec (missing mimeQuality means quality = 1).
* @return quality of this pair according to HTTP spec.
*/
private static Double getQualityofMime(String mimeAndQuality) {
//split off quality factor
final String[] mime = mimeAndQuality.split(";");
if (mime.length <= 1) {
return 1.0;
} else {
final String quality = mime[1].split("=")[1];
return Double.parseDouble(quality);
}
}
Estoy usando el siguiente controlador de excepción en Spring 4.0.3 para interceptar excepciones y mostrar una página de error personalizada al usuario:
@ControllerAdvice
public class ExceptionHandlerController
{
@ExceptionHandler(value = Exception.class)
public ModelAndView handleError(HttpServletRequest request, Exception e)
{
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
}
Pero ahora quiero un tratamiento diferente para las solicitudes JSON, así que obtengo respuestas de error JSON para este tipo de solicitudes cuando ocurre una excepción. Actualmente, el código anterior también se desencadena por las solicitudes JSON (utilizando un encabezado Accept: application/json
) y al cliente JavaScript no le gusta la respuesta HTML.
¿Cómo puedo manejar las excepciones de manera diferente para las solicitudes HTML y JSON?
Como tiene HttpServletRequest, debería poder obtener el encabezado de solicitud "Aceptar". Entonces podría procesar la excepción basada en eso.
Algo como:
String header = request.getHeader("Accept");
if(header != null && header.equals("application/json")) {
// Process JSON exception
} else {
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
El truco es tener un controlador REST con dos asignaciones, una de las cuales especifica "text/html"
y devuelve una fuente HTML válida. El siguiente ejemplo, que se probó en Spring Boot 2.0 , asume la existencia de una plantilla separada llamada "error.html"
.
@RestController
public class CustomErrorController implements ErrorController {
@Autowired
private ErrorAttributes errorAttributes;
private Map<String,Object> getErrorAttributes( HttpServletRequest request ) {
WebRequest webRequest = new ServletWebRequest(request);
boolean includeStacktrace = false;
return errorAttributes.getErrorAttributes(webRequest,includeStacktrace);
}
@GetMapping(value="/error", produces="text/html")
ModelAndView errorHtml(HttpServletRequest request) {
return new ModelAndView("error.html",getErrorAttributes(request));
}
@GetMapping(value="/error")
Map<String,Object> error(HttpServletRequest request) {
return getErrorAttributes(request);
}
@Override public String getErrorPath() { return "/error"; }
}
Referencias
- ModelAndView - tipo de devolución para HTML
- DefaultErrorAttributes : datos utilizados para representar la plantilla HTML (y la respuesta JSON)
- BasicErrorController.java - Fuente de arranque de primavera de la que se deriva este ejemplo
La anotación ControllerAdvice tiene un elemento / atributo llamado basePackage que se puede establecer para determinar qué paquetes debe escanear para Controladores y aplicar los consejos. Entonces, lo que puede hacer es separar los Controladores que manejan las solicitudes normales y los que manejan las solicitudes AJAX en paquetes diferentes y luego escribir 2 Controladores de manejo de excepciones con las anotaciones apropiadas de ControllerAdvice. Por ejemplo:
@ControllerAdvice("com.acme.webapp.ajaxcontrollers")
public class AjaxExceptionHandlingController {
...
@ControllerAdvice("com.acme.webapp.controllers")
public class ExceptionHandlingController {
La anotación controlleradvice tiene varias propiedades que se pueden establecer desde la primavera 4. Puede definir múltiples consejos de controlador aplicando reglas diferentes.
Una propiedad es "anotaciones. Probablemente puede usar una anotación específica en el mapeo de solicitud json o puede encontrar otra propiedad más útil?
La mejor manera de hacerlo (especialmente en el servlet 3) es registrar una página de error con el contenedor y usarla para llamar a Spring @Controller
. De esta forma puede manejar diferentes tipos de respuestas en una forma Spring MVC estándar (por ejemplo, usando @RequestMapping
con produce = ... para sus clientes de máquina).
Veo por tu otra pregunta que estás usando Spring Boot. Si actualiza a una instantánea (1.1 o mejor dicho en otras palabras), obtiene este comportamiento de la caja (vea BasicErrorController
). Si desea sobrescribirlo, solo necesita asignar la ruta / error a su propio @Controller
.
Use @ControllerAdvice. Permita que el manejador de excepciones envíe un DTO que contenga los errores de campo.
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
Este código es de este sitio web: http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/ Busque allí más información.