programing

JAX-RS 2.0 클라이언트 라이브러리에서 사용자 지정 오류 응답 처리

coolbiz 2023. 3. 15. 23:20
반응형

JAX-RS 2.0 클라이언트 라이브러리에서 사용자 지정 오류 응답 처리

JAX-RS에서 새로운 클라이언트 API 라이브러리를 사용하기 시작했는데, 지금까지 매우 마음에 듭니다.하지만 나는 내가 알 수 없는 한 가지를 발견했다.사용하고 있는 API에는 다음과 같은 커스텀에러 메시지 형식이 있습니다.

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

상태 코드로 400이 반환되지만, 무엇을 잘못했는지 알려주는 설명 오류 메시지도 포함되어 있습니다.

그러나 JAX-RS 2.0 클라이언트가 400 상태를 일반적인 것으로 재매핑하고 있어 정상적인 오류 메시지가 표시되지 않습니다.BadRequest에 올바르게 매핑됩니다.예외입니다만, 일반적인 「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)

실제 오류 메시지에 액세스할 수 있도록 삽입할 수 있는 인터셉터나 커스텀 오류 핸들러가 있습니까?서류를 살펴봤는데 방법이 안 보여요.

저는 지금 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);

갱신:

아래의 코멘트에 기재되어 있는 솔루션을 실장했습니다.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;
            }
        }
    }
}

난 네가 이런 걸 하고 싶어한다고 생각해.

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

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

일반적으로 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() );
        }

        ...

위의 필터에서는 각 코드에 대해 특정 예외를 만들거나 응답 코드와 엔티티를 감싸는 하나의 일반 예외 유형을 만들 수 있습니다.

커스텀 필터를 작성하는 것 외에 커스텀에러 메시지를 저지 클라이언트에 송신하는 방법이 있습니다.(필터가 뛰어난 솔루션이지만)

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

클라이언트는 예외(내 경우 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) 다른 해결책은 예외를 포착하여 응답 내용을 읽는 것입니다.

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

Web Application 클래스예외는 이를 위해 설계되었지만 어떤 이유로 사용자가 메시지의 매개 변수로 지정한 것을 무시하고 덮어씁니다.

그래서 저는 제 자신의 확장자를 만들었습니다.WebAppException파라미터를 존중합니다.이것은 단일 클래스이며 응답 필터나 매퍼가 필요하지 않습니다.

예외를 만드는 것보다Response가공 중에 어디서든 던질 수 있기 때문입니다.

간단한 사용법:

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

클래스:

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

}

이 문제에 걸려든 모든 사람에게 훨씬 더 간결한 해결책:

부르기.get(Class<T> responseType)또는 결과 유형을 인수로 사용하는 다른 메서드 중 하나Invocation.Builder가 아닌 원하는 유형의 값을 반환합니다.Response부작용으로 수신된 상태 코드가 2xx 범위에 있는지 확인하고 적절한 코드를 던집니다.WebApplicationException그렇지않으면.

매뉴얼에서 다음 항목을 참조하십시오.

슬로우: Web Application서버에서 반환된 응답의 응답 상태 코드가 성공하지 못하고 지정된 응답 유형이 응답이 아닌 경우 예외입니다.

이렇게 하면,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;
        }
    }
}

ApiExceptionInfo는 어플리케이션의 커스텀 데이터 타입입니다.

import lombok.Data;

@Data
public class ApiExceptionInfo {

    private int code;

    private String message;

}

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

}

[적어도 Resteasy] @Chuck M이 제공하는 솔루션에는 큰 단점이 하나 있습니다.ClientResponseFilter.

ClientResponseFilter를 기반으로 사용하면BadRequestException,NotAuthorizedException, ...예외는 다음과 같이 정리됩니다.javax.ws.rs.ProcessingException.

프록시의 클라이언트는 강제로 이를 수신해서는 안 됩니다.javax.ws.rs.ResponseProcessingException예외.

필터가 없으면 원래 휴식 예외가 발생합니다.디폴트로 캐치 및 처리를 실시하면, 다음과 같은 메리트를 얻을 수 있는 것은 없습니다.

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

오류에서 설명을 추출하면 다른 수준에서 문제를 해결할 수 있습니다. WebApplicationExceptionexception은 모든 예외의 부모이며 javax.ws.rs.core가 포함되어 있습니다.대답.예외인 경우 도우미 메서드를 작성하기만 하면 됩니다.WebApplicationException유형, 응답 본문도 확인합니다.여기 스칼라 코드가 있습니다만, 아이디어는 분명합니다.나머지 예외에 대한 명확한 설명을 반환합니다.

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

이제 클라이언트에 정확한 오류를 보여줄 책임이 있습니다.클라이언트에 대한 노력을 최소화하려면 공유 예외 핸들러를 사용하십시오.

아래가 좋습니다.

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

언급URL : https://stackoverflow.com/questions/22561527/handling-custom-error-response-in-jax-rs-2-0-client-library

반응형