JAX-RS / Джерси, как настроить обработку ошибок? - PullRequest
208 голосов
/ 25 февраля 2009

Я изучаю 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?

Ответы [ 9 ]

265 голосов
/ 01 марта 2009

Существует несколько подходов для настройки поведения обработки ошибок с помощью 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());
     }
}

И чтобы бросить это новое исключение, вы просто:

@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 - это исключение времени выполнения. Это вернет клиенту ответ 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, что, если возбужденное Exception вызвано, вернуть код ответа, определенный в ExceptionMapper.

66 голосов
/ 11 мая 2011
@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 и т. Д., Которые вам необходимо отобразить для предоставления настраиваемых ответов.

34 голосов
/ 28 ноября 2011

Джерси генерирует исключение 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();
    }
}
26 голосов
/ 26 августа 2009

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

11 голосов
/ 28 февраля 2009

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

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

7 голосов
/ 26 марта 2009

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

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

throw new WebApplicationException(Response.Status.BAD_REQUEST);

См. JavaDoc для javax.ws.rs.core.Response.Status для получения дополнительных параметров.

4 голосов
/ 29 апреля 2015

@ В документации QueryParam написано

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

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

Если вы хотите контролировать, какой ответ отправляется пользователю, когда параметр запроса в строковой форме не может быть преобразован в тип 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, если вы используете Java 8. В противном случае рекомендуется использовать дату и время joda.

1 голос
/ 20 октября 2015

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

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

0 голосов
/ 25 апреля 2019

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

Это помогло мне построить 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"); }
...