IntegrationFlow HttpRequestHandlingMessagingGateway reply directly - spring-integration

I am new to Spring Integration and i am trying to use the HttpRequestExecutingMessageHandler and the HttpRequestHandlingMessagingGateway. The Handler is sending a POST Request and expecting reply. The Gateway is consuming the request. It works fine, BUT the Handler is not getting a reply back. I dont know how to setup my flow, that i can direcly send a reply back and continue my flow.
#Bean
public HttpRequestExecutingMessageHandler httpOutbound() {
HttpRequestExecutingMessageHandler handler = Http.outboundGateway(restUrl)
.httpMethod(HttpMethod.POST)
.messageConverters(new MappingJackson2HttpMessageConverter())
.mappedRequestHeaders("Content-Type")
.get();
handler.setExpectReply(true);
handler.setOutputChannel(httpResponseChannel());
return handler;
}
#Bean
public HttpRequestHandlingMessagingGateway httpRequestGateway() {
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
RequestMapping mapping = new RequestMapping();
mapping.setMethods(HttpMethod.POST);
mapping.setPathPatterns(httpRequestHandlingPathPattern);
gateway.setRequestMapping(mapping);
gateway.setErrorChannel(errorChannel());
gateway.setReplyChannel(replyChannel());
gateway.setRequestChannel(requestChannel());
gateway.setRequestPayloadTypeClass(DocumentConverterInput.class);
return gateway;
}
#Bean
public IntegrationFlow documentConverterFlow() {
return IntegrationFlows
.from(requestChannel())
.publishSubscribeChannel(publishSubscribeSpec ->
publishSubscribeSpec.subscribe(flow -> flow
.enrichHeaders(headerEnricherSpec -> headerEnricherSpec.header("http_statusCode", HttpStatus.OK))
.channel(replyChannel())))
.enrichHeaders(headerEnricherSpec ->
headerEnricherSpec.headerExpression(Constants.DOCUMENT_CONVERTER_INPUT, "payload"))
.handle(Jms.outboundAdapter(jmsTemplate(connectionFactory)))
.get();
}
My HttpRequestExecutingMessageHandler is successfully posting the request. The HttpRequestHandlingMessagingGateway is successfully consuming it. At first i had an error "No reply received within timeout", so that i added a publishSubscriberChannel. I don't know if it is the right way, but i cannot find working examples, which show how to reply correctly.
My above code is working, BUT not sending reply back to HttpRequestExecutingMessageHandler!
My goal is to receive the request message and directly send back 200 OK. After that i want to continue my integration flow, do some stuff and send the result to queue.
Any suggestions?

For just 200 OK response you need to consider to configure an HttpRequestHandlingMessagingGateway with a expectReply = false. This way it is going to work as an Inbound Channel Adapter, but having the fact that HTTP is always request-response, it will just do this setStatusCodeIfNeeded(response, httpEntity); and your HttpRequestExecutingMessageHandler on the client side will get an empty, but OK resposnse.
Not sure though why your channel(replyChannel()) doesn't work as expected. It might be the fact that you return a request palyoad into a reply message which eventually becomes as a HTTP response, but somehow it fails there, may be during conversion...
UPDATE
Here is a simple Spring Boot application demonstrating a reply-and-process scenario: https://github.com/artembilan/sandbox/tree/master/http-reply-and-process

Related

What is it for redisQueueInboundGateway.setReplyChannelName

just want to ask what is the redisQueueInboundGateway.setReplyChannelName for
I got a log B and then a log.
1.My question is in what situation will the log C be printed when I set it to the RedisQueueInboundGateway.
the doc in "https://docs.spring.io/spring-integration/reference/html/redis.html#redis-queue-inbound-gateway" seems incorrect for class name and class explanation such like:
2.1 the 'RedisOutboundChannelAdapter' is named in 'RedisPublishingMessageHandler'.
2.2 the 'RedisQueueOutboundChannelAdapter' is named in 'RedisQueueMessageDrivenEndpoint'.
2.3 the explanation of Redis Queue Outbound Gateway is exactly the copy of Redis Queue Inbound Gateway.
#GetMapping("test")
public void test() {
this.teller.test("testing 1");
#Gateway(requestChannel = "inputA")
void test(String transaction);
#Bean("A")
PublishSubscribeChannel getA() {
return new PublishSubscribeChannel();
}
#Bean("B")
PublishSubscribeChannel getB() {
return new PublishSubscribeChannel();
}
#Bean("C")
PublishSubscribeChannel getC() {
return new PublishSubscribeChannel();
}
#ServiceActivator(inputChannel = "A")
void aTesting(Message message) {
System.out.println("A");
System.out.println(message);
}
#ServiceActivator(inputChannel = "B")
String bTesting(Message message) {
System.out.println("B");
System.out.println(message);
return message.getPayload() + "Basdfasdfasdfadsfasdf";
}
#ServiceActivator(inputChannel = "C")
void cTesting(Message message) {
System.out.println("C");
System.out.println(message);
}
#ServiceActivator(inputChannel = "inputA")
#Bean
RedisQueueOutboundGateway getRedisQueueOutboundGateway(RedisConnectionFactory connectionFactory) {
val redisQueueOutboundGateway = new RedisQueueOutboundGateway(Teller.CHANNEL_CREATE_INVOICE, connectionFactory);
redisQueueOutboundGateway.setReceiveTimeout(5);
redisQueueOutboundGateway.setOutputChannelName("A");
redisQueueOutboundGateway.setSerializer(new GenericJackson2JsonRedisSerializer(new ObjectMapper()));
return redisQueueOutboundGateway;
}
#Bean
RedisQueueInboundGateway getRedisQueueInboundGateway(RedisConnectionFactory connectionFactory) {
val redisQueueInboundGateway = new RedisQueueInboundGateway(Teller.CHANNEL_CREATE_INVOICE, connectionFactory);
redisQueueInboundGateway.setReceiveTimeout(5);
redisQueueInboundGateway.setRequestChannelName("B");
redisQueueInboundGateway.setReplyChannelName("C");
redisQueueInboundGateway.setSerializer(new GenericJackson2JsonRedisSerializer(new ObjectMapper()));
return redisQueueInboundGateway;
}
Your concern is not clear.
2.1
There is a component (pattern name) and there is a class on background covering the logic.
Sometime they are not the same.
So, Redis Outbound Channel Adapter is covered by the RedisPublishingMessageHandler, just because there is a ConsumerEndpointFactoryBean to consume messages from the input channel and RedisPublishingMessageHandler to handle them. In other words the framework creates two beans to make such a Redis interaction working. In fact all the outbound channel adapters (gateways) are handled the same way: endpoint plus handler. Together they are called adapter or gateway depending on the type of the interaction.
2.2
I don't see such a misleading in the docs.
2.3
That's not true.
See difference:
Spring Integration introduced the Redis queue outbound gateway to perform request and reply scenarios. It pushes a conversation UUID to the provided queue,
Spring Integration 4.1 introduced the Redis queue inbound gateway to perform request and reply scenarios. It pops a conversation UUID from the provided queue
All the inbound gateways are supplied with an optional replyChannel to track the replies. It is not where this type of gateways is going to send something. It is fully opposite: the place where this inbound gateway is going to take a reply channel to send a reply message into Redis back. The Inbound gateway is initiated externally. In our case as a request message in the configured Redis list. When you Integration flow does its work, it sends a reply message to this gateway. In most cases it is done automatically using a replyChannel header from the message. But if you would like to track that reply, you add a PublishSubscribeChannel as that replyChannel option on the inbound gateway and both your service activator and the gateway get the same message.
The behavior behind that replyChannel option is explained in the Messaging Gateway chapter: https://docs.spring.io/spring-integration/reference/html/messaging-endpoints.html#gateway-default-reply-channel
You probably right about this section in the docs https://docs.spring.io/spring-integration/reference/html/redis.html#redis-queue-inbound-gateway and those requestChannel and replyChannel are really a copy of the text from the Outbound Gateway section. That has to be fixed. Feel free to raise a GH issue so we won't forget to address it.
The C logs are going to be printed when you send a message into that C channel, but again: if you want to make a reply correlation working for the Redis Inbound Gateway it has to be as a PublishSubscribeChannel. Otherwise just omit it and your String bTesting(Message message) { will send its result to the replyChannel header.

How to handle exceptions on InfegrationFlows?

I have this flow connected thru Spring Cloud DataFlow. One microservice gets the email from a queue and sends to this other microservice that should send the email.
I want to be able to catch failed emails and have the original message before .transform() and the exception message.
How to do it?
#Bean
public IntegrationFlow sendMailFlow() {
return IntegrationFlows.from(Sink.INPUT)
.transform(Transformers.converter(converter))
.handle(Mail.outboundAdapter(EmailSinkApplication.this.props.getServer())
.port(EmailSinkApplication.this.props.getPort())
.credentials(EmailSinkApplication.this.props.getUser(), EmailSinkApplication.this.props.getPass())
.protocol(EmailSinkApplication.this.props.getProto())
.javaMailProperties(p -> {
p.put("mail.smtp.starttls.enable", "true");
p.put("mail.smtp.auth", "true");
}),
e -> e.id("sendMailEndpoint"))
.get();
}
I suggest you to implement a ChannelInterceptor and add your logic into the afterSendCompletion(). Use a #GlobalChannelInterceptor on this interceptor bean to specify a patterns as a Sink.INPUT. So, you are going to have a try..catch around the whole flow on that Sink.INPUT channel.

How could I transform my object from my channel (spring-integration, DSL)?

I have got an Integrationflow which returns me a Message and now I would like to validate it in a second channel and after sending it back I would like to transform it.
But the transformation failed.
#Bean
public IntegrationFlow httpUserGetUserByUsername() {
return IntegrationFlows.from(httpGetGateUsersGetUserByUsername())
.channel("http.users.getUserByUsername").handle("userEndpoint", "getUserByUsername")
.channel("http.users.checkUserAuth").handle("userEndpoint","checkUserByUsername")
.transform(User u -> new UserResponseHelper(u))
.get();
}
public void checkUserByUsername(Message<User> msg) {
MessageChannel replayChannel = (MessageChannel) msg.getHeaders().getReplyChannel();
User u = msg.getPayload();
if (Authorization.isAllowByUsername(u.getUsername())) {
replayChannel.send(MessageBuilder.withPayload(u).build());
}else{
replayChannel.send(MessageBuilder.withPayload(new ResponseEntity(new ApiResponse(HttpStatus.FORBIDDEN,false,"You are not allow to get this ressource"), HttpStatus.FORBIDDEN)).build());
}
}
I get the user with all 8 properties and what i expect ist a user with 4 props
OK. I see what is your problem. You really have just missed the fact that your .transform(User u -> new UserResponseHelper(u)) is not called at all. So, that your "transformation failed" has confused me.
So, what is going on here:
Your void checkUserByUsername(Message<User> msg) returns void. Therefore there is nothing to wrap into an output Message. And from here nothing is going to trigger the next .transform() in your flow.
What you see for your REST service, that everything from that checkUserByUsername(Message<User> msg) is sent to the replyChannel header - the place where Inbound Gateway waits for reply.
In case of validation failure you send some custom ApiResponse. Why just don't throw an Exception, e.g. ResponseStatusException? This is going to be handled properly on the REST layer and returned as an error to the REST client.
I suggest you to really return that u from this checkUserByUsername() method and throw an exception otherwise. This way the User object is going to come to your .transform(User u -> new UserResponseHelper(u)) - and all good!
You have just confused yourself that void doesn't trigger transform and replyChannel is just for sending reply directly to the initiator gateway.

Poll on HttpOutboundGateway

#Bean
public HttpMessageHandlerSpec documentumPolledInbound() {
return Http
.outboundGateway("/fintegration/getReadyForReimInvoices/Cz", restTemplate)
.expectedResponseType(String.class)
.errorHandler(new DefaultResponseErrorHandler())
;
}
How to poll the above, to get the payload for further processing
The HTTP Client endpoint is not pollable, but event-driven. So, as you usually call some REST Service from the curl, for example, the same way happens here. You have some .handle(), I guess, for this documentumPolledInbound() and there is some message channel to send message to trigger this endpoint to call your REST service.
Not clear how you are going to handle a response, but there is rally a way to trigger an event periodically to call this endpoint.
For that purpose we only can * mock* an inbound channel adapter which can configured with some trigger policy:
#Bean
public IntegrationFlow httpPollingFlow() {
return IntegrationFlows
.from(() -> "", e -> e.poller(p -> p.fixedDelay(10, TimeUnit.SECONDS)))
.handle(documentumPolledInbound())
.get();
}
This way we are going to send a message with an empty string as a payload to the channel for the handle(). And we do that every 10 seconds.
Since your HttpMessageHandlerSpec doesn't care about an inbound payload, it is really doesn't matter what we return from the MessageSource in the polling channel adapter.

Spring integration: preserve http error code and body in response

I have an inbound http gateway whose incoming message is passed to an outbound http gateway (using integration java dsl).
When the outbound gateway response has http status 200, the outbound response goes back to the caller of the inbound gateway as expected.
But when the outbound gateway responds with specific client errors like 423 locked or 403 forbidden, the caller of the inbound gateway receives 500 and a body message that contains an exception as String.
I understand that this is because SI handles outbound http errors as exceptions. But in my case I need to pass through the error status and the response body that comes with the outbound response back to the caller of the inbound gateway, for the specific error codes. How do I do that?
We call this scenario as HTTP Proxy and have some test-case on the matter: https://github.com/spring-projects/spring-integration/blob/master/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java
I've just done some investigation and here is what you need:
IntegrationFlows
.from(Http.inboundGateway("/service")
.requestMapping(r -> r.params("name"))
.errorChannel("httpProxyErrorFlow.input"))
...
#Bean
public IntegrationFlow httpProxyErrorFlow() {
return f -> f
.transform(Throwable::getCause)
.<HttpClientErrorException>handle((p, h) ->
MessageBuilder.withPayload(p.getResponseBodyAsString())
.setHeader(HttpHeaders.STATUS_CODE, p.getStatusCode())
.build());
}
We have to handle downstream errors on the Inbound Gateway level. For this purpose Spring Integration suggest the errorChannel functionality.
That httpProxyErrorFlow provides some logic on the matter. First of all we know that the message for the errorChannel is ErrorMessage and its payload is MessagingException - as a result of wrapping HttpClientErrorException in the HttpRequestExecutingMessageHandler. So, with the .transform() we drill down to the desired exception and in the .handle() we build a new message based on the HttpClientErrorException content.
The HttpRequestHandlingMessagingGateway is able to process such a message properly and set a desired status code to the response:
protected final Object setupResponseAndConvertReply(ServletServerHttpResponse response, Message<?> replyMessage) {
getHeaderMapper().fromHeaders(replyMessage.getHeaders(), response.getHeaders());
HttpStatus httpStatus = this.resolveHttpStatusFromHeaders(replyMessage.getHeaders());
if (httpStatus != null) {
response.setStatusCode(httpStatus);
}
http://docs.spring.io/spring-integration/docs/4.3.11.RELEASE/reference/html/http.html#http-response-statuscode

Resources