Обработка исключений JAX-RS в Websphere Liberty - PullRequest
0 голосов
/ 21 мая 2018

Мне нужна помощь в понимании того, как Websphere Liberty (18.0.0.1) обрабатывает исключения, сгенерированные в вызове конечной точки JAX-RS.Я использую функцию Liberty jaxrs-2.0, поэтому реализация должна быть предоставлена ​​WLP.

Теперь у моего приложения есть конечная точка POST HTTP, принимающая полезную нагрузку JSON, и я хотел бы предоставить пользовательские сообщения об ошибках для всехвозможные неправильные входные данные клиента.

Вот один случай, который работает так, как я ожидал:

  1. Клиент отправляет application/xml вместо application/json
  2. ЕстьClientErrorException брошенный контейнером
  3. Я могу использовать свой собственный преобразователь исключений (реализующий ExceptionMapper<WebApplicationException> для обработки этого исключения (фактически для обработки всех исключений веб-приложения, с которыми у меня все в порядке)
  4. Таким образом, я могу отформатировать сообщение об ошибке, пометить ошибку с помощью идентификатора, все, что нужно. Это хорошо

И вот случай, который у меня не работает:

  1. Клиент отправляет application/json, но с пустым телом
  2. Основным исключением в этом случае является java.io.EOFException: No content to map to Object due to end of input - да, это выглядит точно
  3. Теперь то, что я не могу понять - вместо переносаэто EOFException в какой-то WebApplicationException(что я мог бы легко обработать), WLP переносит проблему исключений в JaxRsRuntimeException

Пара моментов здесь:

  • Я не хочу создаватьmapper, реализующий ExceptionMapper<JaxRsRuntimeException>, потому что это исключение не является частью спецификации JAX-RS 2.0, и мне пришлось бы обеспечить импорт в JaxRsRuntimeException и связать приложение с некоторой библиотекой, специфичной для Liberty.
  • Возможное решение -чтобы мой маппер реализовал общий ExceptionMapper<RuntimeException> и проверку строки, если он находит исключение имени класса 'JaxRsRuntimeException', а затем обрабатывает его.Но это просто не кажется мне правильным.

Итак, разве дизайн WLP не дает мне исключение WebApplicationEx в этом случае?Какое будет элегантное решение для такого сценария?

Спасибо


РЕДАКТИРОВАТЬ: Добавлены некоторые части исходного кода.

Конечная точка REST и метод ресурса:

@Path("/books")
public class BookEndpoint {

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createBook(Book book, @Context UriInfo uriInfo) {
        bookDao.create(book);
        UriBuilder builder = uriInfo.getAbsolutePathBuilder();
        builder.path(Integer.toString(book.getId()));
        return Response.created(builder.build()).entity(book).build();
    }
}

Объект с аннотациями JAXB:

@XmlRootElement
public class Book {

    private int id;
    private String title;

    // getters, setters
}

Трассировка стека исключений:

com.ibm.ws.jaxrs20.JaxRsRuntimeException: java.io.EOFException: No content to map to Object duto end of input
    at org.apache.cxf.jaxrs.utils.JAXRSUtils.toJaxRsRuntimeException(JAXRSUtils.java:1928)
    at [internal classes]
    at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
    at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:201)
    at [internal classes]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.EOFException: No content to map to Object duto end of input
    at org.codehaus.jackson.map.ObjectMapper._initForReading(ObjectMapper.java:2775)
    at [internal classes]
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBodyReader(JAXRSUtils.java:1413)
    at [internal classes]
    ... 48 more

Ответы [ 2 ]

0 голосов
/ 23 мая 2018

Это ожидаемое поведение, основанное на Разделе 3.3.4 (и 4.5.1) JAX-RS 2.0 Spec .В этих разделах описывается, как обрабатываются исключения из ресурсов и поставщиков JAX-RS - вкратце:

  1. Если исключение составляет WebApplicationException, то оно автоматически сопоставляется с Response.
  2. Если есть зарегистрированный ExceptionMapper, который может обработать выброшенное исключение, то это будет использоваться для генерации ответа.
  3. Непроверенные исключения передаются в контейнер (т. Е. Код реализации JAX-RS Liberty).
  4. Несопоставленные исключения должны обрабатываться через специфичное для контейнера исключение, а затем соответствующим образом распространяться в нижележащий контейнер - в этом случае ServletException необходимо передать в веб-контейнер.

JaxRsRuntimeException используется для выполнения шага 4.

В этом сценарии встроенный поставщик JSON (на основе Jackson 1.X) выбрасывает EOFException.Поскольку для исключения EOFException (или любого из его суперклассов) нет сопоставителей исключений, в конечном итоге оно сопоставляется с ServletException посредством JaxRsRuntimeException.

Для того чтобы приложение могло обрабатывать этот сценарий, есть несколько различных опций:

  1. Вы можете зарегистрировать ExceptionMapper, специфичный для этого типа исключения (EOFExceptionили любой из его суперклассов - т.е. IOException).Вам не нужно регистрировать маппер для JaxRsRuntimeException, поскольку это исключение используется только в Liberty для внутренних целей и не должно отображаться.Если вы видите, что JaxRsRuntimeException передается в ExceptionMapper, то вам следует открыть службу поддержки в IBM, поскольку это, скорее всего, ошибка.

С ExceptionMapper<EOFException> вы можете вернуть определенный ответвсякий раз, когда EOFException выбрасывается из провайдера или ресурса.

Вы можете зарегистрировать свой собственный MessageBodyReader, который будет преобразовывать JSON в объекты (используя Джексона или любой другой код сериализации JSON), но который будет обрабатывать пустые тела сообщений так, как вы хотите - например, преобразовать его в null илииспользуя какой-то экземпляр объекта по умолчанию.Поскольку зарегистрированные пользователем поставщики имеют приоритет перед встроенными поставщиками, этот MBR будет использоваться вместо MBR Liberty на основе Джексона.

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

Зарегистрируйте ContainerRequestFilter провайдера, который прервет работу, когда тело сообщения будет пустым.Вот пример:

@Provider
public class EmptyBodyCheckFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext crc) throws IOException {
        if (crc.getEntityStream().available() < 1) {
            crc.abortWith(Response.status(400).entity("Invalid request - empty message body").build());
        }
    }
}

Я успешно протестировал варианты 1 и 3 с использованием WebSphere Liberty, май 2018 Beta.Я лично не тестировал вариант 2 для этого сценария, но на основе использования пользовательских MBR в прошлом это должно работать.

Следует иметь в виду, что когда Liberty GA имеет функцию jaxrs-2.1, онабудет использовать JSONB в качестве встроенного поставщика для сериализации / десериализации JSON вместо Джексона.Я протестировал ваш сценарий, используя JAX-RS 2.1 (также в бета-версии мая), и вместо EOFException код JSONB выдает NoSuchElementException.Если вы считаете, что можете перейти на JAX-RS 2.1, я бы предложил вариант 2 или 3. Для варианта 1 потребуется создать новый ExceptionMapper для JAX-RS 2.1.

Надеюсь, это поможет,

Энди

0 голосов
/ 22 мая 2018

Не прямой ответ на "почему WLP переносит исключение ..etc" , но, возможно, добавит перехватчик исключений, как вы это сделали, но на "ExceptionMapper<Exception>" и рекурсивно итерирует по "причинам", чтобы проверить, java.io.EOFException является одним из тех ...

...