JAX-RS / Jersey 오류 처리를 사용자 정의하는 방법은 무엇입니까?


216

Jersey를 사용하여 JAX-RS (일명 JSR-311)를 배우고 있습니다. 루트 리소스를 성공적으로 만들었으며 매개 변수를 가지고 놀고 있습니다.

@Path("/hello")
public class HelloWorldResource {

    @GET
    @Produces("text/html")
    public String get(
        @QueryParam("name") String name,
        @QueryParam("birthDate") Date birthDate) {

         // Return a greeting with the name and age
    }
}

이것은 잘 작동하며 Date (String) 생성자가 이해하는 현재 로케일의 모든 형식을 처리합니다 (예 : YYYY / mm / dd 및 mm / dd / YYYY). 그러나 유효하지 않거나 이해할 수없는 값을 제공하면 404 응답이 발생합니다.

예를 들면 다음과 같습니다.

GET /hello?name=Mark&birthDate=X

404 Not Found

이 동작을 어떻게 사용자 정의 할 수 있습니까? 다른 응답 코드 (아마도 "400 Bad Request")가 있습니까? 오류를 기록하는 것은 어떻습니까? 문제 해결을 위해 사용자 정의 헤더에 문제에 대한 설명 ( "잘못된 날짜 형식")을 추가 하시겠습니까? 또는 5xx 상태 코드와 함께 세부 정보가 포함 된 전체 오류 응답을 반환합니까?

답변:


271

JAX-RS를 사용하여 오류 처리 동작을 사용자 정의하는 방법에는 여러 가지가 있습니다. 다음은 쉬운 세 가지 방법입니다.

첫 번째 방법은 WebApplicationException을 확장하는 Exception 클래스를 만드는 것입니다.

예:

public class NotAuthorizedException extends WebApplicationException {
     public NotAuthorizedException(String message) {
         super(Response.status(Response.Status.UNAUTHORIZED)
             .entity(message).type(MediaType.TEXT_PLAIN).build());
     }
}

그리고이 새로 생성 된 예외를 throw하려면 다음을 수행하십시오.

@Path("accounts/{accountId}/")
    public Item getItem(@PathParam("accountId") String accountId) {
       // An unauthorized user tries to enter
       throw new NotAuthorizedException("You Don't Have Permission");
}

WebApplicationException은 런타임 예외이므로 throws 절에서 예외를 선언 할 필요는 없습니다. 클라이언트에게 401 응답을 반환합니다.

두 번째로 쉬운 방법은 WebApplicationException코드 에서 직접 인스턴스를 만드는 것 입니다. 이 방법은 자체 응용 프로그램 예외를 구현할 필요가없는 한 작동합니다.

예:

@Path("accounts/{accountId}/")
public Item getItem(@PathParam("accountId") String accountId) {
   // An unauthorized user tries to enter
   throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}

이 코드도 클라이언트에 401을 반환합니다.

물론 이것은 단순한 예일뿐입니다. 필요한 경우 예외를 훨씬 더 복잡하게 만들 수 있으며 필요한 HTTP 응답 코드를 생성 할 수 있습니다.

또 다른 방법은 아마도, 기존의 예외를 래핑하는 것입니다 ObjectNotFoundException작은 래퍼 클래스와 그 구현ExceptionMapper@Provider 입니다. 아노 테이션으로 주석이 달린 인터페이스 . 이는 JAX-RS 런타임에 랩핑 된 예외가 발생하면에 정의 된 응답 코드를 리턴하도록 알려줍니다 ExceptionMapper.


3
예제에서 super () 호출은 약간 달라야합니다. super (Response.status (Status.UNAUTHORIZED). entity (message) .type ( "text / plain"). build ()); 통찰력에 감사드립니다.
Jon Onstott

65
질문에서 언급 한 시나리오에서 Jersey는 입력 값에서 Date 객체의 인스턴스를 만들 수 없으므로 예외가 발생하므로 예외를 throw 할 기회가 없습니다. Jersey 예외를 가로채는 방법이 있습니까? 그러나 하나의 ExceptionMapper 인터페이스가 있지만 메소드에서 발생한 예외를 가로 채기도합니다 (이 경우에 해당).
Rejeev Divakaran 2016 년

7
404가 유효한 경우이고 오류가 아닌 경우 서버 로그에 예외가 나타나는 것을 피하는 방법 로그).
귀도

3
Jersey 2.x는 가장 일반적인 HTTP 오류 코드 중 일부에 대한 예외를 정의합니다. 따라서 고유 한 WebApplication 서브 클래스를 정의하는 대신 BadRequestException 및 NotAuthorizedException과 같은 내장 서브 클래스를 사용할 수 있습니다. 예를 들어 javax.ws.rs.ClientErrorException의 서브 클래스를보십시오. 또한 생성자에게 세부 사항 문자열을 제공 할 수 있습니다. 예를 들면 다음과 같습니다. throw new BadRequestException ( "시작 날짜는 종료 날짜보다 먼저" ");
Bampfer

1
또 다른 접근 방식을 언급하는 것을 잊었습니다. ExceptionMapper인터페이스 구현 (이는 더 나은 접근 방법입니다. 자세한 내용은 여기를 참조하십시오 vvirlan.wordpress.com/2015/10/19/…
ACV

70
@Provider
public class BadURIExceptionMapper implements ExceptionMapper<NotFoundException> {

public Response toResponse(NotFoundException exception){

    return Response.status(Response.Status.NOT_FOUND).
    entity(new ErrorResponse(exception.getClass().toString(),
                exception.getMessage()) ).
    build();
}
}

위의 클래스를 만듭니다. 이것은 404 (NotFoundException)를 처리하고 여기 toResponse 메소드에서 사용자 정의 응답을 제공 할 수 있습니다. 마찬가지로 사용자 정의 응답을 제공하기 위해 매핑해야하는 ParamException 등이 있습니다.


일반적인 예외에 대해서도 ExceptionMapper <Exception> 구현을 사용할 수 있습니다.
Saurabh

1
이것은 JAX-RS 클라이언트에 의해 발생 된 WebApplicationExceptions도 처리하여 오류 원점을 숨 깁니다. 더 나은 사용자 정의 예외 (WebApplicationException에서 파생되지 않음)가 있거나 완전한 응답으로 WebApplications를 던집니다. JAX-RS 클라이언트에서 발생한 WebApplicationException은 호출시 직접 처리해야합니다. 그렇지 않으면 처리되지 않은 내부 서버 오류이지만 다른 서비스의 응답이 서비스의 응답으로 전달됩니다.
Markus Kull

38

Jersey는 매개 변수를 비 정렬 화하지 못하면 com.sun.jersey.api.ParamException을 발생시켜 한 가지 해결책은 다음 유형의 예외를 처리하는 ExceptionMapper를 작성하는 것입니다.

@Provider
public class ParamExceptionMapper implements ExceptionMapper<ParamException> {
    @Override
    public Response toResponse(ParamException exception) {
        return Response.status(Status.BAD_REQUEST).entity(exception.getParameterName() + " incorrect type").build();
    }
}

Jersey가 등록 할 수 있도록이 맵퍼를 어디에서 만들어야합니까?
Patricio

1
: 당신이 할 일은, 더 자세한 내용은 여기를 참조 @provider 주석을 추가하는 것입니다 stackoverflow.com/questions/15185299/...
월 Kronquist

27

QueryParam 어노테이션이있는 변수에 재사용 가능한 클래스를 작성할 수도 있습니다.

public class DateParam {
  private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

  private Calendar date;

  public DateParam(String in) throws WebApplicationException {
    try {
      date = Calendar.getInstance();
      date.setTime(format.parse(in));
    }
    catch (ParseException exception) {
      throw new WebApplicationException(400);
    }
  }
  public Calendar getDate() {
    return date;
  }
  public String format() {
    return format.format(value.getTime());
  }
}

다음과 같이 사용하십시오 :

private @QueryParam("from") DateParam startDateParam;
private @QueryParam("to") DateParam endDateParam;
// ...
startDateParam.getDate();

이 경우 오류 처리는 사소한 것이지만 (400 응답 발생)이 클래스를 사용하면 일반적으로 로깅 등을 포함 할 수있는 매개 변수 처리를 제외 할 수 있습니다.


Jersey (CXF에서 마이그레이션)에 사용자 지정 쿼리 매개 변수 처리기를 추가하려고하는데 이것은 내가하는 일과 현저하게 비슷하지만 새 공급자를 설치 / 만드는 방법을 모르겠습니다. 위의 수업은 이것을 보여주지 않습니다. QueryParam에 JodaTime DateTime 객체를 사용하고 있으며이를 해독 할 공급자가 없습니다. 서브 클래 싱하는 것만 큼 쉬운가? String 생성자를주고 처리 하는가?
Christian Bongiorno

1
DateParam위 의 클래스와 같이 org.joda.time.DateTime대신 래핑 하는 클래스를 만드십시오 java.util.Calendar. 당신은 그것을 그 자체 @QueryParam대신에 사용 DateTime합니다.
Charlie Brooking

1
Joda DateTime을 사용하는 경우 저지에는 DateTimeParam이 제공되어 직접 사용할 수 있습니다. 직접 작성할 필요가 없습니다. github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/…를
Srikanth

나는 그것이 매우 유용하기 때문에 이것을 추가 할 것이지만, Jersey와 함께 Jackson을 사용하는 경우에만 가능합니다. Jackson 2.x에는 메소드에 JodaModule등록 할 수 있는 것이 있습니다 ObjectMapper registerModules. 모든 joda 유형 변환을 처리 할 수 ​​있습니다. com.fasterxml.jackson.datatype.joda.JodaModule
j_walker_dev

11

하나의 확실한 해결책 : String을 가져 와서 Date로 직접 변환하십시오. 이렇게하면 원하는 형식을 정의하고 예외를 포착하며 전송되는 오류를 다시 발생 시키거나 사용자 지정할 수 있습니다. 구문 분석의 경우 SimpleDateFormat이 제대로 작동합니다.

데이터 유형에 대한 핸들러도 후크하는 방법이 있다고 확신하지만이 경우에는 약간의 간단한 코드 만 있으면됩니다.


7

나도 좋아 StaxMan을 한다 이 QueryParam을 String으로 구현 한 다음 변환을 처리하고 필요에 따라 다시 던지기를 합니다.

로캘 별 동작이 원하는 동작 일 경우 다음을 사용하여 400 BAD REQUEST 오류를 반환합니다.

throw new WebApplicationException(Response.Status.BAD_REQUEST);

자세한 옵션 은 javax.ws.rs.core.Response.Status에 대한 JavaDoc을 참조하십시오 .


4

@QueryParam 설명서에 따르면

"주석이 달린 매개 변수, 필드 또는 특성의 유형 T는 다음 중 하나 여야합니다.

1) 기본 유형이어야합니다.
2) 단일 문자열 인수를 허용하는 생성자를
갖습니다. 3) 단일 문자열 인수 를 허용하는 valueOf 또는 fromString이라는 정적 메소드를 갖습니다 (예 : Integer.valueOf (String)).
4) javax.ws.rs.ext.ParamConverterProvider의 등록 구현 JAX-RS 확장 SPI는 유형에 대해 "문자열에서"변환이 가능한 javax.ws.rs.ext.ParamConverter 인스턴스를 리턴합니다.
5) T는 위의 2, 3 또는 4를 만족하는 목록, 집합 또는 정렬 집합이어야합니다. 결과 모음은 읽기 전용입니다. "

문자열 형식의 쿼리 매개 변수를 유형 T로 변환 할 수 없을 때 사용자에게 전달되는 응답을 제어하려면 WebApplicationException을 발생시킬 수 있습니다. Dropwizard에는 다음과 같은 * Param 클래스가 포함되어 있습니다.

BooleanParam, DateTimeParam, IntParam, LongParam, LocalDateParam, NonEmptyStringParam, UUIDParam. 보다 https://github.com/dropwizard/dropwizard/tree/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/params를

Joda DateTime이 필요한 경우 Dropwizard DateTimeParam을 사용하십시오. .

위의 목록이 필요에 맞지 않으면 AbstractParam을 확장하여 자신을 정의하십시오. 구문 분석 방법을 재정의하십시오. 오류 응답 본문을 제어해야하는 경우 오류 방법을 대체하십시오.

Coda Hale의 좋은 기사는 http://codahale.com/what-makes-jersey-interesting-parameter-classes/ 에 있습니다 .

import io.dropwizard.jersey.params.AbstractParam;

import java.util.Date;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

public class DateParam extends AbstractParam<Date> {

    public DateParam(String input) {
        super(input);
    }

    @Override
    protected Date parse(String input) throws Exception {
        return new Date(input);
    }

    @Override
    protected Response error(String input, Exception e) {
        // customize response body if you like here by specifying entity
        return Response.status(Status.BAD_REQUEST).build();
    }
}

Date (String arg) 생성자는 더 이상 사용되지 않습니다. Java 8을 사용하는 경우 Java 8 날짜 클래스를 사용합니다. 그렇지 않으면 joda 날짜 시간이 권장됩니다.


1

이것은 실제로 올바른 동작입니다. Jersey는 입력에 대한 핸들러를 찾고 제공된 입력에서 오브젝트를 구성하려고 시도합니다. 이 경우 생성자에 제공된 X 값을 사용하여 새 Date 객체를 만들려고합니다. 날짜가 잘못되었으므로 Jersey에서는 규칙에 따라 404를 반환합니다.

당신이 할 수있는 일은 다시 작성하고 생년월일을 문자열로 넣은 다음 구문 분석을 시도하고 원하는 것을 얻지 못하면 예외 매핑 메커니즘 중 원하는 예외를 자유롭게 던질 수 있습니다 (여러 가지가 있습니다) ).


1

나는 같은 문제에 직면했다.

중앙 위치에서 모든 오류를 잡아서 바꾸고 싶었습니다.

다음은 처리 방법에 대한 코드입니다.

구현 ExceptionMapper하고 추가 하는 다음 클래스를 작성하십시오.@Provider이 클래스 주석 . 모든 예외를 처리합니다.

toResponse메소드를 대체 하고 사용자 정의 된 데이터로 채워진 Response 오브젝트를 리턴하십시오.

//ExceptionMapperProvider.java
/**
 * exception thrown by restful endpoints will be caught and transformed here
 * so that client gets a proper error message
 */
@Provider
public class ExceptionMapperProvider implements ExceptionMapper<Throwable> {
    private final ErrorTransformer errorTransformer = new ErrorTransformer();

    public ExceptionMapperProvider() {

    }

    @Override
    public Response toResponse(Throwable throwable) {
        //transforming the error using the custom logic of ErrorTransformer 
        final ServiceError errorResponse = errorTransformer.getErrorResponse(throwable);
        final ResponseBuilder responseBuilder = Response.status(errorResponse.getStatus());

        if (errorResponse.getBody().isPresent()) {
            responseBuilder.type(MediaType.APPLICATION_JSON_TYPE);
            responseBuilder.entity(errorResponse.getBody().get());
        }

        for (Map.Entry<String, String> header : errorResponse.getHeaders().entrySet()) {
            responseBuilder.header(header.getKey(), header.getValue());
        }

        return responseBuilder.build();
    }
}

// ErrorTransformer.java
/**
 * Error transformation logic
 */
public class ErrorTransformer {
    public ServiceError getErrorResponse(Throwable throwable) {
        ServiceError serviceError = new ServiceError();
        //add you logic here
        serviceError.setStatus(getStatus(throwable));
        serviceError.setBody(getBody(throwable));
        serviceError.setHeaders(getHeaders(throwable));

    }
    private String getStatus(Throwable throwable) {
        //your logic
    }
    private Optional<String> getBody(Throwable throwable) {
        //your logic
    }
    private Map<String, String> getHeaders(Throwable throwable) {
        //your logic
    }
}

//ServiceError.java
/**
 * error data holder
 */
public class ServiceError {
    private int status;
    private Map<String, String> headers;
    private Optional<String> body;
    //setters and getters
}

1

접근 방식 1 : WebApplicationException 클래스를 확장하여

WebApplicationException을 확장하여 새 예외를 작성하십시오.

public class RestException extends WebApplicationException {

         private static final long serialVersionUID = 1L;

         public RestException(String message, Status status) {
         super(Response.status(status).entity(message).type(MediaType.TEXT_PLAIN).build());
         }
}

이제 필요할 때마다 'RestException'을 던지십시오.

public static Employee getEmployee(int id) {

         Employee emp = employees.get(id);

         if (emp == null) {
                 throw new RestException("Employee with id " + id + " not exist", Status.NOT_FOUND);
         }
         return emp;
}

링크 에서 전체 응용 프로그램을 볼 수 있습니다 .

접근법 2 : ExceptionMapper 구현

다음 매퍼는 'DataNotFoundException'유형의 예외를 처리합니다.

@Provider
public class DataNotFoundExceptionMapper implements
        ExceptionMapper<DataNotFoundException> {

    @Override
    public Response toResponse(DataNotFoundException ex) {
        ErrorMessage model = new ErrorMessage(ex.getErrorCode(),
                ex.getMessage());
        return Response.status(Status.NOT_FOUND).entity(model).build();
    }

}

링크 에서 전체 응용 프로그램을 볼 수 있습니다 .


0

브라우저 로그인 창을 열고 싶을 때 @Steven Lavine의 확장 기능과 같습니다. 사용자가 아직 인증되지 않은 경우 필터에서 응답 ( MDN HTTP 인증 ) 을 올바르게 반환하기가 어렵다는 것을 알았습니다.

이를 통해 브라우저 로그인을 강제로 수행하는 응답을 작성하는 데 도움이되었습니다. 헤더의 추가 수정 사항에 유의하십시오. 상태 코드를 401로 설정하고 브라우저가 사용자 이름 / 암호 대화 상자를 여는 헤더를 설정합니다.

// The extended Exception class
public class NotLoggedInException extends WebApplicationException {
  public NotLoggedInException(String message) {
    super(Response.status(Response.Status.UNAUTHORIZED)
      .entity(message)
      .type(MediaType.TEXT_PLAIN)
      .header("WWW-Authenticate", "Basic realm=SecuredApp").build()); 
  }
}

// Usage in the Filter
if(headers.get("Authorization") == null) { throw new NotLoggedInException("Not logged in"); }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.