Spring Integragion + Message Gateway error handling - spring

I have a message gateway which is called by a REST controller and I wonder what is the correct approach to handle any errors that might occur on the downstream flow.
What I mean by "handle any errors" is to log some information about them, take some other action whatever that might be, and finally be able to return a 400 to the controller.
For me, the approach that makes more sense is to have an errorChannel on the gateway, BUT I think that a replyChannel might make sense as well.
I've been able to handle the errors with the "errorChannel" approach, don't know if this is the way to go:
#MessagingGateway(errorChannel = "integrationFlowErrorChannel")
public interface OrderGateway {
#Gateway(requestChannel = "orders.input")
void processOrderRequest(Order order);
}
Any error sent to the errorChannel are handle by the following service activator:
#ServiceActivator(inputChannel="integrationFlowErrorChannel")
public void handleExceptions(Message<MessageHandlingException> message) throws RuntimeException {
log.error(String.format("error: %s", message));
log.error(String.format("error: %s", message.getPayload()));
throw new RuntimeException("something bad happened");
}
Thanks

What you have is correct.
BUT I think that a replyChannel might make sense as well.
It's not clear what you mean by that; a reply channel is generally not needed unless you want to do something like logging the reply via a wiretap or sending the reply to multiple places.

Related

Timeout on replyChannel when wireTap is used

We are using wireTap to take timestamps at different parts of the flow. When introduced to the newest flow, it started causing a timeout in the replyChannel. From what I understand from the documentation, wireTap does intercept the message and sends it to secondary channel, while not affecting the main flow - so it looks like the perfect thing to use to take snapshots of said timestamps. Are we using wrong component for the job, or is there something wrong with the configuration? And if so, how would you recommend to register such information?
The exception:
o.s.integration.core.MessagingTemplate : Failed to receive message from channel 'org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#21845b0d' within timeout: 1000
The code:
#Bean
public MarshallingWebServiceInboundGateway inboundGateway(Jaxb2Marshaller jaxb2Marshaller,
DefaultSoapHeaderMapper defaultSoapHeaderMapper) {
final MarshallingWebServiceInboundGateway inboundGateway =
new MarshallingWebServiceInboundGateway(jaxb2Marshaller);
inboundGateway.setRequestChannelName(INPUT_CHANNEL_NAME);
inboundGateway.setHeaderMapper(defaultSoapHeaderMapper);
return inboundGateway;
}
#Bean
public IntegrationFlow querySynchronous() {
return IntegrationFlows.from(INPUT_CHANNEL_NAME)
.enrichHeaders(...)
.wireTap(performanceTimestampRegistrator.registerTimestampFlow(SYNC_REQUEST_RECEIVED_TIMESTAMP_NAME))
.handle(outboundGateway)
.wireTap(performanceTimestampRegistrator.registerTimestampFlow(SYNC_RESPONSE_RECEIVED_TIMESTAMP_NAME))
//.transform( m -> m) // for tests - REMOVE
.get();
}
And the timestamp flow:
public IntegrationFlow registerTimestampFlow(String asyncRequestReceivedTimestampName) {
return channel -> channel.handle(
m -> MetadataStoreConfig.registerFlowTimestamp(m, metadataStore, asyncRequestReceivedTimestampName));
}
The notable thing here is that if I uncomment the no-operation transformer, everything suddenly works fine, but it doesn't sound right and I would like to avoid such workarounds.
Another thing is that the other, very similar flow works correctly, without any workarounds. Notable difference being it puts message in kafka using kafka adapter, instead of calling some web service with outbound gateway. It still generates response to handle (with generateResponseFlow()), so it should behave the same way. Here is the flow, which works fine:
#Bean
public MarshallingWebServiceInboundGateway workingInboundGateway(Jaxb2Marshaller jaxb2Marshaller,
DefaultSoapHeaderMapper defaultSoapHeaderMapper, #Qualifier("errorChannel") MessageChannel errorChannel) {
MarshallingWebServiceInboundGateway aeoNotificationInboundGateway =
new MarshallingWebServiceInboundGateway(jaxb2Marshaller);
aeoNotificationInboundGateway.setRequestChannelName(WORKING_INPUT_CHANNEL_NAME);
aeoNotificationInboundGateway.setHeaderMapper(defaultSoapHeaderMapper);
aeoNotificationInboundGateway.setErrorChannel(errorChannel);
return aeoNotificationInboundGateway;
}
#Bean
public IntegrationFlow workingEnqueue() {
return IntegrationFlows.from(WORKING_INPUT_CHANNEL_NAME)
.enrichHeaders(...)
.wireTap(performanceTimestampRegistrator
.registerTimestampFlow(ASYNC_REQUEST_RECEIVED_TIMESTAMP_NAME))
.filter(...)
.filter(...)
.publishSubscribeChannel(channel -> channel
.subscribe(sendToKafkaFlow())
.subscribe(generateResponseFlow()))
.wireTap(performanceTimestampRegistrator
.registerTimestampFlow(ASYNC_REQUEST_ENQUEUED_TIMESTAMP_NAME))
.get();
}
Then, there is no problem with wireTap being the last component and response is correctly received on replyChannel in time, without any workarounds.
The behavior is expected.
When the wireTap() (or log()) is used in the end of flow, there is no reply by default.
Since we can't assume what logic you try to include into the flow definition, therefore we do our best with the default behavior - the flow becomes a one-way, send-and-forget one: some people really asked to make it non replyable after log() ...
To make it still reply to the caller you need to add a bridge() in the end of flow.
See more in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-log
It works with your much complex scenario because one of the subscriber for your publishSubscribeChannel is that generateResponseFlow() with the reply. Honestly you need to be careful with request-reply behavior and such a publishSubscribeChannel configuration. The replyChannel can accept only one reply and if you would expect a reply from several subscribers, you would be surprised how the behavior is strange.
The wireTap in this your configuration is not a subscriber, it is an interceptor injected into that publishSubscribeChannel. So, your assumption about similarity is misleading. There is the end of the flow after that wiretap, but since one of the subscribers is replying, you get an expected behavior. Let's take a look into the publishSubscribeChannel as a parallel electrical circuit where all the connections get an electricity independently of others. And they perform they job not affecting all others. Anyway this is different story.
To conclude: to reply from the flow after wireTap(), you need to specify a bridge() and reply message will be routed properly into the replyChannel from the caller.

How to catch IOException's while using Spring WebClient?

I'm working on a project in which we are migrating from RestTemplate to WebClient.
The WebClient is implemented like this:
try {
...
return webClient
.get()
.uri(someUri)
.retrieve()
.toEntity(SomeBusinessClass.class).block()
} catch(WebClientException e) {
// do some stuff
// want to catch IOExceptions here as well
}
While refactoring the code I had to refactor the tests as well and I've come across a test in which we basically throw an ConnectException to see if our internal code catch them according to our needs. With RestTemplate's exception classes we was able to define the exception like this:
ResourceAccessException exc = new ResourceAccessException("I/O error on GET request", new ConnectException("Connection refused: connect"))
I tried to do the same with WebClient's provided exception class WebClientException but that's an abstract class and the only class inheriting from it is WebClientResponseException and that don't provide a constructor which would allow to do the same. So my only option was to do it with RuntimeException:
RuntimeException exc = new RuntimeException("I/O error on GET request", new ConnectException("Connection refused: connect"))
But since I don't want to rewrite our internal code to catch exceptions on RuntimeException level but on WebClientException level, is that not an option and I'm wondering how to do that?
I tried to find out in the Spring docs how to handle IOException's while using WebClient but couldn't find anything.
What would be the approach here?
The nicest way would almost certainly be to handle all errors in the reactive stream itself. Server response errors are usually best handled by using exchange() rather than retrieve() and then dealing with the response manually, and an underlying IOException by using the onErrorResume(), onErrorReturn() etc. reactive operators available for this purpose.
However, you mention you're migrating from blocking code, so I understand that practically that may not (yet) be on the cards. If you want to stick to catching exceptions:
But since I don't want to rewrite our internal code to catch exceptions on RuntimeException level but on WebClientException level, is that not an option and I'm wondering how to do that?
Wanting to catch all transport errors under the umbrella of WebClientException is not a sensible option. As you say, neither is just catching RuntimeException for obvious reasons.
Simplifying it, WebClientException means "I connected to the URL and sent stuff to it without an issue, but it told me to sod off" (ie. it generated an error code rather than a 200 response.)
That might be because of a 404 (resource not found), 500 (server error), 418 (you're trying to connect to a teapot, not a server), etc.
IOException on the other hand means "Couldn't even establish a connection to this URL." That could be because the connection was actively refused, the domain name couldn't be resolved, the SSL cert expired, etc.
The two are not analogous, and it would be rather odd and confusing to treat them that way.
If you want to handle them in the same block, then that's fine - naively you might just do:
catch(WebClientException|IOException e) {
// do some stuff
}
...but you can't of course, because IOException is checked. (Reactive streams in Java don't throw checked exceptions, each checked exception is mapped to a RuntimeException instead.)
However, you can map all IOException to an UncheckedIOException:
return webClient
.get()
.uri(someUri)
.retrieve()
.toEntity(SomeBusinessClass.class)
.onErrorMap(IOException.class, UncheckedIOException::new)
.block()
...and then either do catch(WebClientException|UncheckedIOException ex), or deal with them in separate catch blocks.
This certainly isn't the "nice" way to handle exceptions from a reactive mindset, but if you're aiming to migrate with the fewest possible changes, this is likely what you're after.

Spring websocket: how to send to all subscribers except the message sender

I am following the quick-start guide on Spring websocket with sockJs and Stomp here:
https://spring.io/guides/gs/messaging-stomp-websocket/
At this point, my code looks like to one from guide and works as intended. I have a controller class with a method accepting incoming messages and sending them back to all who subscribed on the topic.
What I want to do, is to change the code, so my #MessageMapping annotated method sends response to all subscribers excluding the one who send the message to the controller in the first place (because the sender is also subscribed to the same topic, but i dont want the sender to keep receiving messages it send itself, it is kind of a loop I guess).
I have seen many docs describing how to send a message to a single subscriber, but have not yet seen on describing how to send to all but one - the initial message sender.
Is there any built-in way to do this easily in Spring websocket?
Ok so i've managed to find some solution which works for me at this point of time:
i was able to filter subscribers by principal user name.
I got all simp users form org.springframework.messaging.simp.user.SimpUserRegistry,
and a current sender from org.springframework.messaging.simp.stomp.StompHeaderAccessor.
My code looks something like this:
#MessageMapping("/game/doStuff")
public void gameGrid(DoStuffMessage doStuffMessage,
StompHeaderAccessor headers) {
sendTo("/game/doStuff", doStuffMessage, headers);
}
private void sendTo(String destination, Object payload, StompHeaderAccessor headers) {
Optional<String> user = Optional.ofNullable(headers.getUser())
.map(Principal::getName);
if (user.isPresent()) {
List<String> subscribers = simpUserRegistry.getUsers().stream()
.map(SimpUser::getName)
.filter(name -> !user.get().equals(name))
.collect(Collectors.toList());
subscribers
.forEach(sub -> simpMessagingTemplate.convertAndSendToUser(sub, destination, payload));
}
}
Client is subscribing to /user/game/doStuff
It works for now. What I am worried about is if this code can scale horizontally - if someone has any insight on this I'd greatly appreciate that.

ActiveMQ how to send async message inside method annotated with JmsListener

My goal is simple I have to send notification(about in progress state) instantly after reciving message from JmsListener.
What I did so far is setting async send in yml
broker-url: tcp://localhost:61616?jms.useAsyncSend=true
and nothing happend, obviously it doesnt work like that
My problem is that all messages are being sent after message is already processed. It looks like sync mode.
My simplified code looks like this
#JmsListener(destination = "${messagesQueue}")
public void handleMessage(Message message){
jmsTemplate.convertAndSend("statusQueue", Status.IN_PROGRESS);
//... processing stuff
//... onSuccess
jmsTemplate.convertAndSend("statusQueue", Status.OK);
//... onFailure
jmsTemplate.convertAndSend("statusQueue", Status.ERROR);
}
I use spring boot with fully automated config
Any advice is welcome! What should I do?
Ok I found workaround by passing initial in progress message to another thread.
new Thread(() -> {
jmsTemplate.convertAndSend("statusQueue", Status.IN_PROGRESS);
}).start();
Looks ugly and I don't think its final solution but it may help someone else.

MassTransit fault consumer not invoked for request/response

What is the best practice for handling exceptions in MassTransit 3+ with regard to Request/Response pattern? The docs here mention that if a ResponseAddress exists on a message, the Fault message will be sent to that address, but how does one consumer/receive the messages at that address? The ResponseAddress for Bus.Request seems to be an auto-generated MassTransit address that I don't have control over, so I don't know how to access the exception thrown in the main consumer. What am I missing? Here's my code to register the consumer and its fault consumer using Unity container:
cfg.ReceiveEndpoint(host, "request_response_queue", e =>
{
e.Consumer<IConsumer<IRequestResponse>>(container);
e.Consumer(() => container.Resolve<IMessageFaultConsumer<IRequestResponse>>() as IConsumer<Fault<IRequestResponse>>);
});
And here's my attempt at a global message fault consumer:
public interface IMessageFaultConsumer<TMessage>
{
}
public class MessageFaultConsumer<TMessage> : IConsumer<Fault<TMessage>>, IMessageFaultConsumer<TMessage>
{
public Task Consume(ConsumeContext<Fault<TMessage>> context)
{
Console.WriteLine("MessageFaultConsumer");
return Task.FromResult(0);
}
}
This approach DOES work when I use Bus.Publish as opposed to Bus.Request. I also looked into creating an IConsumeObserver and putting my global exception logging code into the ConsumeFault method, but that has the downside of being invoked every exception prior to the re-tries giving up. What is the proper way to handle exceptions for request/response?
First of all, the request/response support in MassTransit is meant to be used with the .Request() method, or the request client (MessageRequestClient or PublishRequestClient). With these methods, if the consumer of the request message throws an exception, that exception is packaged into the Fault<T>, which is sent to the ResponseAddress. Since the .Request() method, and the request client are both asynchronous, using await will throw an exception with the exception data from the fault included. That's how it is designed, await the request and it will either complete, timeout, or fault (throw an exception upon await).
If you are trying to put in some global "exception handler" code for logging purposes, you really should log those at the service boundary, and an observer is the best way to handle it. This way, you can just implement the ConsumeFault method, and log to your event sink. However, this is synchronous within the consumer pipeline, so recognize the delay that could be introduced.
The other option is to of course just consume Fault<T>, but as you mentioned, it does not get published when the request client is used with the response address in the header. In this case, perhaps your requester should publish an event indicating that operation X faulted, and you can log that -- at the business context level versus the service level.
There are many options here, it's just choosing the one that fits your use case best.

Resources