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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22561527/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 16:30:04  来源:igfitidea点击:

Handling custom error response in JAX-RS 2.0 client library

javajsonjerseyHymansonjax-rs

提问by Chuck M

I am starting to use the new client API library in JAX-RS and really loving it so far. I have found one thing I cannot figure out however. The API I am using has a custom error message format that looks like this for example:

我开始在 JAX-RS 中使用新的客户端 API 库,到目前为止我真的很喜欢它。然而,我发现了一件事我无法弄清楚。我使用的 API 具有自定义错误消息格式,例如如下所示:

{
    "code": 400,
    "message": "This is a message which describes why there was a code 400."
} 

It returns 400 as the status code but also includes a descriptive error message to tell you what you did wrong.

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

However the JAX-RS 2.0 client is re-mapping the 400 status into something generic and I lose the good error message. It correctly maps it to a BadRequestException, but with a generic "HTTP 400 Bad Request" message.

但是,JAX-RS 2.0 客户端正在将 400 状态重新映射为通用状态,而我丢失了正确的错误消息。它正确地将其映射到 BadRequestException,但带有通用的“HTTP 400 错误请求”消息。

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.access0(JerseyInvocation.java:90)
    at org.glassfish.jersey.client.JerseyInvocation.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)

Is there some sort of interceptor or custom error handler that can be injected so that I get access to the real error message. I've been looking through documentation but can't see any way of doing it.

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

I am using Jersey right now, but I tried this using CXF and got the same result. Here is what the code looks like.

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

Client client = ClientBuilder.newClient().register(HymansonFeature.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);

UPDATE:

更新:

I implemented the solution listed in the comment below. It is slightly different since the classes have changed a bit in the JAX-RS 2.0 client API. I still think it is wrong that the default behavior is to give a generic error message and discard the real one. I understand why it wouldn't parse my error object, but the un-parsed version should have been returned. I end up having the replicate exception mapping that the library already does.

我实施了下面评论中列出的解决方案。它略有不同,因为 JAX-RS 2.0 客户端 API 中的类发生了一些变化。我仍然认为默认行为是给出通用错误消息并丢弃真正的错误消息是错误的。我明白为什么它不会解析我的错误对象,但应该返回未解析的版本。我最终拥有库已经完成的复制异常映射。

Thanks for the help.

谢谢您的帮助。

Here is my filter class:

这是我的过滤器类:

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

采纳答案by robert_difalco

I believe you want to do something like this:

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

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

Another thing you can try (since I don't know where this API puts stuff -- i.e. in the header or entity or what) is:

你可以尝试的另一件事(因为我不知道这个 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 );

If you would like to generally map REST response codes to Java exception you can add a client filter to do that:

如果你想将 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() );
        }

        ...

In the above filter you can create specific exceptions for each code or create one generic exception type that wraps the Response code and entity.

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

回答by Pla

The class WebApplicationExceptionwas designed for that but for some reason it ignores and overwrites what you specify as parameter for the message.

WebApplicationException类是为此而设计的,但由于某种原因,它会忽略并覆盖您指定为消息参数的内容。

For that reason I created my own extension WebAppExceptionthat honors the parameters. It is a single class and it doesn't require any response filter or a mapper.

出于这个原因,我创建了自己的扩展WebAppException来尊重参数。它是一个单一的类,不需要任何响应过滤器或映射器。

I prefer exceptions than creating a Responseas it can be thrown from anywhere while processing.

我更喜欢异常而不是创建 aResponse因为它可以在处理时从任何地方抛出。

Simple usage:

简单用法:

throw new WebAppException(Status.BAD_REQUEST, "Field 'name' is missing.");

The class:

班上:

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.core.Response.StatusType;

public class WebAppException extends WebApplicationException {
    private static final long serialVersionUID = -9079411854450419091L;

    public static class MyStatus implements StatusType {
        final int statusCode;
        final String reasonPhrase;

        public MyStatus(int statusCode, String reasonPhrase) {
            this.statusCode = statusCode;
            this.reasonPhrase = reasonPhrase;
        }

        @Override
        public int getStatusCode() {
            return statusCode;
        }
        @Override
        public Family getFamily() {
            return Family.familyOf(statusCode);
        }
        @Override
        public String getReasonPhrase() {
            return reasonPhrase;
        }
    }

    public WebAppException() {
    }

    public WebAppException(int status) {
        super(status);
    }

    public WebAppException(Response response) {
        super(response);
    }

    public WebAppException(Status status) {
        super(status);
    }

    public WebAppException(String message, Response response) {
        super(message, response);
    }

    public WebAppException(int status, String message) {
        super(message, Response.status(new MyStatus(status, message)). build());
    }

    public WebAppException(Status status, String message) {
        this(status.getStatusCode(), message);
    }

    public WebAppException(String message) {
        this(500, message);
    }

}

回答by Shivoam

The following works for me

以下对我有用

Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();

回答by toKrause

A much more concise solution for anyone stumbling on this:

对于任何遇到此问题的人来说,这是一个更简洁的解决方案:

Calling .get(Class<T> responseType)or any of the other methods that take the result type as an argument Invocation.Builderwill return a value of the desired type instead of a Response. As a side effect, these methods will check if the received status code is in the 2xx range and throw an appropriate WebApplicationExceptionotherwise.

调用.get(Class<T> responseType)或将结果类型作为参数的任何其他方法Invocation.Builder将返回所需类型的值而不是Response. 作为副作用,这些方法将检查接收到的状态代码是否在 2xx 范围内,WebApplicationException否则会抛出适当的。

From the documentation:

文档

Throws: WebApplicationException in case the response status code of the response returned by the server is not successful and the specified response type is not Response.

抛出:WebApplicationException 如果服务器返回的响应的响应状态码不成功且指定的响应类型不是 Response。

This allows to catch the WebApplicationException, retrieve the actual Response, process the contained entity as exception details (ApiExceptionInfo) and throw an appropriate exception (ApiException).

这允许捕获WebApplicationException、检索实际Response、将包含的实体作为异常详细信息 ( ApiExceptionInfo) 进行处理并抛出适当的异常 ( ApiException)。

public <Result> Result get(String path, Class<Result> resultType) {
    return perform("GET", path, null, resultType);
}

public <Result> Result post(String path, Object content, Class<Result> resultType) {
    return perform("POST", path, content, resultType);
}

private <Result> Result perform(String method, String path, Object content, Class<Result> resultType) {
    try {
        Entity<Object> entity = null == content ? null : Entity.entity(content, MediaType.APPLICATION_JSON);
        return client.target(uri).path(path).request(MediaType.APPLICATION_JSON).method(method, entity, resultType);
    } catch (WebApplicationException webApplicationException) {
        Response response = webApplicationException.getResponse();
        if (response.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE)) {
            throw new ApiException(response.readEntity(ApiExceptionInfo.class), webApplicationException);
        } else {
            throw webApplicationException;
        }
    }
}

ApiExceptionInfois custom data type in my application:

ApiExceptionInfo是我的应用程序中的自定义数据类型:

import lombok.Data;

@Data
public class ApiExceptionInfo {

    private int code;

    private String message;

}

ApiExceptionis custom exception type in my application:

ApiException是我的应用程序中的自定义异常类型:

import lombok.Getter;

public class ApiException extends RuntimeException {

    @Getter
    private final ApiExceptionInfo info;

    public ApiException(ApiExceptionInfo info, Exception cause) {
        super(info.toString(), cause);
        this.info = info;
    }

}

回答by Peter Tarlos

There are other ways to getting a custom error message to the Jersey client besides writing a custom filter. (although the filter is an excellent solution)

除了编写自定义过滤器之外,还有其他方法可以向 Jersey 客户端获取自定义错误消息。(虽然过滤器是一个很好的解决方案)

1) Pass error message in an HTTP header field.The detail error message could be in the JSON response and in an additional header field, such as "x-error-message".

1) 在 HTTP 标头字段中传递错误消息。详细错误消息可能位于 JSON 响应和附加标头字段中,例如“x-error-message”。

The Serveradds the HTTP error header.

服务器添加HTTP错误头。

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

The Clientcatches the exception, NotFoundException in my case, and reads the response header.

客户端捕获该异常,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;
}

2) Another solution is to catch the exception and read the response content.

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

回答by Alexandr

[At least with Resteasy] there is one big disadvantage with the solution offered by @Chuck M and based on ClientResponseFilter.

[至少对于 Resteasy]@Chuck M 提供的基于ClientResponseFilter.

When you use it based on ClientResponseFilter, your BadRequestException, NotAuthorizedException, ... exceptions are wrapped by javax.ws.rs.ProcessingException.

当您基于 ClientResponseFilter 使用它时,您的BadRequestException, NotAuthorizedException, ... 异常由javax.ws.rs.ProcessingException.

Clients of your proxy must not be forced to catch this javax.ws.rs.ResponseProcessingExceptionexception.

不得强迫代理的客户端捕获此javax.ws.rs.ResponseProcessingException异常。

Without filter, we get an original rest exception. If we catch and handle by default, it does not give us much:

没有过滤器,我们得到一个原始的休息异常。如果我们默认捕获并处理,它不会给我们太多:

catch (WebApplicationException e) {
 //does not return response body:
 e.toString();
 // returns null:
 e.getCause();
}

The problem can be solved on another level, when you extract a description from the error.WebApplicationExceptionexception, which is a parent for all rest exceptions, contains javax.ws.rs.core.Response. Just write a helper method, that in case the exception is of WebApplicationExceptiontype, it will also check the response body. Here is a code in Scala, but the idea should be clear. The methord returns a clear description of the rest exception:

当您从错误中提取描述时,可以在另一个级别上解决该问题WebApplicationException异常是所有其余异常的父级,包含 javax.ws.rs.core.Response。只需编写一个辅助方法,以防异常WebApplicationException类型,它还将检查响应正文。这是Scala中的代码,但想法应该很清楚。该方法返回对其余异常的清晰描述:

  private def descriptiveWebException2String(t: WebApplicationException): String = {
    if (t.getResponse.hasEntity)
      s"${t.toString}. Response: ${t.getResponse.readEntity(classOf[String])}"
    else t.toString
  }

Now we move a responsibility to show exact error, on the client. Just use a shared exception handler to minimize effort for clients.

现在我们将责任转移到客户端上显示确切的错误。只需使用共享异常处理程序即可最大限度地减少客户端的工作量。