【问题标题】:Error handling in REST API with JAX-RS使用 JAX-RS 处理 REST API 中的错误
【发布时间】:2016-12-22 15:13:34
【问题描述】:

任务:我不想在我的堆栈跟踪中接收一般的HTTP 500 Internal Server Error 和在客户端同样可怕的堆栈跟踪,我希望看到我的自定义消息和另一个状态码(例如403),开发人员会更清楚发生了什么。并向用户添加一些关于异常的消息。

以下是我的应用程序中更改的几个类:

服务器部分:

AppException.class - 我所有的服务器响应异常(在返回给客户端之前)我想转换成这个异常。有点标准的实体类

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 - 将我的 AppException 映射到 JAX-RS 运行时,而不是标准异常,客户端接收 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- 我的服务类抛出 AppException

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


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

客户部分:

ErrorHandlingFilter.class- 我的 AppException 响应捕获器。在这里,我想根据状态将每个 Response 异常转换为另一个异常。

@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 - 我想要转换 AppException 的异常,如果它带有 500 状态代码。

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- 一段 UI 逻辑,我想要处理 ErrorHandlingFilter 抛出的 PermissionException。

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

如何解决我的问题?我仍然收到标准500 InternalErrorException.

几乎将整个问题再更新一次!

【问题讨论】:

  • 当您拥有 ExceptionMapper 时,您不会自己捕获异常,而是让框架在 HTTP 请求上调用资源方法时捕获它。 (我不太明白你上一堂课是做什么的;是客户端代码吗?)
  • @gsl 是的,我只是想显示我在哪里捕获了我的异常。我试图做同样的事情,但没有 PermissionExceptionMapper。类,只是只有 PermissionException。它没有工作(
  • 好吧,您不必在自己的代码中捕获它。 (除了一个测试程序,但我不明白这有什么意义。)
  • @gsl 如果您将您的第一条评论作为帖子的答案,我将接受它作为答案。看来我完全错误地理解了 ExctentionMapper 的概念
  • 为什么不处理从 ApplicationService / PermissionExceptionMapper 收到的响应?

标签: java spring rest jersey jax-rs


【解决方案1】:

当您拥有 ExceptionMapper 时,您不会自己捕获异常,而是让框架在 HTTP 请求上调用资源方法时捕获它。

【讨论】:

    【解决方案2】:

    执行错误处理的正确方法是注册ExceptionMapper 实例,这些实例知道在发生特定(或一般)异常时应返回什么响应。

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

    更多详情请看我的其他回答:https://stackoverflow.com/a/23858695/2588800

    【讨论】:

    • 您的回复。 PermissionException我自己创建的客户端。为此我有我的ErrorHandlingFilterPermissionException 的客户端映射器也应该有吗?
    • 所有异常映射器都驻留在服务器端。当服务器上的 REST 端点抛出异常时,它将被异常映射器拦截,该异常映射器将生成适当的响应发送给客户端。
    • 谢谢。我在我的 ÀppExceptionMapper` 中使用了您建议的变体。但我的Mapper不想将propper 响应扔给我的客户((
    • 你能告诉我你的映射器是什么样子的吗?您是否还检查过是否调用了映射器?
    • 当然 - (pastebin.com/mwbrHg99)。他从来没有被叫过,试图理解为什么?。
    【解决方案3】:

    这是Jersey example,但您可以从here 中提取所需信息。最后我只会抛出一个异常并将这个异常映射到任何想要的响应。

    假设你有以下资源方法,抛出异常:

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

    创建你的异常类:

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

    现在添加您的异常映射器:

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

    最后,您必须注册您的异常映射器,以便在您的应用程序中使用它。这是一些伪代码:

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

    【讨论】:

    • 注册是什么意思?以及如何注册?
    • 例如在 Dropwizard 中你可以注册你的异常映射器。
    【解决方案4】:

    我在这里有不同的方法。您可以在主 java 方法中启动码头服务器时尝试此操作

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

    所以你可以得到这样的输出

    {"message":"HTTP ERROR 500"}
    

    您可以参考here

    【讨论】:

      【解决方案5】:

      上面正确建议,理想的做法是让框架为您捕获异常,因为您已经实现了ExceptionMapper。 但是,概述您正在执行的现象的重要一点:如果您需要处理任何未捕获的异常,您需要有一个实现 ExceptionMapperException 类,它映射到 Throwable

      public class UncaughtExcep implements ExceptionMapper<Throwable>{
      
         @Override 
         public Response toResponse(Throwable e){
      
          }
      }
      

      假设您的班级 WhatEverException 满足这一点。如果没有,那么实施是一个好习惯

      【讨论】:

      • 感谢您的回复。但是..我什么都试过了,我试过设置Throwable、Exception、WebApplicationException。没有一个对我有用。关键是我的ExceptionMapper.class从未被调用过。 JAX-RS 运行时只是忽略了我的 ExceptionMapper.class@Provider
      • 对于基于 WebApplicationException 的映射异常,您可以尝试另一种方法移除 ExceptionMapper,而是将异常扩展到 WebApplicationException 并覆盖 getResponse 方法。对于不从 WebApplicationException 扩展的异常,可以使用 ExceptionMapper 的常用方法
      猜你喜欢
      • 1970-01-01
      • 2016-12-18
      • 2015-04-25
      • 1970-01-01
      • 1970-01-01
      • 2013-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多