JAX-RS / Jersey ¿cómo personalizar el manejo de errores?

Estoy aprendiendo JAX-RS (también conocido como JSR-311) usando Jersey. He creado con éxito un recurso raíz y estoy jugando con los 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 sea entendido por el constructor de Fecha (Serie) (como YYYY / mm / dd y mm / dd / YYYY). Pero si proporciono un valor que no es válido o no lo entiendo, recibo una respuesta 404.

Por ejemplo:

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

¿Cómo puedo personalizar este comportamiento? Tal vez un código de respuesta diferente (probablemente “400 Bad Request”)? ¿Qué hay de registrar un error? ¿Tal vez añadir 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?

Hay varios enfoques para personalizar el comportamiento de manejo de errores con JAX-RS. Aquí hay tres de las maneras 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 Excepción recién creada 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 necesita declarar la excepción en una cláusula throws porque WebApplicationException es una excepción en tiempo de ejecución. Esto devolverá una respuesta 401 al cliente.

El segundo y más fácil 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 aplicaciones.

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 solo un simple ejemplo. Puede hacer que la excepción sea mucho más compleja si es necesario, y puede generar el código de respuesta http que necesite.

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

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

Crear clase superior. Esto manejará 404 (NotFoundException) y aquí en el método toResponse puede dar su respuesta personalizada. Del mismo modo, hay ParamException, etc. que necesitaría asignar para proporcionar respuestas personalizadas.

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

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

También podría 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()); } } 

luego úsalo así:

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

Aunque el manejo de errores es trivial en este caso (arrojando una respuesta de 400), el uso de esta clase le permite factorizar el manejo de parámetros en general, lo que puede incluir el registro, etc.

Una solución obvia: tomar una cadena, convertir a la fecha usted mismo. De esta 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 a los manejadores para los tipos de datos, pero tal vez un poco de código simple sea todo lo que necesita en este caso.

También me gusta que StaxMan probablemente implemente ese QueryParam como una Cadena, luego maneje la conversión, reiniciando según sea necesario.

Si el comportamiento específico de la configuración regional es el comportamiento deseado y esperado, debe usar lo siguiente para devolver el error de 400 MALA PETICIÓN:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

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

La documentación de @QueryParam dice

“El tipo T del parámetro anotado, campo o propiedad debe:

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

Si desea controlar qué respuesta recibe el 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 * Param que puedes usar para tus necesidades.

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

Si necesita Joda DateTime, simplemente use Dropwizard DateTimeParam .

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

Un buen artículo de Coda Hale sobre esto es 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 { 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. Usaría las clases de fecha Java 8 si está en Java 8. De lo contrario, se recomienda la fecha joda.

Este es el comportamiento correcto en realidad. Jersey intentará encontrar un controlador para su entrada e intentará construir un objeto a partir de la información provista. En este caso, intentará crear un nuevo objeto Date con el valor X proporcionado al constructor. Como 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 intente analizar y si no obtiene lo que desea, puede lanzar cualquier excepción que desee mediante cualquiera de los mecanismos de mapeo de excepciones (hay varios )

 abtrack class Responce { private String message ; private int code ; public String getMessage(){ return this.message ; } public void setMessage(String message){ this.message =message ; } public String getCode(){ return this.code ; } public void setCode(String code){ this.code =code ; } } @XmlRootElement(name='MyResponce') class MyResponce extends Responce { } @Path("/hello") public class HelloWorldResource { @GET @Produces("text/html") public MyResponce get( MyResponce myResponce = new MyResponce (); @QueryParam("name") String name, @QueryParam("birthDate") Date birthDate) throw WSException { try { }catch(Exception) myResponce.setCode(400); myResponce.setMessage("Exception") } return myResponce ; } 
    Intereting Posts