JAX-RS/Джерси, как настроить обработку ошибок?

Я изучаю JAX-RS (ака, JSR-311), используя Джерси. Я успешно создал Root Resource и играю с параметрами:

@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?

Ответ 1

Существует несколько подходов к настройке поведения обработки ошибок с помощью JAX-RS. Вот три из более простых способов.

Первый подход заключается в создании класса Exception, который расширяет исключение WebApplicationException.

Пример:

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

И для этого нового создания Exception вы просто:

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

Обратите внимание: вам не нужно объявлять исключение в предложении throws, потому что исключение WebApplicationException является Exception. Это вернет 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 клиенту.

Конечно, это простой пример. Вы можете сделать Exception намного более сложным, если это необходимо, и вы сможете генерировать код ответа HTTP, который вам нужен.

Другой подход заключается в переносе существующего Exception, возможно ObjectNotFoundException с небольшим классом-оболочкой, который реализует интерфейс ExceptionMapper, аннотированный аннотацией @Provider. Это говорит о времени выполнения JAX-RS, что если обернутое исключение возникает, верните код ответа, определенный в ExceptionMapper.

Ответ 2

@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 и т.д., Которые вам нужно будет отображать для предоставления настраиваемых ответов.

Ответ 3

Джерси бросает 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();
    }
}

Ответ 4

Вы также можете написать класс многократного использования для переменных QuoredParam-annotated

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), использование этого класса позволяет в любом случае обрабатывать параметры без учета ошибок и т.д.

Ответ 5

Одно очевидное решение: взять в String, конвертировать в Date самостоятельно. Таким образом, вы можете определить формат, который вы хотите, перехватить исключения и либо перебросить, либо настроить отправку ошибки. Для синтаксического анализа SimpleDateFormat должен работать нормально.

Я уверен, что есть способы перехватывать обработчики для типов данных тоже, но, возможно, немного простого кода - это все, что вам нужно в этом случае.

Ответ 6

Мне тоже, как StaxMan, вероятно, будет реализовывать этот QueryParam как String, а затем обработать преобразование, переосмыслив при необходимости.

Если поведение конкретной локали является желаемым и ожидаемым поведением, вы должны использовать следующее, чтобы вернуть ошибку 400 BAD REQUEST:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

Подробнее см. в JavaDoc для javax.ws.rs.core.Response.Status.

Ответ 7

Документация @QueryParam гласит

"Тип T аннотированного параметра, поля или свойства должен либо:

1) Будьте примитивным типом
2) Создайте конструктор, который принимает один Строковый аргумент
3) Имейте статический метод с именем valueOf или fromString который принимает единственный аргумент String (см., например, Integer.valueOf(String))
4) Зарегистрировать внедрение javax.ws.rs.ext.ParamConverterProvider Расширение JAX-RS SPI, которое возвращает экземпляр javax.ws.rs.ext.ParamConverter, способный" от string "для типа.
5) Список, набор или SortedSet, где T удовлетворяет 2, 3 или 4 выше. Результирующий коллекция доступна только для чтения."

Если вы хотите контролировать, какой ответ отправляется пользователю, когда параметр запроса в форме String не может быть преобразован в ваш тип T, вы можете бросить WebApplicationException. Dropwizard поставляется со следующими * параметрами, которые вы можете использовать для своих нужд.

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 date, если вы находитесь на Java 8. В противном случае рекомендуется дата даты joda.

Ответ 8

На самом деле это правильное поведение. Джерси попытается найти обработчик для вашего ввода и попытается построить объект из предоставленного ввода. В этом случае он попытается создать новый объект Date со значением X, предоставленным конструктору. Поскольку это недопустимая дата, по соглашению Джерси вернет 404.

Что вы можете сделать, это переписать и поставить дату рождения в виде строки, а затем попытаться разобрать, и если вы не получите то, что хотите, вы можете бросить любое исключение, которое вы хотите, любым из механизмов отображения исключений ( их несколько).

Ответ 9

Так же, как расширение ответа @Steven Lavine, если вы хотите открыть окно входа в браузер. Мне было трудно правильно вернуть Response (MDN HTTP Authentication) из фильтра, если пользователь еще не прошел аутентификацию.

Это помогло мне создать Response для принудительного входа в браузер, обратите внимание на дополнительную модификацию заголовков. Это установит код состояния на 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"); }

Ответ 10

Я столкнулся с той же проблемой.

Я хотел поймать все ошибки в одном месте и преобразовать их.

Ниже приведен код, с которым я справился.

Создайте следующий класс, который реализует 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
}

Ответ 11

abtrack class Responce
{
private String message ;
private int code ;

public String getMessage(){
 return this.message ;
}
public void setMessage(String message){
this.message =message ;
}


public String getCode(){
 return this.code ;
}
public void setCode(String code){
this.code =code ;
}

}

@XmlRootElement(name='MyResponce')
class MyResponce extends Responce {

}

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

    @GET
    @Produces("text/html")
    public MyResponce  get(
      MyResponce  myResponce = new MyResponce ();
        @QueryParam("name") String name,
        @QueryParam("birthDate") Date birthDate) throw WSException {
          try {
}catch(Exception)
myResponce.setCode(400);
myResponce.setMessage("Exception")
   }
return myResponce ;

}