wake-up-neo.net

Fehlerbehandlung in REST API mit JAX-RS

Die Aufgabe: Anstatt allgemeine HTTP 500 Internal Server Error in meinem Stacktrace und denselben schrecklichen Stacktrace auf der Clientseite zu empfangen, möchte ich meine angepasste Nachricht mit einem anderen Statuscode (z. B. 403) sehen, dass es für den Entwickler viel klarer wird. Was ist passiert? Fügen Sie dem Benutzer eine Nachricht über die Ausnahme hinzu.

Hier sind ein paar geänderte Klassen aus meiner Anwendung:

SERVER PART:

AppException.class - alle meine Server Response-Ausnahmen (bevor ich den Client zurücksende), die ich in diese Ausnahme umwandeln möchte. Irgendeine Art Entitätsklasse

public class AppException extends WebApplicationException {

Integer status;

/** application specific error code */
int code;

/** link documenting the exception */
String link;

/** detailed error description for developers */
String developerMessage;

public AppException(int status, int code, String message, String developerMessage, String link) {
    super(message);
    this.status = status;
    this.code = code;
    this.developerMessage = developerMessage;
    this.link = link;
}

public int getStatus() {
    return status;
}

public void setStatus(int status) {
    this.status = status;
}

public int getCode() {
    return code;
}

public void setCode(int code) {
    this.code = code;
}

public String getDeveloperMessage() {
    return developerMessage;
}

public void setDeveloperMessage(String developerMessage) {
    this.developerMessage = developerMessage;
}

public String getLink() {
    return link;
}

public void setLink(String link) {
    this.link = link;
}

public AppException() {
}

public AppException(String message) {
    super("Something went wrong on the server");
}
}

ÀppExceptionMapper.class - ordnet meine AppException der JAX-RS-Laufzeit zu. Stattdessen empfängt der Client die Standardausnahme, AppException. 

    @Provider
public class AppExceptionMapper implements ExceptionMapper<AppException> {

    @Override
    public Response toResponse(AppException exception) {
        return Response.status(403)
                .entity("toResponse entity").type("text/plain").build();
    }


}

ApplicationService.class - meine Service-Klasse, die AppException auslöst

 @Path("/applications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public interface ApplicationService {


    @DELETE
    @Path("/deleteById")
    void deleteById(@NotNull Long id) throws AppException;
}

KLIENTENTEIL:

ErrorHandlingFilter.class - mein Response-Catcher der AppException. Hier möchte ich jede Response-Ausnahme je nach Status in eine andere Ausnahme umwandeln. 

@Provider
public class ErrorHandlingFilter implements ClientResponseFilter {

    private static ObjectMapper _MAPPER = new ObjectMapper();

    @Override
    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
        if (responseContext.getStatus() != Response.Status.OK.getStatusCode()) {
            if(responseContext.hasEntity()) {
                Error error = _MAPPER.readValue(responseContext.getEntityStream(), Error.class);
                String message = error.getMessage();

                Response.Status status = Response.Status.fromStatusCode(responseContext.getStatus());
                AppException clientException;

                switch (status) {

                case INTERNAL_SERVER_ERROR:
                    clientException = new PermissionException(message);
                    break;


                case NOT_FOUND:
                    clientException = new MyNotFoundException(message);
                    break;

                default:
                    clientException =  new WhatEverException(message);
                }
                    throw clientException;
        }
    }
    }
}

PermissionException.class - Ausnahme bei dem, was ich AppException umwandeln möchte, wenn der Statuscode 500 angezeigt wurde.

public class PermissionException extends AppException{

        public PermissionException(String message) {
    super("403 - Forbidden. You dont have enough rights to delete this Application");

}

Integer status;

/** application specific error code */
int code;

/** link documenting the exception */
String link;

/** detailed error description for developers */
String developerMessage;

public PermissionException(int status, int code, String message, String developerMessage, String link) {
    super(message);
    this.status = status;
    this.code = code;
    this.developerMessage = developerMessage;
    this.link = link;
}

public int getStatus() {
    return status;
}

public void setStatus(int status) {
    this.status = status;
}

public int getCode() {
    return code;
}

public void setCode(int code) {
    this.code = code;
}

public String getDeveloperMessage() {
    return developerMessage;
}

public void setDeveloperMessage(String developerMessage) {
    this.developerMessage = developerMessage;
}

public String getLink() {
    return link;
}

public void setLink(String link) {
    this.link = link;
}

public PermissionException() {}


}

ApplicationPresenter.class - ein Teil der UI-Logik, bei dem ich etwas mit PermissionException zu tun haben möchte, die vom ErrorHandlingFilter geworfen wird.

@SpringPresenter
public class ApplicationPresenter implements ApplicationView.Observer {

@Resource
    private ApplicationService applicationService;

    @Resource
    private UiEnvironment uiEnvironment;

@Override
    public void deleteSelectedApplication(BeanItemGrid<Application> applicationGrid) {

        try {
applicationService.deleteById(applicationGrid.getSelectedItem().getId());
                    } catch (PermissionException e) {
                        e.printStackTrace();
                        e.getMessage();
                    } catch (AppException e2) {
                    }
}
}

Wie kann ich mein Problem lösen? Ich erhalte immer noch den Standard 500 InternalErrorException.

AKTUALISIERTE FAST DIE GANZE FRAGE EINMALIGER!

13
TARS

Wenn Sie über einen ExceptionMapper verfügen, fangen Sie die Ausnahme nicht selbst, sondern werden vom Framework abgefangen, wenn die Ressourcenmethode für eine HTTP-Anforderung aufgerufen wird.

4
gsl

Die richtige Methode zur Fehlerbehandlung ist die Registrierung von ExceptionMapper-Instanzen, die wissen, welche Antwort im Falle einer bestimmten (oder generischen) Ausnahme zurückgegeben werden soll. 

@Provider
public class PermissionExceptionHandler implements ExceptionMapper<PermissionException>{
    @Override
    public Response toResponse(PermissionException ex){
        //You can place whatever logic you need here
        return Response.status(403).entity(yourMessage).build();
    }  
}

Bitte schauen Sie sich meine andere Antwort für weitere Details an: https://stackoverflow.com/a/23858695/2588800

3
Svetlin Zarev

Ich habe hier einen anderen Ansatz. Sie können dies beim Start Ihres Anlegeservers in der Java-Hauptmethode versuchen

public static void main(String[] args) throws UnknownHostException, JSONException, IOException, Exception {

        MyMain myMain = new MyMain();

        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");

        Server jettyServer = new Server(5550);
        jettyServer.setHandler(context);
        context.setErrorHandler(new ErrorHandler());
        // default error handler for resources out of "context" scope
        jettyServer.addBean(new ErrorHandler());

        ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/*");
        jerseyServlet.setInitOrder(0);

        // Tells the Jersey Servlet which REST service/class to load.
        jerseyServlet.setInitParameter("jersey.config.server.provider.classnames",
                ControllerInn.class.getCanonicalName() );

        try {
            jettyServer.start();            
            jettyServer.join();

        } catch (Exception ex) {
            Logger.getLogger(ControllerInn.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            jettyServer.destroy();
        }
    }
    /**
     * Dummy error handler that disables any error pages or jetty related messages and returns our
     * ERROR status JSON with plain HTTP status instead. All original error messages (from our code) are preserved
     * as they are not handled by this code.
     */
    static class ErrorHandler extends ErrorPageErrorHandler {
        @Override
        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
            response.getWriter()
            .append("{\"message\":\"HTTP ERROR ")
            .append(String.valueOf(response.getStatus()))
            .append("\"}");
        }
    }

So können Sie eine Ausgabe wie diese erhalten

{"message":"HTTP ERROR 500"}

Sie können Referenz von hier

1
Young Emil

Dies ist ein Jersey-Beispiel , aber Sie können die erforderlichen Informationen aus hier extrahieren. Ich würde nur eine Ausnahme werfen und diese Ausnahme am Ende jeder gewünschten Antwort zuordnen.

Nehmen wir an, Sie haben die folgende Ressourcenmethode, obwohl die Ausnahme vorliegt:

@Path("items/{itemid}/")
public Item getItem(@PathParam("itemid") String itemid) {
  Item i = getItems().get(itemid);
  if (i == null) {
    throw new CustomNotFoundException("Item, " + itemid + ", is not found");
  }

  return i;
}

Erstellen Sie Ihre Ausnahmeklasse:

public class CustomNotFoundException extends WebApplicationException {

  /**
  * Create a HTTP 404 (Not Found) exception.
  */
  public CustomNotFoundException() {
    super(Responses.notFound().build());
  }

  /**
  * Create a HTTP 404 (Not Found) exception.
  * @param message the String that is the entity of the 404 response.
  */
  public CustomNotFoundException(String message) {
    super(Response.status(Responses.NOT_FOUND).
    entity(message).type("text/plain").build());
  }
}

Fügen Sie jetzt Ihren Exception-Mapper hinzu:

@Provider
public class EntityNotFoundMapper implements ExceptionMapper<CustomNotFoundException> {
  public Response toResponse(CustomNotFoundException  ex) {
    return Response.status(404).
      entity("Ouchhh, this item leads to following error:" + ex.getMessage()).
      type("text/plain").
      build();
  }
}

Am Ende müssen Sie Ihren Exception-Mapper registrieren, damit er in Ihrer Anwendung verwendet werden kann. Hier ist ein Pseudo-Code:

register(new EntityNotFoundMapper());
//or
register(EntityNotFoundMapper.class);
1
hiaclibe

Richtig vorgeschlagen wird, dass das Framework die Ausnahme für Sie jetzt einfängt, nachdem Sie eine ExceptionMapper..__ implementiert haben. Allerdings ist ein wichtiger Punkt, der die von Ihnen ausgeführten Phänomene auf einen Blick zeigt: Wenn Sie nicht abgefangene Ausnahmen behandeln müssen, müssen Sie dies tun Sie müssen eine Exception-Klasse haben, die ExceptionMapper implementiert, die Throwable zugeordnet wird. 

public class UncaughtExcep implements ExceptionMapper<Throwable>{

   @Override 
   public Response toResponse(Throwable e){

    }
}

Angenommen, Ihre Klasse WhatEverException ist darauf ausgerichtet. Wenn nicht, dann ist dies eine gute Praxis 

0
Akash Mishra