spring-integrationspring-integration-dslspring-integration-http

Surfacing errors of Http.outboundGateway() when used as part of a enricher subflow


I've got a scenario that was caused by misconfiguration of a URL

@Bean
ExpressionEvaluatingRequestHandlerAdvice notFoundAdvice() {
    final ExpressionEvaluatingRequestHandlerAdvice advice =
        new ExpressionEvaluatingRequestHandlerAdvice();
    advice.setOnFailureExpressionString(
        "#exception.getCause()"
        + " instanceof T(org.springframework.web.client.HttpClientErrorException.NotFound)"
        + " ? '{ \"value\": null }' : payload"
    );
    advice.setReturnFailureExpressionResult(true);
    return advice;
}

@Bean
public IntegrationFlow myIntegrationFlow(final RestTemplate restTemplate) {
    return f -> f
        f.enrich(
            e -> e.requestSubFlow(
                sf -> sf
                    .handle(
                        Http.outboundGateway(misconfiguredUrl, restTemplate)
                            .httpMethod(HttpMethod.GET)
                            .uriVariable("productId", "headers.productId")
                            .mappedResponseHeaders()
                            .expectedResponseType(String.class),
                        ec -> ec.advice(notFoundAdvice())
                    )
                )
                .propertyExpression("productData", "#jsonPath(payload, '$.value')")
            )
        );
}

Obviously this is overly contrived, but if misconfiguredUrl were to say not include the http://host portion of the URL, this would result in a URL failure.

I am not seeing this particular error blowing out but that the #jsonPath(...) fails with $[value] not found.

Is there some configuration that can be applied to log an error from the outboundGateway in this case?

EDIT:

After commentary exchange I realized that I had (1) omitted the endpoint configuration that handles the case of a "legit" 404 error (2) in doing so identified that I had set setReturnFailureExpressionResult which was incorrect.

Removing the setReturnFailureExpressionResult setting corrected the problem and surfaced the error.

EDIT2: So I spoke to soon and did not completely test this.

While this surfaces the error it does not allow me to trap specifically one case (404) and return a default while still allowing other calls to fail as normal with exception.

I might have to rethink the approach a little here.

EDIT3: Implemented a custom handling class

public class CustomNotFoundAdvice extends AbstractRequestHandlerAdvice {
    private static final Logger log = LoggerFactory.getLogger(CustomNotFoundAdvice.class);

    final Object defaultReturn;

    public CustomNotFoundAdvice(final Object defaultReturn) {
        this.defaultReturn = defaultReturn;
    }

    @Override
    protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
        Object result;
        try {
            result = callback.execute();
        }
        catch(RuntimeException ex) {
            final Exception realException = unwrapExceptionIfNecessary(ex);
            if( realException instanceof MessageHandlingException
                    && realException.getCause() instanceof HttpClientErrorException.NotFound) {
                log.warn("Unable to locate object "+target);
                result = defaultReturn;
            }
            else {
                throw ex;
            }
        }
        return result;
    }
    
}

Solution

  • Probably you don't show the whole picture or the error you face is not relevant. Here is a simplified flow for your use-case:

        @Bean
        public IntegrationFlow httpEnricherInvalidUrl() {
            return f -> f
                    .enrich(e -> e.requestSubFlow(sf -> sf.handle(Http.outboundGateway("foo")))
                            .propertyExpression("productData", "payload"));
        }
    

    The unit-test is like this:

    @Autowired
    @Qualifier("httpEnricherInvalidUrl.input")
    MessageChannel httpEnricherInvalidUrlInput;
    
    @Test
    void testInvalidHttpUrlWithinEnricher() {
        QueueChannel replyChannel = new QueueChannel();
        Message<?> testMessage = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build();
    
        this.httpEnricherInvalidUrlInput.send(testMessage);
    }
    

    and here is an expected exception in the logs since I don't assert anything in the test:

    org.springframework.messaging.MessageHandlingException: error occurred in message handler [bean 'httpEnricherInvalidUrl.subFlow#0.http:outbound-gateway#0' for component 'httpEnricherInvalidUrl.subFlow#0.org.springframework.integration.config.ConsumerEndpointFactoryBean#0'; defined in: 'org.springframework.integration.http.dsl.HttpDslTests$ContextConfiguration'; from source: 'bean method httpEnricherInvalidUrl']; nested exception is java.lang.IllegalArgumentException: URI is not absolute
    , failedMessage=GenericMessage [payload=test, headers={replyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel@7f323b3a, errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel@7f323b3a, id=91f2d0c0-e824-64ff-701b-83871a34a680, timestamp=1631820501148}]
        at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:192)
        at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:65)
        at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:115)
        at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:133)
        at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:106)
        at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:72)
        at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:317)
        at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:187)
        at org.springframework.messaging.core.GenericMessagingTemplate.doSendAndReceive(GenericMessagingTemplate.java:233)
        at org.springframework.messaging.core.GenericMessagingTemplate.doSendAndReceive(GenericMessagingTemplate.java:47)
        at org.springframework.messaging.core.AbstractMessagingTemplate.sendAndReceive(AbstractMessagingTemplate.java:46)
        at org.springframework.integration.core.MessagingTemplate.sendAndReceive(MessagingTemplate.java:97)
        at org.springframework.integration.gateway.MessagingGatewaySupport.doSendAndReceive(MessagingGatewaySupport.java:522)
        at org.springframework.integration.gateway.MessagingGatewaySupport.sendAndReceiveMessage(MessagingGatewaySupport.java:492)
        at org.springframework.integration.transformer.ContentEnricher$Gateway.sendAndReceiveMessage(ContentEnricher.java:497)
        at org.springframework.integration.transformer.ContentEnricher.handleRequestMessage(ContentEnricher.java:350)
        at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:136)
        at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:56)
        at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:115)
        at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:133)
        at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:106)
        at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:72)
        at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:317)
        at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:272)
        at org.springframework.integration.http.dsl.HttpDslTests.testInvalidHttpUrlWithinEnricher(HttpDslTests.java:309)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:564)
        at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
        at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
        at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
        at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
        at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
        at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
        at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
        at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
        at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1510)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1510)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
        at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
        at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
        at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
        at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
        at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
        at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
        at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
        at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
    Caused by: java.lang.IllegalArgumentException: URI is not absolute
        at java.base/java.net.URL.fromURI(URL.java:719)
        at java.base/java.net.URI.toURL(URI.java:1139)
        at org.springframework.http.client.SimpleClientHttpRequestFactory.createRequest(SimpleClientHttpRequestFactory.java:145)
        at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:124)
        at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:772)
        at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:732)
        at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:612)
        at org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler.exchange(HttpRequestExecutingMessageHandler.java:196)
        at org.springframework.integration.http.outbound.AbstractHttpRequestExecutingMessageHandler.handleRequestMessage(AbstractHttpRequestExecutingMessageHandler.java:311)
        at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:136)
        at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:56)
    

    So, probably we talk about different things. Just because the code is the same, but result different.

    Maybe time for you to share with us some simple project to let us reproduce and play with?