Spring Integration Повторная попытка шлюза исходящей почты HTTP на основе содержимого ответа

Я использую API, который работает в 2 этапа:

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

Итак, вопрос в том, как я могу реализовать пользовательские критерии успеха для исходящего HTTP-шлюза. Я также хотел бы объединить его с RetryAdvice, который я уже реализовал.

Я пробовал следующее, но, во-первых, полезная нагрузка сообщения, предоставленная в HandleMessageAdvice, пуста, а во-вторых, повторная попытка не запускается:

.handle(Http.outboundGateway("https://northeurope.api.cognitive.microsoft.com/vision/v3" +
        ".0/read/analyzeResults/abc")
        .mappedRequestHeaders("Ocp-Apim-Subscription-Key")
        .httpMethod(HttpMethod.GET), c -> c.advice(this.advices.retryAdvice())
              .handleMessageAdvice(new AbstractHandleMessageAdvice() {
    @Override
    protected Object doInvoke(MethodInvocation invocation, Message<?> message) throws Throwable {
        String body = (String) message.getPayload();
        if (StringUtils.isEmpty(body))
            throw new RuntimeException("Still analyzing");
        JSONObject document = new JSONObject(body);
        if (document.has("analyzeResult"))
            return message;
        else
            throw new RuntimeException("Still analyzing");
    }
}))

Я нашел этот ответ от Артема 4 года назад, но, во-первых, я не нашел метод канала ответа на исходящем шлюзе и, во-вторых, не уверен, был ли этот сценарий уже улучшен в новой версии Spring Integaration: HTTP-повторная попытка исходящего трафика с условиями (для условия проверки).

ОБНОВЛЕНИЕ

По предложению Артема имею следующее:

.handle(Http.outboundGateway("https://northeurope.api.cognitive.microsoft.com/vision/v3" +
        ".0/read/analyzeResults/abc")
        .mappedRequestHeaders("Ocp-Apim-Subscription-Key")
        .httpMethod(HttpMethod.GET), c -> c.advice(advices.verifyReplySuccess())
        .advice(advices.retryUntilRequestCompleteAdvice()))

И совет:

@Bean
public Advice verifyReplySuccess() {
    return new AbstractRequestHandlerAdvice() {
        @Override
        protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
            try {
                Object payload = ((MessageBuilder) callback.execute()).build().getPayload();
                String body = (String) ((ResponseEntity) payload).getBody();
                JSONObject document = new JSONObject(body);
                if (document.has("analyzeResult"))
                    return message;
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
            throw new RuntimeException("Still analyzing");
        }
    };
}

Но теперь, когда я отлаживаю метод doInvoke, тело полезной нагрузки имеет значение null. Это странно, когда я выполняю тот же запрос GET с помощью Postman, тело возвращается правильно. Любая идея?

Тело ответа с использованием Postman выглядит так:

{
    "status": "succeeded",
    "createdDateTime": "2020-09-01T10:55:52Z",
    "lastUpdatedDateTime": "2020-09-01T10:55:57Z",
    "analyzeResult": {
        "version": "3.0.0",
        "readResults": [
            {
                "page": 1,........

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

введите описание изображения здесь

<200,[Transfer-Encoding:"chunked", Content-Type:"application/json; charset=utf-8", x-envoy-upstream-service-time:"27", CSP-Billing-Usage:"CognitiveServices.ComputerVision.Transaction=1", apim-request-id:"a503c72f-deae-4299-9e32-625d831cfd91", Strict-Transport-Security:"max-age=31536000; includeSubDomains; preload", x-content-type-options:"nosniff", Date:"Tue, 01 Sep 2020 19:48:36 GMT"]>

person Blink    schedule 01.09.2020    source источник


Ответы (2)


Действительно, в Java DSL нет параметров канала request и reply, потому что вы просто оборачиваете эти handle() в channel() конфигурацию или просто связываете конечные точки в потоке естественным образом, и они собираются обмениваться сообщениями, используя неявные прямые каналы между ними. Вы можете посмотреть на Java DSL IntegrationFlow как на <chain> в конфигурации XML.

Конфигурация вашего совета немного неверна: вам нужно объявить свой собственный совет как первый в цепочке, поэтому, когда оттуда выбрасывается исключение, его обрабатывает повторная попытка.

Вам также следует подумать о реализации AbstractRequestHandlerAdvice, чтобы согласовать его с логикой RequestHandlerRetryAdvice.

Вы реализуете там doInvoke(), вызываете ExecutionCallback.execute() и анализируете результат, чтобы вернуть его как есть или выбросить желаемое исключение. Результатом этого вызова для HttpRequestExecutingMessageHandler будет AbstractIntegrationMessageBuilder и, вероятно, ResponseEntity в качестве payload, чтобы проверить вашу дальнейшую логику.

person Artem Bilan    schedule 01.09.2020
comment
Спасибо, Артем. Пожалуйста, взгляните на мое ОБНОВЛЕНИЕ. - person Blink; 01.09.2020
comment
То, что вы показываете, не является "телом" ResponseEntity. Все это просто заголовки HTTP и статус 200 OK. Насколько я понимаю, в этом HTTP-ответе нет тела. - person Artem Bilan; 01.09.2020
comment
Это копия всей полезной нагрузки от отладчика IntelliJ. Он не показывает тело как нулевое. При необходимости я могу опубликовать изображение ... - person Blink; 02.09.2020
comment
Каким же тогда должно быть тело? Я думал, что null означает, что вам все равно нужно повторить попытку ... - person Artem Bilan; 02.09.2020
comment
Пожалуйста, взгляните на ОБНОВЛЕНИЕ еще раз, я добавил ответ почтальона. Может быть, это как-то связано с Transfer-Encoding: chunked? Может быть, с помощью обратного вызова он не разбивается должным образом, а оставшиеся фрагменты не запрашиваются? - person Blink; 02.09.2020
comment
Привет, Артем, есть идеи, что я делаю не так? Спасибо. - person Blink; 04.09.2020
comment
Я не знаю, как это возможно, потому что дальнейшая логика в HttpRequestExecutingMessageHandler действительно основана на getBody() из ResponseEntity. Можете ли вы попробовать использовать HttpComponentsClientHttpRequestFactory для этого Http.outboundGateway() вместо стандартного? - person Artem Bilan; 04.09.2020
comment
Вы имеете в виду вот что: .requestFactory (новый HttpComponentsClientHttpRequestFactory ())? К сожалению, тело все еще пустое ... Есть другие идеи? - person Blink; 07.09.2020
comment
Итак, я просто реализую его, используя простой дескриптор с restTemplate, и я все еще получаю пустое тело в качестве ответа, поэтому он определенно не имеет ничего общего с исходящим шлюзом, я думаю ... - person Blink; 07.09.2020
comment
Посоветуйтесь со своим поставщиком услуг, что могло быть причиной такого пустого тела от них. - person Artem Bilan; 07.09.2020
comment
Хорошо, я только что понял ... Я использовал restTemplate с ResponseEntity ‹JSONDocument› вместо ResponseEntity ‹String›. Теперь он работает с restTemplate. К сожалению, проблема с исходящим шлюзом до сих пор не решена: / - person Blink; 07.09.2020
comment
Почему бы вам тоже не сделать ожидаемый тип ответа как String? - person Artem Bilan; 07.09.2020
comment
Хорошо, это почти решено ???????? Я думал, что проверил это правильно, но я думаю, что не совсем ... Проблема, которая у меня есть сейчас, заключается в том, что когда выбрасывается RuntimeException (все еще анализируется), повторная попытка не происходит. Должен ли я делать что-то другое, кроме выброса исключения? - person Blink; 25.09.2020
comment
Я думаю, вы говорите о своем ответе. Проблема в том, что эти советы связаны друг с другом в цепочку. Итак, следующий совет называется вместе с предыдущим. Следовательно, чтобы ваш совет о повторных попытках работал после исключения из другого исключения, этот advices.retryUntilRequestCompleteAdvice() обязательно должен быть первым в цепочке. - person Artem Bilan; 25.09.2020

Следуя предложению Артема, я придумал следующее (дополнительная уловка заключалась в том, чтобы установить для expectedResponseType значение String, иначе при использовании ResponseEntity тело было пустым):

.handle(Http.outboundGateway("https://northeurope.api.cognitive.microsoft.com/vision/v3" +
        ".0/read/analyzeResults/abc")
        .mappedRequestHeaders("Ocp-Apim-Subscription-Key")
        .httpMethod(HttpMethod.GET).expectedResponseType(String.class),
        c -> c.advice(advices.retryUntilRequestCompleteAdvice())
              .advice(advices.verifyReplySuccess()))

И совет:

@Bean
public Advice verifyReplySuccess() {
    return new AbstractRequestHandlerAdvice() {
        @Override
        protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
            Object payload = ((MessageBuilder) callback.execute()).build().getPayload();
            if (((String) payload).contains("analyzeResult"))
                return payload;
            else
                throw new RuntimeException("Still analyzing");
        }
    };
}
person Blink    schedule 07.09.2020