web-dev-qa-db-fra.com

Traitement des erreurs dans REST API avec JAX-RS

La tâche: Au lieu de recevoir le HTTP 500 Internal Server Error général dans mon stacktrace et le même horrible stacktrace côté client, je veux voir mon message personnalisé avec un autre statuscode (403 par exemple), ce sera beaucoup plus clair pour le développeur, qu'est-ce qui est arrivé Et ajoutez un message à l'utilisateur à propos de l'exception.

Voici quelques classes modifiées de mon application:

PARTIE SERVEUR:

AppException.class - toutes mes exceptions Server Response (avant de redonner au client) je souhaite me transformer en cette exception. Kinda classe d'entité standard

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 - mappe mon exception AppException à l'environnement d'exécution JAX-RS, à la place d'une exception standard, le client reçoit une exception 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 - ma classe de service qui lève une exception AppException

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


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

PARTIE CLIENT:

ErrorHandlingFilter.class - mon récepteur de réponse de la exception AppException. Ici, je veux transformer chaque exception de réponse en une autre exception en fonction du statut. 

@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 - exception dans ce que je veux transformer AppException, s'il est venu avec 500 codes d'état.

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 - morceau de la logique de l'interface utilisateur, où je veux quelque chose à faire avec PermissionException émise par ErrorHandlingFilter.

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

Comment puis-je résoudre mon problème? Je reçois toujours le 500 InternalErrorException. standard 

MISE À JOUR PRESQUE TOUTES LES QUESTIONS UNE FOIS DE PLUS!

13
TARS

Lorsque vous avez un ExceptionMapper, vous n'attrapez pas l'exception vous-même, mais le faites attraper par la structure, lorsque la méthode de ressource est appelée sur une requête HTTP.

4
gsl

La manière appropriée de gérer les erreurs consiste à enregistrer les instances ExceptionMapper qui savent quelle réponse doit être renvoyée en cas d'exception spécifique (ou générique). 

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

S'il vous plaît jeter un oeil à mon autre réponse pour plus de détails: https://stackoverflow.com/a/23858695/2588800

3
Svetlin Zarev

J'ai une approche différente ici. Vous pouvez essayer cela au démarrage de votre serveur Jetty dans la méthode Java principale.

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

Donc, vous pouvez obtenir une sortie comme celle-ci

{"message":"HTTP ERROR 500"}

Vous pouvez référencer de ici

1
Young Emil

Ceci est un exemple Jersey , mais vous pouvez extraire les informations nécessaires à partir de here . Je voudrais seulement lancer une exception et mapper cette exception à toute réponse souhaitée à la fin.

Supposons que vous ayez la méthode de ressource suivante, en générant l'exception:

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

Créez votre classe d'exception:

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

Maintenant, ajoutez votre mappeur d'exception:

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

En fin de compte, vous devez enregistrer votre mappeur d'exceptions afin qu'il puisse être utilisé dans votre application. Voici un pseudo-code:

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

La pratique idéale suggérée ci-dessus est de laisser le cadre capturer l’exception maintenant que vous avez implémenté une ExceptionMapper. nécessité d'avoir une classe Exception implémentant ExceptionMapper qui mappe sur Throwable 

public class UncaughtExcep implements ExceptionMapper<Throwable>{

   @Override 
   public Response toResponse(Throwable e){

    }
}

En supposant que votre classe WhatEverException y réponde. Si ce n’est pas le cas, c’est une bonne pratique à mettre en œuvre. 

0
Akash Mishra