restful handling exceptionmapper error custom java rest error-handling jersey jax-rs

java - handling - JAX-RS/Jersey ¿Cómo personalizar el manejo de errores?



spring boot custom error response (8)

Estoy aprendiendo JAX-RS (aka, JSR-311) usando Jersey. He creado con éxito un recurso raíz y estoy jugando con parámetros:

@Path("/hello") public class HelloWorldResource { @GET @Produces("text/html") public String get( @QueryParam("name") String name, @QueryParam("birthDate") Date birthDate) { // Return a greeting with the name and age } }

Esto funciona muy bien, y maneja cualquier formato en la configuración regional actual que se entiende por el constructor Fecha (Cadena) (como YYYY / mm / dd y mm / dd / YYYY). Pero si proporciono un valor que no es válido o no se comprende, obtengo una respuesta 404.

Por ejemplo:

GET /hello?name=Mark&birthDate=X 404 Not Found

¿Cómo puedo personalizar este comportamiento? ¿Quizás un código de respuesta diferente (probablemente "400 Solicitud incorrecta")? ¿Qué hay de registrar un error? ¿Tal vez agregar una descripción del problema ("formato de fecha incorrecta") en un encabezado personalizado para ayudar a solucionar problemas? ¿O devolver una respuesta de error completa con detalles, junto con un código de estado 5xx?


Este es el comportamiento correcto en realidad. Jersey intentará encontrar un controlador para su entrada e intentará construir un objeto a partir de la entrada provista. En este caso, intentará crear un nuevo objeto Date con el valor X proporcionado al constructor. Dado que esta es una fecha no válida, por convención, Jersey devolverá 404.

Lo que puede hacer es volver a escribir y poner la fecha de nacimiento como una Cadena, luego tratar de analizar y si no obtiene lo que desea, puede lanzar cualquier excepción que desee mediante cualquiera de los mecanismos de asignación de excepciones (hay varios ).


Existen varios enfoques para personalizar el comportamiento del manejo de errores con JAX-RS. Aquí están tres de las formas más fáciles.

El primer enfoque es crear una clase de excepción que extienda WebApplicationException.

Ejemplo:

public class NotAuthorizedException extends WebApplicationException { public NotAuthorizedException(String message) { super(Response.status(Response.Status.UNAUTHORIZED) .entity(message).type(MediaType.TEXT_PLAIN).build()); } }

Y para lanzar esta nueva excepción, simplemente:

@Path("accounts/{accountId}/") public Item getItem(@PathParam("accountId") String accountId) { // An unauthorized user tries to enter throw new NotAuthorizedException("You Don''t Have Permission"); }

Tenga en cuenta que no es necesario declarar la excepción en una cláusula de lanzamientos porque la excepción WebApplicationException es una excepción en tiempo de ejecución. Esto devolverá una respuesta 401 al cliente.

El segundo y más sencillo enfoque es simplemente construir una instancia de WebApplicationException directamente en su código. Este enfoque funciona siempre que no tenga que implementar sus propias excepciones de aplicación.

Ejemplo:

@Path("accounts/{accountId}/") public Item getItem(@PathParam("accountId") String accountId) { // An unauthorized user tries to enter throw new WebApplicationException(Response.Status.UNAUTHORIZED); }

Este código también devuelve un 401 al cliente.

Por supuesto, esto es sólo un ejemplo simple. Puede hacer la excepción mucho más compleja si es necesario, y puede generar cualquier código de respuesta http que necesite.

Otro enfoque es envolver una excepción existente, tal vez una excepción ObjectNotFoundException con una pequeña clase de envoltura que implementa la interfaz de ExceptionMapper anotada con una anotación @Provider. Esto le dice al tiempo de ejecución de JAX-RS, que si se genera la excepción envuelta, devuelva el código de respuesta definido en el ExceptionMapper.


Jersey lanza una com.sun.jersey.api.ParamException cuando no logra eliminar todos los parámetros, por lo que una solución es crear un ExceptionMapper que maneje estos tipos de excepciones:

@Provider public class ParamExceptionMapper implements ExceptionMapper<ParamException> { @Override public Response toResponse(ParamException exception) { return Response.status(Status.BAD_REQUEST).entity(exception.getParameterName() + " incorrect type").build(); } }


La documentación de @QueryParam dice

"El tipo T del parámetro, campo o propiedad anotados debe:

1) Ser un tipo primitivo.
2) Tener un constructor que acepte un solo argumento de cadena
3) Tener un método estático llamado valueOf o fromString que acepte un solo argumento de String (ver, por ejemplo, Integer.valueOf (String))
4) Tener una implementación registrada de la extensión SPI javax.ws.rs.ext.ParamConverterProvider JAX-RS que devuelve una instancia javax.ws.rs.ext.ParamConverter capaz de una conversión "de cadena" para el tipo.
5) Sea List, Set o SortedSet, donde T satisface 2, 3 o 4 anteriores. La colección resultante es de solo lectura. "

Si desea controlar qué respuesta va al usuario cuando el parámetro de consulta en forma de cadena no se puede convertir a su tipo T, puede lanzar WebApplicationException. Dropwizard viene con las siguientes * clases de parámetros que puede usar para sus necesidades.

BooleanParam, DateTimeParam, IntParam, LongParam, LocalDateParam, NonEmptyStringParam, UUIDParam. Consulte https://github.com/dropwizard/dropwizard/tree/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/params

Si necesita Joda DateTime, solo use Dropwizard github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/… .

Si la lista anterior no satisface sus necesidades, defina las suyas extendiendo AbstractParam. Anular el método de análisis. Si necesita control sobre el cuerpo de respuesta de error, anule el método de error.

Un buen artículo de Coda Hale sobre esto está en http://codahale.com/what-makes-jersey-interesting-parameter-classes/

import io.dropwizard.jersey.params.AbstractParam; import java.util.Date; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; public class DateParam extends AbstractParam<Date> { public DateParam(String input) { super(input); } @Override protected Date parse(String input) throws Exception { return new Date(input); } @Override protected Response error(String input, Exception e) { // customize response body if you like here by specifying entity return Response.status(Status.BAD_REQUEST).build(); } }

El constructor Date (String arg) está en desuso. Yo usaría clases de fecha de Java 8 si está en Java 8. De lo contrario, se recomienda joda fecha y hora.


También me gusta que StaxMan probablemente implemente ese QueryParam como una cadena, y luego maneje la conversión, volviendo a generarla según sea necesario.

Si el comportamiento específico del entorno local es el comportamiento deseado y esperado, usaría lo siguiente para devolver el error 400 BAD REQUEST:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

Vea el javax.ws.rs.core.Response.Status para javax.ws.rs.core.Response.Status para más opciones.


También puede escribir una clase reutilizable para las variables anotadas de QueryParam

public class DateParam { private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); private Calendar date; public DateParam(String in) throws WebApplicationException { try { date = Calendar.getInstance(); date.setTime(format.parse(in)); } catch (ParseException exception) { throw new WebApplicationException(400); } } public Calendar getDate() { return date; } public String format() { return format.format(value.getTime()); } }

entonces úsalo así:

private @QueryParam("from") DateParam startDateParam; private @QueryParam("to") DateParam endDateParam; // ... startDateParam.getDate();

Aunque el manejo de errores es trivial en este caso (lanzar una respuesta 400), el uso de esta clase le permite descontar el manejo de parámetros en general, lo que podría incluir el registro, etc.


Una solución obvia: ingresa un String, conviértete a Date por ti mismo. De esa forma, puede definir el formato que desee, detectar excepciones y volver a lanzar o personalizar el error que se envía. Para el análisis, SimpleDateFormat debería funcionar bien.

Estoy seguro de que también hay formas de enganchar controladores para tipos de datos, pero tal vez solo necesite un poco de código simple en este caso.


@Provider public class BadURIExceptionMapper implements ExceptionMapper<NotFoundException> { public Response toResponse(NotFoundException exception){ return Response.status(Response.Status.NOT_FOUND). entity(new ErrorResponse(exception.getClass().toString(), exception.getMessage()) ). build(); } }

Crear por encima de la clase. Esto manejará 404 (NotFoundException) y aquí en el método toResponse puede dar su respuesta personalizada. De manera similar, hay ParamException, etc., que necesitaría asignar para proporcionar respuestas personalizadas.