Обработка исключений JAX-RS в Websphere Liberty

Мне нужна помощь в понимании того, как 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

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

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

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

Спасибо


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

Конечная точка 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

person Adam    schedule 21.05.2018    source источник
comment
Привет @alukes, Можете ли вы предоставить исходный код для вашего метода ресурсов и средства чтения тела сообщения, которое вы используете (при условии, что вы регистрируете свое собственное)? Также было бы полезно увидеть трассировку стека JaxRsRuntimeException. JaxRsRuntimeException предназначен для исключения, специфичного для контейнера, упомянутого в разделе 3.3.4 спецификации JAX-RS ( download.oracle.com/otn-pub/jcp/jaxrs-2_0-fr-eval-spec/ ), но, возможно, здесь он используется неправильно.   -  person Andy McCright    schedule 22.05.2018
comment
Привет, Энди, я отредактировал вопрос с некоторым исходным кодом. Я не использую какой-либо пользовательский считыватель тела сообщения. Я хотел упростить задачу и позволить реализации JAX-RS выполнить за меня преобразование JSON в сущность с использованием аннотаций JAXB. Я также опубликовал трассировку стека, дайте мне знать, если я должен предоставить более подробную информацию. Спасибо   -  person Adam    schedule 23.05.2018


Ответы (2)


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

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

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

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

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

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

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

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

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

  1. Зарегистрируйте поставщика 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 года. Я лично не тестировал вариант 2 для этого сценария, но, основываясь на использовании пользовательских MBR в прошлом, это должно работать.

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

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

Энди

person Andy McCright    schedule 23.05.2018
comment
Спасибо, Энди, я отмечу это как принятое, потому что это дало мне достаточно информации, чтобы решить, как решить проблему. Мне нравятся оба варианта 1 и 2, насчет того, что вариант 2 не будет страдать дополнительными правками кода при обновлении до jaxrs-2.1 пожалуй соглашусь. Кстати, как вы упомянули: если вы видите, что JaxRsRuntimeException передается ExceptionMapper... это, вероятно, ошибка - спасибо за подтверждение, потому что это вызвало у меня недоумение в самом начале. У меня было ощущение, что получать внутреннее исключение Liberty неправильно. - person Adam; 24.05.2018

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

person titou10    schedule 22.05.2018
comment
Привет @ titou10, на самом деле это мое текущее решение - иметь очень общий преобразователь, а затем вручную обрабатывать каждый отдельный тип исключения, переданный в этот преобразователь. Но есть много типов исключений во время выполнения, которые могут произойти, и код сложен. Моя точка зрения заключалась в том, что если я могу определить точный тип исключения, который я не получаю в приложении (EOFException), то мне кажется более чистым создать преобразователь для конкретного типа. И причина, по которой я хотел использовать исключение WebAppException, заключается в том, что оно прекрасно оборачивает все случаи сбоев клиента. - person Adam; 23.05.2018