在JAX-RS 2.0客户端库中处理自定义错误响应

Chu*_*k M 36 java json jax-rs jersey jackson

我开始在JAX-RS中使用新的客户端API库,并且到目前为止真的非常喜欢它.我找到了一件我无法弄清楚的事情.我使用的API有一个自定义错误消息格式,例如:

{
    "code": 400,
    "message": "This is a message which describes why there was a code 400."
} 
Run Code Online (Sandbox Code Playgroud)

它返回400作为状态代码,但还包含一条描述性错误消息,告诉您错误.

但是,JAX-RS 2.0客户端将400状态重新映射到通用的状态,我丢失了良好的错误消息.它正确地将它映射到BadRequestException,但具有通用的"HTTP 400 Bad Request"消息.

javax.ws.rs.BadRequestException: HTTP 400 Bad Request
    at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:908)
    at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:770)
    at org.glassfish.jersey.client.JerseyInvocation.access$500(JerseyInvocation.java:90)
    at org.glassfish.jersey.client.JerseyInvocation$2.call(JerseyInvocation.java:671)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:424)
    at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:667)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:396)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:296)
Run Code Online (Sandbox Code Playgroud)

是否有某种拦截器或自定义错误处理程序可以注入,以便我可以访问真正的错误消息.我一直在查看文档,但看不到任何方法.

我现在正在使用Jersey,但是我尝试使用CXF并得到了相同的结果.这是代码的样子.

Client client = ClientBuilder.newClient().register(JacksonFeature.class).register(GzipInterceptor.class);
WebTarget target = client.target("https://somesite.com").path("/api/test");
Invocation.Builder builder = target.request()
                                   .header("some_header", value)
                                   .accept(MediaType.APPLICATION_JSON_TYPE)
                                   .acceptEncoding("gzip");
MyEntity entity = builder.get(MyEntity.class);
Run Code Online (Sandbox Code Playgroud)

更新:

我实现了下面评论中列出的解决方案.它略有不同,因为类在JAX-RS 2.0客户端API中有所改变.我仍然认为默认行为是提供一般错误消息并丢弃真实错误消息是错误的.我理解为什么它不会解析我的错误对象,但应该返回未解析的版本.我最终得到了库已经执行的复制异常映射.

谢谢您的帮助.

这是我的过滤器类:

@Provider
public class ErrorResponseFilter implements ClientResponseFilter {

    private static ObjectMapper _MAPPER = new ObjectMapper();

    @Override
    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
        // for non-200 response, deal with the custom error messages
        if (responseContext.getStatus() != Response.Status.OK.getStatusCode()) {
            if (responseContext.hasEntity()) {
                // get the "real" error message
                ErrorResponse error = _MAPPER.readValue(responseContext.getEntityStream(), ErrorResponse.class);
                String message = error.getMessage();

                Response.Status status = Response.Status.fromStatusCode(responseContext.getStatus());
                WebApplicationException webAppException;
                switch (status) {
                    case BAD_REQUEST:
                        webAppException = new BadRequestException(message);
                        break;
                    case UNAUTHORIZED:
                        webAppException = new NotAuthorizedException(message);
                        break;
                    case FORBIDDEN:
                        webAppException = new ForbiddenException(message);
                        break;
                    case NOT_FOUND:
                        webAppException = new NotFoundException(message);
                        break;
                    case METHOD_NOT_ALLOWED:
                        webAppException = new NotAllowedException(message);
                        break;
                    case NOT_ACCEPTABLE:
                        webAppException = new NotAcceptableException(message);
                        break;
                    case UNSUPPORTED_MEDIA_TYPE:
                        webAppException = new NotSupportedException(message);
                        break;
                    case INTERNAL_SERVER_ERROR:
                        webAppException = new InternalServerErrorException(message);
                        break;
                    case SERVICE_UNAVAILABLE:
                        webAppException = new ServiceUnavailableException(message);
                        break;
                    default:
                        webAppException = new WebApplicationException(message);
                }

                throw webAppException;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

rob*_*lco 22

我相信你想做这样的事情:

Response response = builder.get( Response.class );
if ( response.getStatusCode() != Response.Status.OK.getStatusCode() ) {
    System.out.println( response.getStatusType() );
    return null;
}

return response.readEntity( MyEntity.class );
Run Code Online (Sandbox Code Playgroud)

你可以尝试的另一件事(因为我不知道这个API放在哪里 - 即在标题或实体或什么)是:

Response response = builder.get( Response.class );
if ( response.getStatusCode() != Response.Status.OK.getStatusCode() ) {
    // if they put the custom error stuff in the entity
    System.out.println( response.readEntity( String.class ) );
    return null;
}

return response.readEntity( MyEntity.class );
Run Code Online (Sandbox Code Playgroud)

如果您希望将REST响应代码通常映射到Java异常,则可以添加客户端过滤器来执行此操作:

class ClientResponseLoggingFilter implements ClientResponseFilter {

    @Override
    public void filter(final ClientRequestContext reqCtx,
                       final ClientResponseContext resCtx) throws IOException {

        if ( resCtx.getStatus() == Response.Status.BAD_REQUEST.getStatusCode() ) {
            throw new MyClientException( resCtx.getStatusInfo() );
        }

        ...
Run Code Online (Sandbox Code Playgroud)

在上面的过滤器中,您可以为每个代码创建特定的异常,或者创建一个包装Response代码和实体的通用异常类型.

  • 在我的情况下(使用带有内置 JAX-RS 客户端的 Apache CXF),我不得不使用 `ResponseExceptionMapper` 代替,将 `Response` 映射到适当的(自定义)`Exception` 类型,作为抛出的自定义异常`ClientResponseFilter` 没有传播到调用线程(CXF 的 `PhaseInterceptorChain` 自己捕获并丢弃自定义异常,最终导致相同的旧 JAX-RS 异常(在我的情况下为 `InternalServerErrorException`)被抛出到调用线) (2认同)

Pet*_*los 5

除了编写自定义过滤器外,还有其他方法可以将自定义错误消息发送给Jersey客户端。(尽管过滤器是一个很好的解决方案)

1)在HTTP标头字段中传递错误消息。 详细错误消息可能在JSON响应中以及其他标头字段中,例如“ x-error-message”。

服务器添加HTTP错误头。

ResponseBuilder rb = Response.status(respCode.getCode()).entity(resp);
if (!StringUtils.isEmpty(errMsg)){
    rb.header("x-error-message", errMsg);
}
return rb.build();
Run Code Online (Sandbox Code Playgroud)

客户端捕获该异常,NotFoundException在我的情况,并读取响应头。

try {
    Integer accountId = 2222;
    Client client = ClientBuilder.newClient();
    WebTarget webTarget = client.target("http://localhost:8080/rest-jersey/rest");
    webTarget = webTarget.path("/accounts/"+ accountId);
    Invocation.Builder ib = webTarget.request(MediaType.APPLICATION_JSON);
    Account resp = ib.get(new GenericType<Account>() {
    });
} catch (NotFoundException e) {
    String errorMsg = e.getResponse().getHeaderString("x-error-message");
    // do whatever ...
    return;
}
Run Code Online (Sandbox Code Playgroud)

2)另一个解决方案是捕获异常并读取响应内容。

try {
    // same as above ...
} catch (NotFoundException e) {
    String respString = e.getResponse().readEntity(String.class);
    // you can convert to JSON or search for error message in String ...
    return;
} 
Run Code Online (Sandbox Code Playgroud)