Блоги

WSO2 Micro Integrator - вызов REST сервиса с Basic Auth

В недавнем прошлом была относительно небольшая и простая задача - вызов REST сервиса из WSO2 MI 4.0. Этот сервис был прикрыт при помощи Basic Auth. Для подобных вещей в Apache Synapse можно использовать handlers - это вполне удобно, пользователей можно добавить прямо в локальное хранилище. Так и было сделано:

<?xml version="1.0" encoding="UTF-8"?>

<api context="/rest/api" name="processApi" xmlns="http://ws.apache.org/ns/synapse">

    <resource methods="POST" protocol="https" uri-template="/process">

        <inSequence>

            <log level="custom" separator="&#xa;">

                <property name="START" value="request POST /rest/api/process"/>

                <property expression="get-property('axis2', 'REMOTE_ADDR')" name="client_ip"/>

                <property expression="$trp:X-Forwarded-For" name="X-Forwarded-For value"/>

            </log>

            <sequence key="process"/>

            <property description="HTTP_SC" name="HTTP_SC" scope="axis2" type="STRING" value="200"/>

            <header action="remove" description="remove Authorization" name="Authorization" scope="transport"/>

            <log level="custom" separator="&#xa;">

                <property name="END" value="request POST /rest/api/process"/>

            </log>

            <respond/>

        </inSequence>

        <outSequence/>

        <faultSequence/>

    </resource>

  <handlers>

           <handler class="org.wso2.micro.integrator.security.handler.RESTBasicAuthHandler"/>

     </handlers>

</api>

Задеплоили, проверяем - работает. 

Но тестировщик не дремлет и проверяет данный сервис по крайним значениям:

отправляет в сервис пустой запрос, запрос > 60КБ…

Всё отработало… идём дальше.

И вот, при проверке Basic аутентификации с payload > 60КБ и не верными учётными данными сервис уходит в долгое раздумье. 

Пришло время дебага!

В логах обнаруживается WARNING по истечении Socket таймаута, кричащий что за 180 секунд сервер не успел считать тело запроса.

WARN {org.apache.synapse.transport.passthru.SourceHandler} - STATE_DESCRIPTION = Socket Timeout occurred after reading the request headers but Server is still reading the request body, INTERNAL_STATE = REQUEST_BODY, DIRECTION = REQUEST, CAUSE_OF_ERROR = Connection between the client and the EI timeouts, HTTP_URL = /api/v1/ccc/method, HTTP_METHOD = POST, SOCKET_TIMEOUT = 180000, CLIENT_ADDRESS = /0:0:0:0:0:0:0:1:58376, CONNECTION http-incoming-188 Correlation ID : b2dfbffa-09f3-4a84-919a-c448027c702d

Выкручивая все логи в дебаг видим, что входящий http поток остановил чтение на позиции  16384 байт… где-то я видел эту цифру...

DEBUG {org.apache.synapse.transport.passthru.SourceHandler} - http-incoming-4: Content decoder [content length: 63371; pos: 16384; completed: false]

После чего сразу подготавливается вывод:

DEBUG {org.apache.synapse.transport.http.conn.LoggingNHttpServerConnection} - http-incoming-4: Produce output
...
DEBUG {org.apache.synapse.transport.passthru.SourceHandler} - http-incoming-4: Response ready

и далее

DEBUG {org.apache.synapse.transport.passthru.SourceHandler} - http-incoming-4: Timeout

Варианты решения:

1. Можно в deployment.toml повысить ту самую цифру 16384 до максимального значения размера сообщения, заданного по умолчанию, 81920

[transport.http]
io_buffer_size=16384 → 81920

но WSO2 не рекомендуют заниматься таким тюнингом.

В общем вариант, но не вариант)

2.1. Заглядываем в RESTBasicAuthHandler и ничего криминального там не видим,

 но как раз тут поток перенаправляется назад в случае не успешной аутентификации.

org.apache.axis2.context.MessageContext axis2MessageContext

                = ((Axis2MessageContext) messageContext).getAxis2MessageContext();

        Object headers = axis2MessageContext.getProperty(

                org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);

 

        if (headers != null && headers instanceof Map) {

            Map headersMap = (Map) headers;

            if (headersMap.get(HTTPConstants.HEADER_AUTHORIZATION) == null) {

                log.error(AUTH_FAILED_MESSAGE + HTTPConstants.HEADER_AUTHORIZATION + " header does not exist.");

                messageContext.getEnvelope();

                log.info( messageContext.getEnvelope());

                log.info( messageContext.getMessageString());

                headersMap.clear();

                axis2MessageContext.setProperty(BasicAuthConstants.HTTP_STATUS_CODE, BasicAuthConstants.SC_UNAUTHORIZED);

                headersMap.put(BasicAuthConstants.WWW_AUTHENTICATE, BasicAuthConstants.WWW_AUTH_METHOD);

                axis2MessageContext.setProperty(BasicAuthConstants.NO_ENTITY_BODY, true);

                messageContext.setProperty(BasicAuthConstants.RESPONSE, BasicAuthConstants.TRUE);

                messageContext.setTo(null);

                Axis2Sender.sendBack(messageContext);

                return false;

...

            }

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

С помощью чудо утилиты RelayUtils из артефакта synapse-nhttp-transport мы собираем сообщение из входящего потока.

// build input message

try {

                  RelayUtils.buildMessage(((Axis2MessageContext) messageContext).getAxis2MessageContext());

} catch (IOException | XMLStreamException e) {

                  log.error(e);

}

// read the body

log.debug(messageContext.getEnvelope().getBody());

Тело обязательно надо получить messageContext.getEnvelope().getBody() иначе процесс так и не закончится до отправки ответа.

2.2 Либо вместо messageContext.getEnvelope().getBody() можно добавить в параметры запуска для Micro integrator:

-Dforce.json.message.validation="true" -Dforce.xml.message.validation="true"

при таких параметрах в RelayUtils.buildMessage  тело полностью вычитывается для валидации.

 

Имплементируем свой Custom Handler на основе RESTBasicAuthHandler с описанными выше

изменениями. Прописываем в API и получаем результат.

 <handlers>

        <handler class="ru.emdev.wso2.BasicAuthHandler"/>

 </handlers>

 

* Картинка для статьи взята http://guywithtech.blogspot.com/2020/12/

27.12.2021