Programing

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

lottogame 2020. 4. 30. 07:24
반응형

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


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 상태 코드와 함께 세부 정보가 포함 된 전체 오류 응답을 반환합니까?


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 응답 코드를 생성 할 수 있습니다.

One other approach is to wrap an existing Exception, perhaps an ObjectNotFoundException with an small wrapper class that implements the ExceptionMapper interface annotated with a @Provider annotation. This tells the JAX-RS runtime, that if the wrapped Exception is raised, return the response code defined in the ExceptionMapper.


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

Create above class. This will handle 404 (NotFoundException) and here in toResponse method you can give your custom response. Similarly there are ParamException etc. which you would need to map to provide customized responses.


Jersey throws an com.sun.jersey.api.ParamException when it fails to unmarshall the parameters so one solution is to create an ExceptionMapper that handles these types of exceptions:

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

You could also write a reusable class for QueryParam-annotated variables

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

then use it like this:

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

Although the error handling is trivial in this case (throwing a 400 response), using this class allows you to factor-out parameter handling in general which might include logging etc.


One obvious solution: take in a String, convert to Date yourself. That way you can define format you want, catch exceptions and either re-throw or customize error being sent. For parsing, SimpleDateFormat should work fine.

I am sure there are ways to hook handlers for data types too, but perhaps little bit of simple code is all you need in this case.


I too like StaxMan would probably implement that QueryParam as a String, then handle the conversion, rethrowing as necessary.

If the locale specific behavior is the desired and expected behavior, you would use the following to return the 400 BAD REQUEST error:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

See the JavaDoc for javax.ws.rs.core.Response.Status for more options.


@QueryParam documentation says

" The type T of the annotated parameter, field or property must either:

1) Be a primitive type
2) Have a constructor that accepts a single String argument
3) Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String))
4) Have a registered implementation of javax.ws.rs.ext.ParamConverterProvider JAX-RS extension SPI that returns a javax.ws.rs.ext.ParamConverter instance capable of a "from string" conversion for the type.
5) Be List, Set or SortedSet, where T satisfies 2, 3 or 4 above. The resulting collection is read-only. "

If you want to control what response goes to user when query parameter in String form can't be converted to your type T, you can throw WebApplicationException. Dropwizard comes with following *Param classes you can use for your needs.

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

If you need Joda DateTime, just use Dropwizard DateTimeParam.

If the above list does not suit your needs, define your own by extending AbstractParam. Override parse method. If you need control over error response body, override error method.

Good article from Coda Hale on this is at 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) constructor is deprecated. I would use Java 8 date classes if you are on Java 8. Otherwise joda date time is recommended.


This is the correct behavior actually. Jersey will try to find a handler for your input and will try to construct an object from the provided input. In this case it will try to create a new Date object with the value X provided to the constructor. Since this is an invalid date, by convention Jersey will return 404.

What you can do is rewrite and put birth date as a String, then try to parse and if you don't get what you want, you're free to throw any exception you want by any of the exception mapping mechanisms (there are several).


Just as an extension to @Steven Lavine answer in case you want to open the browser login window. I found it hard to properly return the Response (MDN HTTP Authentication) from the Filter in case that the user wasn't authenticated yet

This helped me to build the Response to force browser login, note the additional modification of the headers. This will set the status code to 401 and set the header that causes the browser to open the username/password dialog.

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

참고URL : https://stackoverflow.com/questions/583973/jax-rs-jersey-how-to-customize-error-handling

반응형