Error handling - no output-channel or replyChannel header available - spring-integration

I am trying to handle exceptions using ExpressionEvaluatingRequestHandlerAdvice, have a transformer for a fail channel,
<int:transformer input-channel="afterFailureChannel" output-channel="validateOutputChannel" ref="testExceptionTransformer" method="handleLockServiceResponse"/>
In testExceptionTransformer, I am forming user defined exception and sending it in http response entity which I want to send as a rest api response, Even though transformer has outputChannel, application throws
org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutput(AbstractMessageProducingHandler.java:452) ~[spring-integration-core-5.5.13.jar:5.5.13]
Could you please help?
Edit:
Transformer looks like this,
public ResponseEntity<Object> handleLockServiceResponse(Message<MessagingException> message) throws Exception {
ResponseEntity<Object> response = null;
LOGGER.error(message.getPayload().getFailedMessage().toString());
LOGGER.error(message.getPayload().getCause().toString());
try {
Throwable exception = message.getPayload().getCause();
if (exception.getCause() instanceof HttpClientErrorException) {
throw new handleValidationException(exception.getCause().getMessage());
}
}catch(handleValidationException ex){
return adapterErrorHandler.handleCustomValidationException(ex);
}
return response;
}

It indeed doesn't fail in your transformer since you have that output-channel it fails in the initial gateway when it tries to correlate the reply message into a TemporaryReplyChannel from headers. We need to see what your transformer does, but the rule of thumb is if you return a Message from the transformer, you have to coyp headers from request message. However with an ExpressionEvaluatingRequestHandlerAdvice and its failureChannel it is a bit tricky.
The logic there is like this:
if (evalResult != null && this.failureChannel != null) {
MessagingException messagingException =
new MessageHandlingExpressionEvaluatingAdviceException(message, "Handler Failed",
unwrapThrowableIfNecessary(exception), evalResult);
ErrorMessage errorMessage = new ErrorMessage(messagingException);
this.messagingTemplate.send(this.failureChannel, errorMessage);
}
It becomes obvious that ErrorMessage doesn't have a request message headers. So, you need to extract them from that exception via getFailedMessage() and that's the one is sent to your service instrumented with that ExpressionEvaluatingRequestHandlerAdvice.
We probably need to improve the doc on the matter: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#expression-advice
UPDATE
So, now you return a ResponseEntity from your transformer method and headers for the reply message is copied from that ErrorMessage we send from the ExpressionEvaluatingRequestHandlerAdvice. To preserve original message headers in the reply message you must do something like this:
public Message<ResponseEntity<Object>> handleLockServiceResponse(Message<MessagingException> message) throws Exception {
ResponseEntity<Object> response = null;
LOGGER.error(message.getPayload().getFailedMessage().toString());
LOGGER.error(message.getPayload().getCause().toString());
try {
Throwable exception = message.getPayload().getCause();
if (exception.getCause() instanceof HttpClientErrorException) {
throw new handleValidationException(exception.getCause().getMessage());
}
}catch(handleValidationException ex){
response = adapterErrorHandler.handleCustomValidationException(ex);
}
return MessageBuilder.withPayload(response).copyHeaders(message.getPayload().getFailedMessage().getHeaders()).build();
}

Related

Spring Integration DSL - OAuth2ErrorHandler issues with 4XX series error codes

We are using spring integration DSL to call downstream services. But we are facing issues when 4XX series error code is returned by downstream service.
Below is the code snippet that we are using to call downstream services
#Bean
public IntegrationFlow getDataChannelFlow() {
return IntegrationFlows.from(MessageChannels.executor("getDataChannel", Executors.newCachedThreadPool()))
.enrichHeaders(h -> h.headerExpression(USER_REF, SRC_USER_REF)
.handle(Http.outboundGateway(endPoint1, auth2RestTemplate)
.uriVariable("data", PAYLOAD)
.httpMethod(HttpMethod.GET)
.transferCookies(true)
.expectedResponseType(String.class), e->e.advice(integrationAdvice).id("docIDAdvice"))
.transform(this::responseTransformer)
.enrichHeaders(header -> header.headerExpression("DOCUMENTS", PAYLOAD))
}
In case of 200 and 500 response from downstream services, our code is working fine but when we get 4XX series errors we are getting below exception in logs and control does not return back to transformer method
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/fetchUser": Attempted read from closed stream.; nested exception is java.io.IOException: Attempted read from closed stream.
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:785)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:138)
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)
... 42 more
Caused by: java.io.IOException: Attempted read from closed stream.
at org.apache.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:141)
at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:118)
Few things that we noticed while debugging -
Spring's OAuth2ErrorHandler.java class differentiates between 4XX and 5XX series of errors
public boolean hasError(ClientHttpResponse response) throws IOException
{
return HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())
|| this.errorHandler.hasError(response);
}
In above code snippet hasError() method returns true for 4XX series of codes and due to this we are getting IOException when below code snippet is executed
protected <T> T doExecute(URI url, #Nullable HttpMethod method, #Nullable RequestCallback requestCallback,
#Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}
finally {
if (response != null) {
response.close();
}
}
}
Our expectation is that control should return back to transformer method so that we will have the control over response processing.
Any suggestions on this issue would be much appreciated.

Error handling issue with http ResponseEntity using ExpressionEvaluatingRequestHandlerAdvice

Continuation of Error handling - no output-channel or replyChannel header available,
I am returning ResponseEntity from the transformer of ExpressionEvaluatingRequestHandlerAdvice failureChannel. when I debug I can see
ResponseEntity<Object> response = <409 CONFLICT Conflict,com.practice.integration.commons.error.AdapterErrorResponse#4d5a370b,[]> and its body(AdapterErrorResponse POJO) has HttpStatus status, List<AdapterError> errors which has populated correct value that I want and as per Artem Bilan's suggestion for preserving request message headers I am sending that response as MessageBuilder.withPayload(response).copyHeaders(message.getPayload().getFailedMessage().getHeaders()).build()
and I have also configured output channel on the transformer but it still does not show the above response as a part of http response payload, output channel I have is same as reply channel of the inbound gateway. could you please help here?
and I have one more external call following the above, there also I have used different transformer to handle exception and I am sending similar ResponseEntity from there , it works fine there and send response to the reply channel of the inbound gateway. Only difference is I am not using ExpressionEvaluatingRequestHandlerAdvice for the second outbound gateway.
Do you think I should do something extra with handling response using ExpressionEvaluatingRequestHandlerAdvice or am I missing anything on the first outbound gateway?
You probably didn't do this: ExpressionEvaluatingRequestHandlerAdvice.setTrapException(true);
Here is a working test, it is not HTTP based, but approach is exactly the same for any inbound request-reply gateway:
#SpringJUnitConfig
public class So74658669Tests {
#Autowired
InputGateway inputGateway;
#Test
void errorHandlerResultPropagatedBackToGateway() {
assertThat(this.inputGateway.sendAndReceive("test"))
.isEqualTo("Request failed for: test");
}
#Configuration
#EnableIntegration
#Import(InputGateway.class)
public static class TestConfiguration {
#Bean
MessageChannel outputChannel() {
return new DirectChannel();
}
#ServiceActivator(inputChannel = "inputChannel", outputChannel = "outputChannel", adviceChain = "requestHandlerAdvice")
String requestAndReply(String payload) {
throw new RuntimeException("failure");
}
#Bean
ExpressionEvaluatingRequestHandlerAdvice requestHandlerAdvice() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setFailureChannelName("errorHandlerChannel");
advice.setTrapException(true);
return advice;
}
#Transformer(inputChannel = "errorHandlerChannel", outputChannel = "outputChannel")
Message<String> errorHandler(Message<MessagingException> errorMessage) {
return MessageBuilder.withPayload("Request failed for: " + errorMessage.getPayload().getFailedMessage().getPayload())
.copyHeaders(errorMessage.getPayload().getFailedMessage().getHeaders())
.build();
}
}
#MessagingGateway
interface InputGateway {
#Gateway(requestChannel = "inputChannel", replyChannel = "outputChannel")
String sendAndReceive(String payload);
}
}
By the way there is no need in that outputChannel at all if you don't do any extra work on reply. The framework just find a replyChannel header and sends reply message directly to the input gateway.

Configured errorChannel not called after aggregation

We are facing a strange behavior in our integration flows where the errorChannel does not receive a message in case an exception is thrown in a step after an aggregation.
This is the (reduced) flow:
#Bean
public StandardIntegrationFlow startKafkaInbound() {
return IntegrationFlows.from(Kafka
.messageDrivenChannelAdapter(
kafkaConsumerFactory,
ListenerMode.record,
serviceProperties.getInputTopic().getName())
.errorChannel(errorHandler.getInputChannel())
)
.channel(nextChannel().getInputChannel())
.get();
}
#Bean
public IntegrationFlow nextChannel() {
return IntegrationFlows.from("next")
.transform(Transformers.fromJson(MyObject.class)) // An exception here is sent to errorChannel
.aggregate(aggregatorSpec ->
aggregatorSpec
.releaseStrategy(new MessageCountReleaseStrategy(100))
.sendPartialResultOnExpiry(true)
.groupTimeout(2000L)
.expireGroupsUponCompletion(true)
.correlationStrategy(message -> KafkaHeaderUtils.getOrDefault(message.getHeaders(), MY_CORRELATION_HEADER, ""))
)
.transform(myObjectTransformer) // Exception here is not sent to errorChannel
.channel(acknowledgeMyObjectFlow().getInputChannel())
.get();
}
If we add an explicit channel which is not of type DirectChannel the errorHandling is working as expected. Working code looks like:
// ...
.aggregate(aggregatorSpec -> ...)
.channel(MessageChannels.queue())
.transform(myObjectTransformer) // Now the exception is sent to errorChannel
.channel(acknowledgeMyObjectFlow().getInputChannel())
// ...
Also we'd like to mention, that we have a very similar flow with an aggregation where errorHandling works as expected (Exception sent to errorChannel)
So we were actually able to get the code running, but since errorHandling is a very critical part of the application we'd really like to understand how we can ensure each error will be sent to the configured channel and why explicitly setting a QueueChannel leads to the wanted behavior.
Thanks in advance
You can add this
.enrichHeaders(headers -> headers.header(MessageHeaders.ERROR_CHANNEL, (errorHandler.getInputChannel()))
before an aggregator.
The .channel(MessageChannels.queue()) is misleading over here because the error is sent to the global errorChannel, which is apparently is the same as yours errorHandler.getInputChannel().
The problem that .groupTimeout(2000L) is done on a separate TaskScheduler thread and when an error happens downstream there is no knowledge about try..catch in that Kafka.messageDrivenChannelAdapter.
Feel free to raise a GH issue, so we will think about populating that errorChannel into message headers from the MessageProducerSupport, like that Kafka.messageDrivenChannelAdapter. So, the error handling would be the same independently of the async nature of the downstream flow.
UPDATE
Please, try this as a solution:
.transform(Transformers.fromJson(MyDataObject.class)) // An exception here is sent to errorChannel
.enrichHeaders(headers -> headers.header(MessageHeaders.ERROR_CHANNEL, (errorHandler.getInputChannel())))
.aggregate(aggregatorSpec ->
The enrichHeaders() should do the trick to determine a proper error channel to send error.
Plus your MyDataObjectTransformer has to be modified to this:
throw new MessageTransformationException(source, "test");
The point is that there is a logic like this when exception is caught by the endpoint:
if (handler != null) {
try {
handler.handleMessage(message);
return true;
}
catch (Exception e) {
throw IntegrationUtils.wrapInDeliveryExceptionIfNecessary(message,
() -> "Dispatcher failed to deliver Message", e);
}
}
where:
if (!(ex instanceof MessagingException) ||
((MessagingException) ex).getFailedMessage() == null) {
runtimeException = new MessageDeliveryException(message, text.get(), ex);
}
And then in the AbstractCorrelatingMessageHandler:
catch (MessageDeliveryException ex) {
logger.warn(ex, () ->
"The MessageGroup [" + groupId +
"] is rescheduled by the reason of: ");
scheduleGroupToForceComplete(groupId);
}
That's how your exception does not reach the error channel.
You may consider to not use that MessageTransformationException. The logic in the wrapping handler is like this:
protected Object handleRequestMessage(Message<?> message) {
try {
return this.transformer.transform(message);
}
catch (Exception e) {
if (e instanceof MessageTransformationException) { // NOSONAR
throw (MessageTransformationException) e;
}
throw new MessageTransformationException(message, "Failed to transform Message in " + this, e);
}
}
UPDATE 2
OK. I see that you use Spring Boot and that one does not register a respective ErrorHandler to the TaskScheduler used in the aggregator for group timeout feature.
Please, consider to add this bean into your configuration:
#Bean
TaskSchedulerCustomizer taskSchedulerCustomizer(ErrorHandler integrationMessagePublishingErrorHandler) {
return taskScheduler -> taskScheduler.setErrorHandler(integrationMessagePublishingErrorHandler);
}
And then feel free to raise a GH issue for Spring Boot to make this customization as a default one in the auto-configuration.

Spring Integration Java DSL and Http.outboundGateway: How to get the real error message JSON

How to get the real error message JSON when the Http.outboundGateway call is failed.
For example my program does the HTTP POST. The operation fails with the error code 400 Bad Request and the real error message is (tested with the Postman):
{
"name": [
"This field is needed."
]
}
I have the error channel like this:
#Bean
private IntegrationFlow myErrorChannel() {
return f -> f.handle("myErrorHandler", "handle")
....
;
}
and the Class MyErrorHandler is like this:
#Service
public class MyErrorHandler {
#ServiceActivator
public Message<MessageHandlingException> handle(Message<MessageHandlingException> message) {
...
}
}
Does the MessageHandlingException contain the real error message?
{
"name": [
"This field is needed."
]
}
I debugged the code and checked the MessageHandlingException exception and it seems it doesn't contain the real error message. The detailMessage contains the text 400 Bad Request, but I want to know the real error message.
How to get the real error message?
Edit:
This is working (I'm assigning the real error message to the new payload):
final RestClientResponseException clientException = (RestClientResponseException) messagingHandlingException.getCause();
payload = clientException.getResponseBodyAsString();
The Resttemplate uses a DefaultResponseErrorHandler by default. That one has a logic like:
protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
String statusText = response.getStatusText();
HttpHeaders headers = response.getHeaders();
byte[] body = getResponseBody(response);
Charset charset = getCharset(response);
switch (statusCode.series()) {
case CLIENT_ERROR:
throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset);
case SERVER_ERROR:
throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset);
default:
throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset);
}
}
An exception from here is thrown to the HttpRequestExecutingMessageHandler which really wraps it into the MessageHandlingException.
Since you say that you can handle the last one via your MyErrorHandler, I would suggest you just take a look into the cause of the MessageHandlingException, and you'll that RestClientResponseException with all the required info from the response.

Need to send Messaging Exception from the Transformer in Spring Integration

I have to customize the error handling for my project . In case of error, depending on the error type, I want to send it to different queues.
However, before sending it to the queues depending on the type we do some transformations in the exception message along with the conversion of object to String (For which I am using the transformer) so that the queue can have the object value as the message text, so that we can track easily.
Now the problem is, during the conversion of object to String or any other error (code specific) occurs , service starts to throw an exception with no error channel has been registered.
This Error handling code is generalized for several components, hence in case of any failure even in transformation. I want to pass the exception to the Error Transformer, to pass it to Error Queue.
XML configuration for Error is:
<int:transformer id = "errorTransformer" input-channel="errorsDest"
ref="exceptionTransformer" output-channel="errors" />
<bean id="exceptionTransformer"
class="com.commons.spring.integration.error.ErrorTransformer">
</bean>
<int-jms:outbound-channel-adapter id ="errorQueueAdapter"
explicit-qos-enabled="${jms.qos.enabled}"
auto-startup="${jms.connect}" channel="errors" pub-sub-domain="false"
connection-factory="connectionFactory" destination-name="${error}" />
#Transformer
public Message<String> handleError(MessagingException message) {
headers.put("stacktrace", ExceptionUtils.getStackTrace(message));
headers.put("serviceCausedTheException", EnvironmentResolver.getService());
Message<?> failedMessage = message.getFailedMessage();
Object msgPayload = failedMessage.getPayload();
String payload = "";
try {
if (msgPayload instanceof String)
payload = (String) failedMessage.getPayload();
else if (msgPayload instanceof MyObject)
payload = XMLMarshallingUtil.objectToXml((MyObject) msgPayload);
} catch (Exception e) {
payload = msgPayload.toString();
headers.put("Object Conversion Exception Occurred in Error Transformer",
e.getMessage());
}
Message<String> parsed = MessageBuilder.withPayload(payload).copyHeaders(headers).
copyHeaders(message.getFailedMessage().getHeaders()).build();
return parsed;
}
Now, as this Transformer expects Messaging Exception Object, hence in order to forward the message to Error Transformer from other transformers , I need to pass the Messaging Exception Object. Using MessageChannel.send method, I could only pass on the message object.Please suggest
Below is my Another transformer code , from where I would like to forward the message to error queue
public Message<MyObject> handleError(MessagingException message) {
Message<MyObject> messageMyObject = null;
try {
Object obj = message.getFailedMessage().getPayload();
MyObject styleML = null;
if (obj instanceof String) {
String temp = (String) obj;
if (temp != null && temp.contains("MyObject"))
styleML = XMLMarshallingUtil.xmlToObject(temp, MyObject.class);
} else if (obj instanceof MyObject) {
styleML = (MyObject) obj;
}
String serviceName = EnvironmentResolver.getService();
Throwable t = ExceptionUtils.getRootCause(message.getCause());
if (t == null)
t = message.getCause();
String userComment = "Exception Occurred";
String sysComment = t.getMessage();
MyObject= MyObjectUtils.addMessageEventToMyObject(sysComment, userComment, styleML, serviceName, ProcessState.IN_ERROR);
messageMyObject = MessageBuilder.withPayload(styleML).copyHeaders(message.getFailedMessage().getHeaders()).build();
} catch (Exception e) {
errorsDest.send(MessageBuilder.withPayload(message).build());
}
return messageStyleML;
}
Right. Channels have Message contract for sending and receiving methods. However each message has payload. And in your case handleError accepts messages with MessagingException payload. In case of error-handling to the errorChannel, when any your endpoint throws exception, it will be wrapped to the MessagingException with failedMessage and further to the ErrorMessage.
So, if you want to send something directly to the channel, even if it is Exception, you need to wrap it with Message. And it can be a result of MessageBuilder.
From other side Method Invocation principle of Spring Integration allows you to have any desired flexibility over method arguments. In your case the handleError method accepts MessagingException, and it is a default strategy to map message's payload to the method's argument.
Hope I understood you correctly...
UPDATE
Regarding the second question about how to avoid sending message to both channels.
I suggest the simplest way:
change <transformer> to the <service-activator>. (Fom method invocation perspective they are similar.)
After errorsDest.send add just return null;. Transformer doesn't allow to return null. ServiceActivator allows it by default. In this case your main flow will be stopped.

Resources