JAX-RS / Jersey come personalizzare la gestione degli errori?

Sto imparando JAX-RS (aka, JSR-311) usando Jersey. Ho creato con successo una risorsa di root e sto giocando con i parametri:

@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 } } 

Funziona alla grande e gestisce qualsiasi formato nella locale corrente che è compresa dal costruttore Date (String) (come YYYY / mm / gg e mm / gg / AAAA). Ma se fornisco un valore che non è valido o non compreso, ottengo una risposta 404.

Per esempio:

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

Come posso personalizzare questo comportamento? Forse un codice di risposta diverso (probabilmente “400 Bad Request”)? Che ne dici di registrare un errore? Forse aggiungere una descrizione del problema (“formato data errato”) in un’intestazione personalizzata per facilitare la risoluzione dei problemi? Oppure restituire un’intera risposta di errore con i dettagli, insieme a un codice di stato 5xx?

Esistono diversi approcci per personalizzare il comportamento di gestione degli errori con JAX-RS. Ecco tre dei modi più semplici.

Il primo approccio consiste nel creare una class Exception che estenda WebApplicationException.

Esempio:

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

E per lanciare questa Eccezione appena creata, semplicemente:

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

Si noti, non è necessario dichiarare l’eccezione in una clausola throws perché WebApplicationException è un’eccezione di runtime. Ciò restituirà una risposta 401 al client.

Il secondo e più semplice approccio consiste nel build semplicemente un’istanza di WebApplicationException direttamente nel codice. Questo approccio funziona finché non è necessario implementare la propria applicazione Eccezioni.

Esempio:

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

Anche questo codice restituisce un 401 al client.

Naturalmente, questo è solo un semplice esempio. È ansible rendere l’Eccezione molto più complessa se necessario ed è ansible generare qualsiasi codice di risposta http necessario.

Un altro approccio consiste nel racchiudere un’eccezione esistente, forse un’eccezione ObjectNotFoundException con una piccola class wrapper che implementa l’interfaccia ExceptionMapper annotata con un’annotazione @Provider. Questo indica al runtime JAX-RS che, se viene sollevata l’eccezione avvolta, restituisce il codice di risposta definito in 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(); } } 

Crea sopra la class. Questo gestirà 404 (NotFoundException) e qui nel metodo toResponse puoi dare la tua risposta personalizzata. Allo stesso modo ci sono ParamException ecc. Che dovresti mappare per fornire risposte personalizzate.

Jersey lancia una com.sun.jersey.api.ParamException quando non riesce a eliminare i parametri, quindi una soluzione è creare un ExceptionMapper che gestisca questi tipi di eccezioni:

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

È anche ansible scrivere una class riutilizzabile per le variabili annotate da 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()); } } 

quindi usarlo in questo modo:

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

Sebbene la gestione degli errori in questo caso sia banale (generando una risposta 400), l’utilizzo di questa class consente di escludere in generale la gestione dei parametri, che potrebbe includere la registrazione ecc.

Una soluzione ovvia: prendi una stringa, converti in data da solo. In questo modo è ansible definire il formato desiderato, catturare le eccezioni e riorientare o personalizzare l’errore inviato. Per l’analisi, SimpleDateFormat dovrebbe funzionare correttamente.

Sono sicuro che ci sono modi per colbind i gestori anche per i tipi di dati, ma forse un po ‘di semplice codice è tutto ciò che serve in questo caso.

Anche a me StaxMan probabilmente implementerà QueryParam come stringa, quindi gestirà la conversione, rilanciando se necessario.

Se il comportamento specifico della locale è il comportamento desiderato e previsto, si utilizzerà il seguente comando per restituire l’errore 400 BAD REQUEST:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

Vedere il JavaDoc per javax.ws.rs.core.Response.Status per ulteriori opzioni.

La documentazione di @QueryParam dice

“Il tipo T del parametro, campo o proprietà annotati deve:

1) Sii un tipo primitivo
2) Avere un costruttore che accetta un singolo argomento String
3) Avere un metodo statico chiamato valueOf o fromString che accetta un singolo argomento String (vedere, ad esempio, Integer.valueOf (String))
4) Avere un’implementazione registrata di javax.ws.rs.ext.ParamConverterProvider Estensione SPI JAX-RS che restituisce un’istanza javax.ws.rs.ext.ParamConverter capace di una conversione “da stringa” per il tipo.
5) Be List, Set o Sorted Set, dove T soddisfa 2, 3 o 4 sopra. La raccolta risultante è di sola lettura. ”

Se si desidera controllare quale risposta viene inviata all’utente quando il parametro di query in formato stringa non può essere convertito nel tipo T, è ansible generare WebApplicationException. Dropwizard include le seguenti classi * Param che puoi utilizzare per le tue esigenze.

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

Se hai bisogno di Joda DateTime, usa Dropwizard DateTimeParam .

Se l’elenco precedente non soddisfa le tue esigenze, definisci il tuo estendendo AbstractParam. Sovrascrivi il metodo di analisi. Se è necessario il controllo sul corpo della risposta dell’errore, metodo di errore di sovrascrittura.

Il buon articolo di Coda Hale su questo è su http://codahale.com/what-makes-jersey-interesting-parameter-classs/

 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(); } } 

Il costruttore Date (String arg) è deprecato. Vorrei utilizzare le classi di data Java 8 se si è su Java 8. In caso contrario, si consiglia di utilizzare l’ora di data joda.

Questo è il comportamento corretto in realtà. Jersey proverà a trovare un gestore per il tuo input e proverà a build un object dall’input fornito. In questo caso proverà a creare un nuovo object Date con il valore X fornito al costruttore. Poiché questa è una data non valida, per convenzione Jersey restituirà 404.

Quello che puoi fare è riscrivere e mettere la data di nascita come una stringa, quindi provare ad analizzare e se non ottieni quello che vuoi, sei libero di lanciare qualsiasi eccezione tu voglia da uno qualsiasi dei meccanismi di mapping delle eccezioni (ce ne sono diversi ).

 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 ; }