Implementing Request Response in Apache Kafka java - spring-boot

Please find the use case we need to implement.
First, we need to invoke a Kafka producer a message as a rest service, they will process and give back the response in another topic.
For us, It is a request-reply topic we need to reply back for the same request the response, using replykafka template is working fine, but we can set co-relation id in the header.
As a topic message metadata there are sending in attributes, is there any way to map the co-relation id with request topic message and reply topic message.
Explain to you better.
One microservice expects the payload as given below with correlationId in payload.
{
"operationDate": "2020-09-16T11:58:25",
"correlationId": "-5544538377183901824042719876882142227",
"birthDate": "2013-12-12",
"firstNameEn": "boby",
"firstNameAr": "الشيخ",
}
The microservice will process the payload and will give a response in another topic as.
{
"correlationId": -5544538377183901824042719876882142227,
"consumerId": null,
"userid": 123456,
"statusCode": "SUCCESS",
"errors": null
}
Now as this we need to implement using spring ReplyingKafkaTemplate.
As ReplyingKafkaTemplate will work with correlationId in the header only

Assuming you mean you want to include the topic(s) in the correlation id, see
/**
* Set a function to be called to establish a unique correlation key for each request
* record.
* #param correlationStrategy the function.
* #since 2.3
*/
public void setCorrelationIdStrategy(Function<ProducerRecord<K, V>, CorrelationKey> correlationStrategy) {
You can create your own correlation id, based on the ProducerRecord (which has the topic()).
You just need to make sure it is unique. If you manually set the KafkaHeaders.REPLY_TOPIC, it will be visible to the strategy.
EDIT
With the correlation id in the payload, use setCorrelationIdStrategy to extract the correlationId from the payload and add a RecordInterceptor to do the same on the reply side.

Thanks for the hint.
I have done as overriding the payload with Kafka header correlationId.
#Override
protected ListenableFuture<SendResult> doSend(ProducerRecord producerRecord) {
if(producerRecord.value()!=null){
// i have appeneded the header correlationId in th payload
}
return super.doSend(producerRecord);
}
And in Replay onMessage ,i have populated the response payload correlationId to the header.
#Override
public void onMessage(List<ConsumerRecord<K, R>> data) {
data.forEach(
krConsumerRecord -> //update each record header
);
super.onMessage(data);
}
In this way was successfully integrated request-response semantics with correlationId in the request and response payload.

Related

Spring AMQP AsyncRabbitTemplate Doesn't Send Message In Delay Time

I'm trying to send delayed messages on RabbitMQ with Spring AMQP.
I'm defining MessageProperties like this:
MessageProperties delayedMessageProperties = new MessageProperties();
delayedMessageProperties.setDelay(45000);
I'm defining the message which should be send in delay time like this:
org.springframework.amqp.core.Message amqpDelayedMessage = org.springframework.amqp.core.MessageBuilder.withBody(objectMapper.writeValueAsString(reversalMessage).getBytes())
.andProperties(reversalMessageProperties).build();
And then, If I send this message with RabbitTemplate, there is no problem. Message is being sent in defined delay time.
rabbitTemplate.convertSendAndReceiveAsType("delay-exchange",delayQueue, amqpDelayedMessage, new ParameterizedTypeReference<org.springframework.amqp.core.Message>() {
});
But I need to send this message asynchronously because I need not to block any other message in the system and to get more performance and if I use asyncRabbitTemplate, message is being delivered immediately. There is no delay.
asyncRabbitTemplate.convertSendAndReceiveAsType("delay-exchange",delayQueue, amqpDelayedMessage, new ParameterizedTypeReference<org.springframework.amqp.core.Message>() {
});
How can I obtain the delay with asnycRabbitTemplate?
This is probably a bug; please open an issue on GitHub.
The convertSendAndReceive() methods are not intended to send and receive raw Message objects.
In the case of the RabbitTemplate the conversion is skipped if the object is already a Message; there are some cases where this skip is not performed with the async template; please edit the question to show your template configuration.
However, since you are dealing with Message directly, don't use the convert... methods at all, simply use
public RabbitMessageFuture sendAndReceive(String exchange, String routingKey, Message message) {

Custom Bot always replies with an error

I am trying to send a webhook out from Teams, which is apparently accomplished with a Custom Bot. I am able to get the bot created and then I can do #botname stuff and the endpoint receives a payload.
However, the bot immediately replies with "Sorry, there was a problem encountered with your request". I get this error if I point the "Callback URL" to a requestb.in url or if I point it to my endpoint. This leads me to suspect the bot is expecting some specific response from the endpoint, but this isn't documented. My endpoint responds with a 202 and some json. Requestb.in responds with a 200 and "ok".
So, is it true that the bot requires a specific response payload and if so what is this payload?
That link above mentions Your custom bot will need to reply asynchronously to the HTTP request from Microsoft Teams. It will have 5 seconds to reply to the message before the connection is terminated. But there is no indication of how to satisfy this request, unless the custom bot will need to reply synchronously.
You need to return a JSON response with the keys 'text' and 'type' as shown in the example here
{
"type": "message",
"text": "This is a reply!"
}
In case you are using NodeJS, you can try this sample code
I created an azure function in C# as the callback for the custom bot and was initially sending back a json string but that didnt work. Finally I had to set the response object's Content and ContentType to get it working (as shown here). Here is the code for a simple bot that echoes back what the user types in the channel, feel free to adapt it to your scenario.
Custom MS Teams bot example code using azure functions
#r "Newtonsoft.Json"
using System.Net;
using System.Net.Http.Headers;
using Newtonsoft.Json;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
// parse query parameter
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
log.Info(JsonConvert.SerializeObject(data));
// Set name to query string or body data
name = name ?? data?.text;
Response res = new Response();
res.type = "Message";
res.text = $"You said:{name}";
var response = req.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(JsonConvert.SerializeObject(res));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
}
public class Response {
public string type;
public string text;
}

Apache Camel Spring webservices SpringWebserviceConsumer does not read answer from in if not out

I use Camel spring-ws component to expose SOAP web service by specifying it in the 'from' part of the route.
It happens to be, that at the end of the route logic, the 'out' message of Exchange is not populated, however the 'in' message contains desired response data.
Default convention for producer component is to use 'in' message of exchange if 'out' is not present when generating final response.
SpringWebserviceConsumer however only supports scenario when final exchange has the 'out' message.
Here is the snippet of code from https://github.com/apache/camel/blob/master/components/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceConsumer.java:
public void invoke(MessageContext messageContext) throws Exception {
Exchange exchange = getEndpoint().createExchange(ExchangePattern.InOptionalOut);
populateExchangeFromMessageContext(messageContext, exchange);
// start message processing
getProcessor().process(exchange);
if (exchange.getException() != null) {
throw exchange.getException();
} else if (exchange.getPattern().isOutCapable()) {
Message responseMessage = exchange.getOut(Message.class);
if (responseMessage != null) {
Source responseBody = responseMessage.getBody(Source.class);
WebServiceMessage response = messageContext.getResponse();
configuration.getMessageFilter().filterConsumer(exchange, response);
XmlConverter xmlConverter = configuration.getXmlConverter();
xmlConverter.toResult(responseBody, response.getPayloadResult());
}
}
}
This results in no response generated to the SOAP request.
Question:
Is this a bug/limitation of camel-spring-ws or I'm not using the spring-ws consumer correctly?
Otherwise, it sounds like I have to explicitly set the exchange patter to InOut?
Until CAMEL-10888 is released, as a work-around, in the route, you can set the exchange pattern to InOut to get not-null 'out' message of Exchange:
.setExchangePattern(ExchangePattern.InOut)

How to list all the jms headers attributes in apache camel?

I'm trying to read the jms header in apache-camel route. The following is the route in which I'm reading body & header.
String endPointTopic = "activemq:topic:geoLoc";
String endPointTopicOut = endPointTopic + "_outbox";
from(endPointTopic)
.log("Message from Topic is ${body} & header is ${header.Action}")
.to(endPointTopicOut);
Basically the And from the logs I'm able to see the following, which means I'm able to read the body but not the id in header.
Message from Topic is GeoLocationInfoDTO{id=2, geoLocationUUId='null',
geoLocationName='null', geoLocationDesc='null',
geoLocationPolygon='null', geoLocationCenterLatitude='null',
geoLocationCenterLongitude='null'} & header is
And the following is the code in which I'm publishing the message to activeMQ through jms template.
private MessageHeaders getMessageHeaders(HttpMethod action) {
log.debug("DomainPublisher : getMessageHeaders");
Map <String, Object> headerMap = new HashMap<>();
headerMap.put("Action", action);
return new MessageHeaders(headerMap);
}
public void publish(BaseDTO dto, HttpMethod action) {
log.debug("DomainPublisher : type is : {} : ", dto.getClass().getName());
getJmsMessagingTemplate().convertAndSend(topicMap.get(dto.getClass().getName()), dto, getMessageHeaders(action));
}
Note: I also tried to log the header id like ${header.id} instead of ${header.Action} but nothing is getting printed.
And I also wanted to know all the headers that are available to the jms message.
You can log exchange with all headers and properties as shown in this example:
.to("log:like-to-see-all?level=INFO&showAll=true&multiline=true")
http://camel.apache.org/log.html
More information about JMS headers can be found here: http://camel.apache.org/jms.html
List of possible headers:
JMSCorrelationID - The JMS correlation ID.
JMSDeliveryMode - The JMS delivery mode.
JMSDestination - The JMS destination.
JMSExpiration - The JMS expiration.
JMSMessageID - The JMS unique message ID.
JMSPriority - The JMS priority (with 0 as the lowest priority and 9 as the highest).
JMSRedelivered - Is the JMS message redelivered.
JMSReplyTo - The JMS reply-to destination.
JMSTimestamp - The JMS timestamp.
JMSType - The JMS type.
JMSXGroupID - The JMS group ID.
As per the Claus Ibsen comment looks like JMS headers only allow certain types as headers and camel will drop invalid headers. And it looks like HttpMethod (type Enum) is been dropped by Camel. All I have to do in my code is convert the Enum as String while constructing the header.
headerMap.put("Action", action);
to
headerMap.put("Action", action.toString());
The JMS headers can be viewed from the karaf client console by running the following command:
activemq:browse --amqurl tcp://localhost:61616 --msgsel JMSMessaageID='1' -Vheader TEST.FOO
Note: The above are all example values, change according to your config.

Request-response pattern using Spring amqp library

everyone. I have an HTTP API for posting messages in a RabbitMQ broker and I need to implement the request-response pattern in order to receive the responses from the server. So I am something like a bridge between the clients and the server. I push the messages to the broker with specific routing-key and there is a Consumer for that messages, which is publishing back massages as response and my API must consume the response for every request. So the diagram is something like this:
So what I do is the following- For every HTTP session I create a temporary responseQueue(which is bound to the default exchange, with routing key the name of that queue), after that I set the replyTo header of the message to be the name of the response queue(where I will wait for the response) and also set the template replyQueue to that queue. Here is my code:
public void sendMessage(AbstractEvent objectToSend, final String routingKey) {
final Queue responseQueue = rabbitAdmin.declareQueue();
byte[] messageAsBytes = null;
try {
messageAsBytes = new ObjectMapper().writeValueAsBytes(objectToSend);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
MessageProperties properties = new MessageProperties();
properties.setHeader("ContentType", MessageBodyFormat.JSON);
properties.setReplyTo(responseQueue.getName());
requestTemplate.setReplyQueue(responseQueue);
Message message = new Message(messageAsBytes, properties);
Message receivedMessage = (Message)requestTemplate.convertSendAndReceive(routingKey, message);
}
So what is the problem: The message is sent, after that it is consumed by the Consumer and its response is correctly sent to the right queue, but for some reason it is not taken back in the convertSendAndReceived method and after the set timeout my receivedMessage is null. So I tried to do several things- I started to inspect the spring code(by the way it's a real nightmare to do that) and saw that is I don't declare the response queue it creates a temporal for me, and the replyTo header is set to the name of the queue(the same what I do). The result was the same- the receivedMessage is still null. After that I decided to use another template which uses the default exchange, because the responseQueue is bound to that exchange:
requestTemplate.send(routingKey, message);
Message receivedMessage = receivingTemplate.receive(responseQueue.getName());
The result was the same- the responseMessage is still null.
The versions of the amqp and rabbit are respectively 1.2.1 and 1.2.0. So I am sure that I miss something, but I don't know what is it, so if someone can help me I would be extremely grateful.
1> It's strange that RabbitTemplate uses doSendAndReceiveWithFixed if you provide the requestTemplate.setReplyQueue(responseQueue). Looks like it is false in your explanation.
2> To make it worked with fixed ReplyQueue you should configure a reply ListenerContainer:
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory);
container.setQueues(responseQueue);
container.setMessageListener(requestTemplate);
3> But the most important part here is around correlation. The RabbitTemplate.sendAndReceive populates correlationId message property, but the consumer side has to get deal with it, too: it's not enough just to send reply to the responseQueue, the reply message should has the same correlationId property. See here: how to send response from consumer to producer to the particular request using Spring AMQP?
BTW there is no reason to populate the Message manually: You can just simply support Jackson2JsonMessageConverter to the RabbitTemplate and it will convert your objectToSend to the JSON bytes automatically with appropriate headers.

Resources