2009-02-24 12 views
199

Sto imparando JAX-RS (aka, JSR-311) usando Jersey. Ho con successo creato una risorsa Root e sto giocando intorno con parametri:JAX-RS/Jersey come personalizzare la gestione degli errori?

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

Questa grande opera, e gestisce qualsiasi formato nella localizzazione corrente che è compresa entro la Data (String) (come AAAA/mm/gg e mm/gg/aaaa). Ma se fornisco un valore che non è valido o non compreso, ottengo una risposta 404.

Ad 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?

risposta

253

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 classe 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 gettare questo nuovo creare Eccezione è sufficiente:

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

Avviso, non è necessario dichiarare l'eccezione in una clausola throws perché WebApplicationException è un runtime Eccezione. Ciò restituirà una risposta 401 al client.

Il secondo e più semplice approccio consiste nel costruire 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); 
} 

Questo codice restituisce anche un 401 al cliente.

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

Un altro approccio consiste nel racchiudere un'eccezione esistente, forse un'eccezione ObjectNotFoundException con una piccola classe 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.

+3

Nel tuo esempio, la chiamata a super() dovrebbe essere leggermente diversa:.. super (Response.Status (Status.UNAUTHORIZED) entità (messaggio) .Type ("text/plain") costruire()); Grazie per l'intuizione però. –

+0

Penso che il metodo non sarebbe stato eseguito nel suo esempio. – deamon

+61

Nello scenario menzionato nella domanda, non avrai la possibilità di lanciare un'eccezione, poiché Jersey genererà un'eccezione in quanto non sarà in grado di creare un'istanza di oggetto Date dal valore di input. C'è un modo per intercettare l'eccezione di Jersey? Esiste un'interfaccia ExceptionMapper, che però intercetta anche le eccezioni generate dal metodo (ottenere in questo caso). –

11

Una soluzione ovvia: prendi una stringa, converti in data da te. In questo modo è possibile 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 collegare i gestori anche per i tipi di dati, ma forse un po 'di semplice codice è tutto ciò che serve in questo caso.

5

Anche io come StaxMan probabilmente implementerei QueryParam come stringa, quindi gestisco la conversione, rilanciando se necessario.

Se il comportamento specifico locale è il comportamento desiderato e atteso, è necessario utilizzare il seguente per restituire l'errore 400 Bad Request:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

consultare Javadoc per javax.ws.rs.core.Response.Status per ulteriori opzioni.

26

Si potrebbe anche scrivere una classe riutilizzabile per le variabili QueryParam-annotati

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 utilizzarlo in questo modo:

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

Anche se la gestione degli errori è banale in questo caso (lanciando una risposta 400), l'utilizzo di questa classe consente di definire la gestione dei parametri in generale che potrebbe includere la registrazione ecc.

+0

Sto provando ad aggiungere un gestore param di query personalizzato in Jersey (migrando da CXF) questo aspetto è molto simile a quello che sto facendo, ma non so come installare/creare un nuovo provider. La tua classe di cui sopra non mi mostra questo. Sto usando oggetti DateTime di JodaTime per QueryParam e non ho un provider per decodificarli. È altrettanto semplice che creare una sottoclasse, dandogli una funzione di costruzione String e gestendola? –

+1

Basta creare una classe come quella di 'DateParam' in alto che racchiude un' org.joda.time.DateTime' invece di 'java.util.Calendar'. Lo usi con '@ QueryParam' piuttosto che' DateTime' stesso. –

+1

Se utilizzi Joda DateTime, la maglia viene fornita con DateTimeParam che puoi utilizzare direttamente. Non c'è bisogno di scrivere il tuo. Vedere https://github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/params/DateTimeParam.java – Srikanth

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

Creare sopra la classe. 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.

+0

È possibile utilizzare implementa ExceptionMapper anche per eccezioni generiche – userRaj

+0

Questo gestirà anche le eccezioni di WebApplication generate dal client JAX-RS, nascondendo l'origine dell'errore. Meglio avere un'eccezione personalizzata (non derivata da WebApplicationException) o lanciare WebApplications con la risposta completa. WebApplicationException generate da JAX-RS Il client deve essere gestito direttamente alla chiamata, altrimenti la risposta di un altro servizio viene passata come risposta del servizio, sebbene si tratti di un errore interno del server non gestito. –

33

Jersey lancia un com.sun.jersey.api.ParamException quando non riesce a unmarshall i parametri in modo una soluzione è quella di creare un ExceptionMapper che gestisce questi tipi di eccezioni:

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

dove dovrei creare questo mappatore specificamente per Jersey per registrarlo? – Patricio

+1

Tutto quello che devi fare è aggiungere l'annotazione @Provider, vedi qui per maggiori dettagli: http://stackoverflow.com/questions/15185299/jax-rs-jersey-exceptionmappers-user-defined-exception –

4

documentazione @QueryParam dice

" The type T of the annotated parameter, field or property must either:

1) Be a primitive type
2) Have a constructor that accepts a single String argument
3) Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String))
4) Have a registered implementation of javax.ws.rs.ext.ParamConverterProvider JAX-RS extension SPI that returns a javax.ws.rs.ext.ParamConverter instance capable of a "from string" conversion for the type.
5) Be List, Set or SortedSet, where T satisfies 2, 3 or 4 above. The resulting collection is read-only. "

Se si desidera controllare quale risposta va all'utente quando il parametro di query in forma di stringa non può essere convertito in vostro tipo T, si può buttare 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 è necessario Joda DateTime, utilizzare 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.

buon articolo da Coda Hale su questo è in 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(); 
    } 
} 

Data (String arg) costruttore è 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.

1

Questo è il comportamento corretto in realtà. Jersey proverà a trovare un gestore per il tuo input e proverà a costruire un oggetto dall'input fornito. In questo caso proverà a creare un nuovo oggetto 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 a analizzare e se non ottieni quello che vuoi, sei libero di lanciare qualsiasi eccezione desiderata da uno qualsiasi dei meccanismi di mappatura delle eccezioni (ce ne sono diversi).

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

}