it-swarm.com.de

JAX-RS / Jersey Wie kann ich die Fehlerbehandlung anpassen?

Ich lerne JAX-RS (auch bekannt als JSR-311) mit Jersey. Ich habe erfolgreich eine Root-Ressource erstellt und spiele mit folgenden Parametern:

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

Dies funktioniert hervorragend und verarbeitet jedes Format im aktuellen Gebietsschema, das vom Date (String) -Konstruktor verstanden wird (wie JJJJ/mm/tt und mm/tt/JJJJ). Wenn ich jedoch einen ungültigen oder nicht verstandenen Wert eingebe, erhalte ich eine 404-Antwort.

Beispielsweise:

GET /hello?name=Mark&birthDate=X

404 Not Found

Wie kann ich dieses Verhalten anpassen? Möglicherweise ein anderer Antwortcode (wahrscheinlich "400 Bad Request")? Was ist mit dem Protokollieren eines Fehlers? Fügen Sie möglicherweise eine Beschreibung des Problems ("falsches Datumsformat") in einen benutzerdefinierten Header ein, um die Fehlerbehebung zu erleichtern. Oder eine vollständige Fehlerantwort mit Details zusammen mit einem 5xx-Statuscode zurückgeben?

209
Mark Renouf

Es gibt verschiedene Ansätze, um das Fehlerbehandlungsverhalten mit JAX-RS anzupassen. Hier sind drei der einfacheren Möglichkeiten.

Der erste Ansatz besteht darin, eine Exception-Klasse zu erstellen, die WebApplicationException erweitert.

Beispiel:

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

Und um diese neu erstellte Exception zu werfen, einfach:

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

Beachten Sie, dass Sie die Ausnahme nicht in einer throws-Klausel deklarieren müssen, da es sich bei der WebApplicationException um eine Laufzeitausnahme handelt. Dies gibt eine Antwort 401 an den Client zurück.

Der zweite und einfachere Ansatz besteht darin, eine Instanz der WebApplicationException direkt in Ihrem Code zu erstellen. Dieser Ansatz funktioniert, solange Sie keine eigenen Anwendungsausnahmen implementieren müssen.

Beispiel:

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

Auch dieser Code gibt eine 401 an den Client zurück.

Dies ist natürlich nur ein einfaches Beispiel. Sie können die Ausnahmebedingung bei Bedarf viel komplexer gestalten und den erforderlichen HTTP-Antwortcode generieren.

Ein anderer Ansatz besteht darin, eine vorhandene Ausnahme, z. B. eine ObjectNotFoundException, mit einer kleinen Wrapper-Klasse zu umschließen, die die mit einer @Provider-Annotation versehene ExceptionMapper-Schnittstelle implementiert. Dies teilt der JAX-RS-Laufzeit mit, dass, wenn die umschlossene Ausnahme ausgelöst wird, der im ExceptionMapper definierte Antwortcode zurückgegeben wird.

267
Steven Levine
@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();
}
}

Erstellen Sie über Klasse. Dies behandelt 404 (NotFoundException) und hier in der toResponse-Methode können Sie Ihre benutzerdefinierte Antwort geben. Ebenso gibt es ParamException usw., die Sie zuordnen müssen, um benutzerdefinierte Antworten bereitzustellen.

67
Arnav

Jersey löst eine com.Sun.jersey.api.ParamException aus, wenn die Zuordnung der Parameter nicht aufgehoben werden kann. Daher besteht eine Lösung darin, einen ExceptionMapper zu erstellen, der diese Ausnahmetypen verarbeitet:

@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();
    }
}
34
Jan Kronquist

Sie können auch eine wiederverwendbare Klasse für mit QueryParam annotierte Variablen schreiben

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

dann benutze es so:

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

Obwohl die Fehlerbehandlung in diesem Fall trivial ist (Auslösen einer 400-Antwort), können Sie mit dieser Klasse die Parameterbehandlung im Allgemeinen ausklammern, einschließlich der Protokollierung usw.

26

Eine naheliegende Lösung: Nehmen Sie einen String in die Hand und konvertieren Sie ihn selbst zu Date. Auf diese Weise können Sie das gewünschte Format definieren, Ausnahmen abfangen und den gesendeten Fehler entweder erneut auslösen oder anpassen. Für das Parsen sollte SimpleDateFormat gut funktionieren.

Ich bin mir sicher, dass es auch Möglichkeiten gibt, Handler für Datentypen zu verknüpfen, aber in diesem Fall ist vielleicht ein bisschen einfacher Code alles, was Sie brauchen.

11
StaxMan

Ich mag auch StaxMan würde wahrscheinlich dieses QueryParam als String implementieren, dann die Konvertierung abwickeln und nach Bedarf neu werfen.

Wenn das länderspezifische Verhalten das gewünschte und erwartete Verhalten ist, würden Sie Folgendes verwenden, um den Fehler 400 BAD REQUEST zurückzugeben:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

Weitere Optionen finden Sie im JavaDoc für javax.ws.rs.core.Response.Status .

7
dshaw

@QueryParam Dokumentation sagt

"Der Typ T des mit Anmerkungen versehenen Parameters, Felds oder der Eigenschaft muss entweder:

1) Sei ein primitiver Typ
2) Haben Sie einen Konstruktor, der ein einzelnes String-Argument akzeptiert
3) Haben Sie eine statische Methode mit dem Namen valueOf oder fromString, die ein einzelnes String-Argument akzeptiert (siehe z. B. Integer.valueOf (String)).
4) Haben Sie eine registrierte Implementierung der JAX-RS-Erweiterung javax.ws.ext.ParamConverterProvider SPI=, die eine javax.ws.rs.ext.ParamConverter-Instanz mit der Fähigkeit zurückgibt eine "from string" -Konvertierung für den Typ.
5) Be List, Set oder SortedSet, wobei T 2, 3 oder 4 oben erfüllt. Die resultierende Sammlung ist schreibgeschützt. "

Wenn Sie steuern möchten, welche Antwort an den Benutzer gesendet wird, wenn Abfrageparameter in Zeichenfolgenform nicht in Typ T konvertiert werden können, können Sie die WebApplicationException auslösen. Dropwizard wird mit folgenden * Param-Klassen geliefert, die Sie für Ihre Anforderungen verwenden können.

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

Wenn Sie Joda DateTime benötigen, verwenden Sie einfach den Dropwizard DateTimeParam .

Wenn die obige Liste nicht Ihren Anforderungen entspricht, definieren Sie Ihre eigene, indem Sie AbstractParam erweitern. Analysemethode überschreiben. Wenn Sie die Kontrolle über den Hauptteil der Fehlerantwort benötigen, überschreiben Sie die Fehlermethode.

Ein guter Artikel von Coda Hale dazu ist bei 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();
    }
}

Date (String arg) -Konstruktor ist veraltet. Ich würde Java 8 Datumsklassen verwenden, wenn Sie auf Java 8 sind. Andernfalls wird joda Datum Zeit empfohlen.

4
Srikanth

Dies ist eigentlich das richtige Verhalten. Jersey wird versuchen, einen Handler für Ihre Eingabe zu finden, und wird versuchen, ein Objekt aus der bereitgestellten Eingabe zu konstruieren. In diesem Fall wird versucht, ein neues Date-Objekt mit dem für den Konstruktor angegebenen Wert X zu erstellen. Da dies ein ungültiges Datum ist, wird Jersey gemäß Konvention 404 zurückgeben.

Sie können das Geburtsdatum neu schreiben und als Zeichenfolge einfügen. Versuchen Sie dann zu analysieren. Wenn Sie nicht das bekommen, was Sie möchten, können Sie jede gewünschte Ausnahme durch einen der Ausnahmezuordnungsmechanismen auslösen (es gibt mehrere) ).

1
ACV

Nur als Erweiterung zu @Steven Lavine answer, falls Sie das Browser-Anmeldefenster öffnen möchten. Ich fand es schwierig, die Antwort ( MDN HTTP Authentication ) vom Filter ordnungsgemäß zurückzugeben, falls der Benutzer noch nicht authentifiziert war

Dies hat mir geholfen, die Antwort zu erstellen, um die Browser-Anmeldung zu erzwingen. Beachten Sie die zusätzliche Änderung der Header. Dies setzt den Statuscode auf 401 und den Header, der den Browser veranlasst, das Dialogfeld Benutzername/Passwort zu öffnen.

// The extended Exception class
public class NotLoggedInException extends WebApplicationException {
  public NotLoggedInException(String message) {
    super(Response.status(Response.Status.UNAUTHORIZED)
      .entity(message)
      .type(MediaType.TEXT_PLAIN)
      .header("WWW-Authenticate", "Basic realm=SecuredApp").build()); 
  }
}

// Usage in the Filter
if(headers.get("Authorization") == null) { throw new NotLoggedInException("Not logged in"); }
0
Omnibyte