Message headers not included in error handling with Spring Integration DSL - spring

I am trying to track all transactions adding extra headers on each operation, these extra headers work fine with request and response, but in error case no headers are included.
This is my configuration (with Spring Integration DSL and Java 1.7)
#Bean
public IntegrationFlow inboundFlow() {
return IntegrationFlows.from(Amqp.inboundGateway(simpleMessageListenerContainer())
.mappedReplyHeaders(AMQPConstants.AMQP_CUSTOM_HEADER_FIELD_NAME_MATCH_PATTERN)
.mappedRequestHeaders(AMQPConstants.AMQP_CUSTOM_HEADER_FIELD_NAME_MATCH_PATTERN)
.errorChannel(gatewayErrorChannel())
.requestChannel(gatewayRequestChannel())
.replyChannel(gatewayResponseChannel())
)
.transform(getCustomFromJsonTransformer())
.route(new HeaderValueRouter(AMQPConstants.OPERATION_ROUTING_KEY))
.get();
}
#Bean
public MessageChannel gatewayRequestChannel() {
return MessageChannels.direct().get();
}
#Bean
public MessageChannel gatewayResponseChannel() {
return MessageChannels.publishSubscribe().get();
}
#Bean
public MessageChannel gatewayErrorChannel() {
return MessageChannels.publishSubscribe().get();
}
#Bean
public IntegrationFlow responseTrackerOutboundFlow() {
return trackerOutboundFlowTemplate(gatewayResponseChannel());
}
#Bean
public IntegrationFlow errorTrackerOutboundFlow() {
return trackerOutboundFlowTemplate(gatewayErrorChannel());
}
private IntegrationFlow trackerOutboundFlowTemplate(MessageChannel fromMessageChannel) {
return IntegrationFlows.from(fromMessageChannel)
.handle(Amqp.outboundAdapter(new RabbitTemplate(getConnectionFactory()))
.exchangeName(LOGGER_EXCHANGE_NAME)
.routingKey(LOGGER_EXCHANGE_ROUTING_KEY)
.mappedRequestHeaders("*"))
.get();
}
I am using errorChannel for inboundGateway and also using mappedReplyHeaders and mappedRequestHeaders, is it possible to have headers in errorChannel? There is a way to configure mapped error headers or something like that?

The mappedReplyHeaders work only if you receive the good reply from the downstream flow. They are applied exactly before sending the reply message to the AMQP.
The errorChannel is a part of integration messaging, therefore no access to the mappedReplyHeaders at all. Forget them here!
From other side the errorChannel is responsible to wrap an Exception into the new ErrorMessage. That's why you don't see your headers there directly.
But you should bare in mind that integration messaging in most cases is MessagingException with the failedMessage property. That failedMessage is a "guilty" message for an exception.
And if the normal headers population process is done everywhere, you can get access to your headers from this failedMessage of the MessagingException payload of the ErrorMessage in the errorChannel flow.

Related

Spring integration: discardChannel doesn't work for filter of integration flow

I faced with a problem when I create IntegrationFlow dynamically using DSL.
If discardChannel is defined as message channel object and if the filter returns false - nothing happens (the message is not sent to specified discard channel)
The source is:
#Autowired
#Qualifier("SIMPLE_CHANNEL")
private MessageChannel simpleChannel;
IntegrationFlow integrationFlow = IntegrationFlows.from("channelName")
.filter(simpleMessageSelectorImpl, e -> e.discardChannel(simpleChannel))
.get();
...
#Autowired
#Qualifier("SIMPLE_CHANNEL")
private MessageChannel simpleChannel;
#Bean
public IntegrationFlow simpleFlow() {
return IntegrationFlows.from(simpleChannel)
.handle(m -> System.out.println("Hello world"))
.get();
#Bean(name = "SIMPLE_CHANNEL")
public MessageChannel simpleChannel() {
return new DirectChannel();
}
But if the discard channel is defined as name of the channel, everything works.
Debuging I found that mentioned above the part of the code:
IntegrationFlow integrationFlow = IntegrationFlows.from("channelName")
.filter(simpleMessageSelectorImpl, e -> e.discardChannel(simpleChannel))
.get();
returns flow object which has map with integrationComponents and one of the component which is FilterEndpointSpec has "handler" field of type MessageFilter with discardChannel = null, and discardChannelName = null;
But if discard channel is defined as name of the channel the mentioned field "handler" with discardChannel=null but discardChannelName="SIMPLE_CHANNEL", as result everything works good.
It is behavior of my running application. Also I wrote the test and in test everything works good for both cases (the test doesn't run all spring context so maybe it is related to any conflict there)
Maybe someone has idea what it can be.
The spring boot version is 2.1.8.RELEASE, spring integration is 5.1.7.RELEASE
Thanks
The behaviour you describe is indeed incorrect and made me wonder, but after testing it out I can't seem to reproduce it, so perhaps there is something missing from the information you provided. In any event, here is the complete app that I've modeled after yours which works as expected. So perhaps you can compare and see if something jumps:
#SpringBootApplication
public class IntegrationBootApp {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IntegrationBootApp.class, args);
MessageChannel channel = context.getBean("channelName", MessageChannel.class);
PollableChannel resultChannel = context.getBean("resultChannel", PollableChannel.class);
PollableChannel discardChannel = context.getBean("SIMPLE_CHANNEL", PollableChannel.class);
channel.send(MessageBuilder.withPayload("foo").build());
System.out.println("SUCCESS: " + resultChannel.receive());
channel.send(MessageBuilder.withPayload("bar").build());
System.out.println("DISCARD: " + discardChannel.receive());
}
#Autowired
#Qualifier("SIMPLE_CHANNEL")
private PollableChannel simpleChannel;
#Bean
public IntegrationFlow integrationFlow() {
IntegrationFlow integrationFlow = IntegrationFlows.from("channelName")
.filter(v -> v.equals("foo"), e -> e.discardChannel(simpleChannel))
.channel("resultChannel")
.get();
return integrationFlow;
}
#Bean(name = "SIMPLE_CHANNEL")
public PollableChannel simpleChannel() {
return new QueueChannel();
}
#Bean
public PollableChannel resultChannel() {
return new QueueChannel(10);
}
}
with output
SUCCESS: GenericMessage [payload=foo, headers={id=cf7e2ef1-e49d-1ecb-9c92-45224d0d91c1, timestamp=1576219339077}]
DISCARD: GenericMessage [payload=bar, headers={id=bf209500-c3cd-9a7c-0216-7d6f51cd5f40, timestamp=1576219339078}]

How I post and consume a Kafka message in the same IntegrationFlows builder

I want to do a simple kafka producer/consumer using spring integration, the way I did is separate in two builders each one like bean, but I wish do this just one #Bean
#Bean
fun myProducerFlow(kafkaTemplate: KafkaTemplate<*, *>): IntegrationFlow {
return IntegrationFlows.from("testChannel")
.handle(Kafka.outboundChannelAdapter(kafkaTemplate)
.topic("channel1"))
.get()
}
#Bean
fun myConsumerFlow(consumerFactory: ConsumerFactory<*, *>): IntegrationFlow {
return IntegrationFlows.from(Kafka.messageDrivenChannelAdapter(consumerFactory,"channel1"))
.handle { message -> println(message) }
.get()
}
I wish something like this:
#Bean
fun myFlow(kafkaTemplate: KafkaTemplate<*, *>): IntegrationFlow {
return IntegrationFlows.from("testChannel")
.handle(Kafka.outboundChannelAdapter(kafkaTemplate)
.topic("channel1"))
.channel(Kafka.messageDrivenChannelAdapter(consumerFactory,"channel1"))
.handle { message -> println(message) }
.get()
}
There's no way to do that; the message-driven adapter always starts a flow.
This is two diffrents flow. use other way

How implement Error decoder for multiple feign clients

I have multiple feign clients in a Spring Boot application. I am using a Controller Advice for handling custom exceptions for each feign client.
Here my controller advice that handles two custom exceptions (one for each client: client1 and client2):
#ControllerAdvice
public class ExceptionTranslator implements ProblemHandling {
#ExceptionHandler
public ResponseEntity<Problem> handleCustomClient1Exception(CustomException1 ex, NativeWebRequest request) {
Problem problem = Problem.builder()
.title(ex.getTitle())
.detail(ex.getMessage())
.status(ex.getStatusType())
.code(ex.getCode())
.build();
return create(ex, problem, request);
}
#ExceptionHandler
public ResponseEntity<Problem> handleCustomClient2Exception(CustomException2 ex, NativeWebRequest request) {
Problem problem = Problem.builder()
.title(ex.getTitle())
.detail(ex.getMessage())
.status(ex.getStatusType())
.code(ex.getCode())
.build();
return create(ex, problem, request);
}
}
I have implemented an error decoder for feign client1.
public class ClientErrorDecoder implements ErrorDecoder {
final ObjectMapper mapper;
public ClientErrorDecoder() {
this.mapper = new ObjectMapper();
}
#Override
public Exception decode(String methodKey, Response response) {
ExceptionDTO exceptionDTO;
try {
exceptionDTO = mapper.readValue(response.body().asInputStream(), ExceptionDTO.class);
} catch (IOException e) {
throw new RuntimeException("Failed to process response body.", e);
}
return new CustomException1(exceptionDTO.getDetail(), exceptionDTO.getCode(), exceptionDTO.getTitle(), exceptionDTO.getStatus());
}
}
I have also configured feign for using that error decoder for that specific client like this:
feign:
client:
config:
client1:
errorDecoder: feign.codec.ErrorDecoder.Default
My question is: what is the best approach for handling more than one feign client exceptions? Should I use the same error decoder and treat their responses as a generic exception? Or should I create an error decoder for each feign client?
Quick Answer
If you work with different APIs, error responses will not be formatted the same way. Hence handling them separately seems to be the best approach.
Remarks
From your example, it seems like you defined a custom ErrorDecoder that may
be not used because you also configured feign to use default error decoder for you client1 in properties file.
Even if you defined a #Configuration class somewhere with a bean for your custom ClientErrorDecoder,
Spring Cloud documentation mentions that configuration properties take precedence over #Configuration annotation
If we create both #Configuration bean and configuration properties,
configuration properties will win. It will override #Configuration
values. But if you want to change the priority to #Configuration, you
can change feign.client.default-to-properties to false.
Example
Here is a hypothetical pruned configuration to handle multiple feign clients with different error decoders :
Client1:
You tell feign to load beans defined in CustomFeignConfiguration class for client1
#FeignClient(name = "client1", configuration = {CustomFeignConfiguration.class})
public interface Client1 {...}
Client2:
Client2 will use default Feign ErrorDecoder because no configuration is specified. (Will throw a FeignException on error)
#FeignClient(name = "client2")
public interface Client2 {...}
Configuration: Be carefull here, if you add #Configuration to CustomFeignConfiguration, then ClientErrorDecoder bean will be used for every loaded feign clients (depending on your application component scanning behaviour)
public class CustomFeignConfiguration {
#Bean
public ClientErrorDecoder clientErrorDecoder(ObjectMapper objectMapper) {
return new ClientErrorDecoder(objectMapper);
}
}
This configuration could be done with properties file aswell.
Side remark
From my point of view, you don't even need controller advice. If you use Spring Web #ResponseStatus annotation, you can tell which HTTP status code should be sent back with exception body thrown by your custom ErrorDecoder.
Helpeful resources
Spring Cloud Documentation
GitHub issue related to the subject

Throwing Custom Runtime exception in Spring Cloud Gateway Filters

We are using Spring Cloud Gateway with Spring Boot 2 and reactive WebFlux module.
There is an authentication filter which is added for one of the routes. Now if we throw a RuntimeException with a particular status code, it is really not picking up.
Earlier this authentication check was part of the HandlerInterceptor in Spring, but now we cannot use the web module along with WebFlux (conflict from Spring cloud gateway).
Example:
#Override
public GatewayFilter apply(Object config) {
ServerHttpRequest httpRequest = exchange.getRequest();
if(!someUtil.validRequest(httpRequest) {
throw new RuntimeException("Throw 401 Unauthorized with Custom error code and message");
}
}
Currently, the actual response always gives a 500 internal server error. From where is this coming from? Can we get hold of the errors from Filters here?
You can implement a custom error handler, and here is the Spring Boot document.
Or you can simply throw a ResponseStatusException. The default error handler will render the specific status.
Keep in mind, as of the time of writing, spring-cloud-gateway uses Spring Framework WebFlux. This means that the approach would be different. You can get hold of the exception in a filter as shown below.
Declare an exception like this:
public class UnauthorisedException extends ResponseStatusException {
public UnauthorisedException(HttpStatusCode status) {
super(status);
}
public UnauthorisedException(HttpStatusCode status, String reason) {
super(status, reason);
}
}
NB: The exception extends ResponseStatusException.
The ControllerAdvice class can be implemented as follows:
#ControllerAdvice
public class MyErrorWebExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(UnauthorisedException.class)
public Mono<ServerResponse> handleIllegalState(ServerWebExchange exchange, UnauthorisedException exc) {
exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
return ServerResponse.from(ErrorResponse.builder(exc,HttpStatus.FORBIDDEN,exc.getMessage()).build());
}
}
In your filter you can now implement the apply method as follows:
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if (request.getHeaders().get("token") == null){ //test is an example
throw new UnauthorisedException(HttpStatus.FORBIDDEN, "Not Authorised from Gateway");
}
ServerHttpRequest.Builder builder = request.mutate();
return chain.filter(exchange.mutate().request(builder.build()).build());
};
}

Spring Integration - #Filter discardChannel and/or throwExceptionOnRejection being ignored?

I have a java DSL based spring integration (spring-integration-java-dsl:1.0.1.RELEASE) flow which puts messages through a Filter to filter out certain messages. The Filter component works okay in terms of filtering out unwanted messages.
Now, I would like to set either a discardChannel="discard.ch" but, when I set the discard channel, the filtered out messages never seem to actually go to the specified discardChannel. Any ideas why this might be?
My #Filter annotated class/method:
#Component
public class MessageFilter {
#Filter(discardChannel = "discard.ch")
public boolean filter(String payload) {
// force all messages to be discarded to test discardChannel
return false;
}
}
My Integration Flow class:
#Configuration
#EnableIntegration
public class IntegrationConfig {
#Autowired
private MessageFilter messageFilter;
#Bean(name = "discard.ch")
public DirectChannel discardCh() {
return new DirectChannel();
}
#Bean
public IntegrationFlow inFlow() {
return IntegrationFlows
.from(Jms.messageDriverChannelAdapter(mlc)
.filter("#messageFilter.filter('payload')")
...
.get();
}
#Bean
public IntegrationFlow discardFlow() {
return IntegrationFlows
.from("discard.ch")
...
.get();
}
}
I have turned on spring debugging on and, I can't see where discarded messages are actually going. It is as though the discardChannel I have set on the #Filter is not being picked up at all. Any ideas why this might be?
The annotation configuration is for when using annotation-based configuration.
When using the dsl, the annotation is not relevant; you need to configure the .filter within the DSL itself...
.filter("#messageFilter.filter('payload')", e -> e.discardChannel(discardCh())

Resources