I am writing a simple TCP Server application with Spring Boot Integration Framework that has to send a greeting to the client on connection establishment. The workflow should be as follows:
Client connects to server
Server sends a greeting message to the client "Hello client"
Client sends a message to the server "Any string"
Server responds with "OK"
...
Currently steps 1, 3 and 4 are working, but I'm failing on step 2.
My code so far:
#SpringBootApplication
#EnableIntegration
public class ExampleApp {
public static void main(String[] args) {
SpringApplication.run(ExampleApp.class, args);
}
// Create listener on port 1234
#Bean
public AbstractServerConnectionFactory serverConnectionFactory() {
TcpNetServerConnectionFactory tcpNetServerConnectionFactory = new TcpNetServerConnectionFactory(1234);
return tcpNetServerConnectionFactory;
}
// Inbound channel
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
// Outbound channel
#Bean
public MessageChannel replyChannel() {
return new DirectChannel();
}
// Inbound gateway
#Bean
public TcpInboundGateway tcpInboundGateway(AbstractServerConnectionFactory serverConnectionFactory, MessageChannel requestChannel, MessageChannel replyChannel) {
TcpInboundGateway tcpInboundGateway = new TcpInboundGateway();
tcpInboundGateway.setConnectionFactory(serverConnectionFactory);
tcpInboundGateway.setRequestChannel(requestChannel);
tcpInboundGateway.setReplyChannel(replyChannel);
return tcpInboundGateway;
}
// Send reply for incoming message -> working
#ServiceActivator(inputChannel = "requestChannel", outputChannel = "replyChannel")
public Message<String> processMessage(Message<String> message) {
Message reply = MessageBuilder
.withPayload("OK")
.setHeader(IpHeaders.CONNECTION_ID, message.getHeaders().get(IpHeaders.CONNECTION_ID, String.class))
.build();
return reply;
}
// Send greeting -> not working
#Bean
public ApplicationListener<TcpConnectionEvent> listener(MessageChannel replyChannel) {
return tcpConnectionEvent -> {
if (tcpConnectionEvent instanceof TcpConnectionOpenEvent) {
Message<String> message = MessageBuilder
.withPayload("Hello client")
.setHeader(IpHeaders.CONNECTION_ID, tcpConnectionEvent.getConnectionId())
.build();
replyChannel.send(message);
}
};
}
}
If I connect to the server with
nc -C localhost 1234
connection is established, but I'm getting the following error in the log:
Failed to publish TcpConnectionOpenEvent [source=TcpNetConnection:localhost:37656:1234:187cfbc2-7e5d-4f4e-97de-1a3b55a4e264], [factory=serverConnectionFactory, connectionId=localhost:37656:1234:187cfbc2-7e5d-4f4e-97de-1a3b55a4e264] OPENED:Dispatcher has no subscribers for channel 'application.replyChannel'
If I send a String to the server he replies with "OK" as intended.
What am I missing to get this greeting message working?
Solution
Thanks to the comment of Gary Russel I found a solution. The Inbound gateway must be "split" into a Inbound/Outbound channel adapter pair. Here is the fully working example:
#SpringBootApplication
#EnableIntegration
public class ExampleApp {
public static void main(String[] args) {
SpringApplication.run(ExampleApp.class, args);
}
// Create listener on port 1234
#Bean
public AbstractServerConnectionFactory serverConnectionFactory() {
TcpNetServerConnectionFactory tcpNetServerConnectionFactory = new TcpNetServerConnectionFactory(1234);
return tcpNetServerConnectionFactory;
}
// Inbound channel
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
// Outbound channel
#Bean
public MessageChannel replyChannel() {
return new DirectChannel();
}
// Inbound channel adapter
#Bean
public TcpReceivingChannelAdapter receivingChannelAdapter(AbstractServerConnectionFactory serverConnectionFactory, MessageChannel requestChannel) {
TcpReceivingChannelAdapter tcpReceivingChannelAdapter = new TcpReceivingChannelAdapter();
tcpReceivingChannelAdapter.setConnectionFactory(serverConnectionFactory);
tcpReceivingChannelAdapter.setOutputChannel(requestChannel);
return tcpReceivingChannelAdapter;
}
// Outbound channel adapter
#Bean
#ServiceActivator(inputChannel = "replyChannel")
public TcpSendingMessageHandler tcpSendingMessageHandler(AbstractServerConnectionFactory serverConnectionFactory) {
TcpSendingMessageHandler tcpSendingMessageHandler = new TcpSendingMessageHandler();
tcpSendingMessageHandler.setConnectionFactory(serverConnectionFactory);
return tcpSendingMessageHandler;
}
// Send reply for incoming message -> working
#ServiceActivator(inputChannel = "requestChannel", outputChannel = "replyChannel")
public Message<String> processMessage(Message<String> message) {
Message<String> reply = MessageBuilder
.withPayload("OK")
.setHeader(IpHeaders.CONNECTION_ID, message.getHeaders().get(IpHeaders.CONNECTION_ID, String.class))
.build();
return reply;
}
// Send greeting -> now working
#Bean
public ApplicationListener<TcpConnectionEvent> listener(MessageChannel replyChannel) {
return tcpConnectionEvent -> {
if (tcpConnectionEvent instanceof TcpConnectionOpenEvent) {
Message<String> message = MessageBuilder
.withPayload("Hello client")
.setHeader(IpHeaders.CONNECTION_ID, tcpConnectionEvent.getConnectionId())
.build();
replyChannel.send(message);
}
};
}
}
Now the client gets the greeting "Hello client" on connection establishment and an "OK" as reply on each send message.
replyChannel.send(message);
You can't do that; the reply channel is wired up when the first request comes in.
In any case, you can't use a gateway like that, the reply channel is for a reply to a request not for sending some arbitrary message.
You have to use a pair of inbound/outbound channel adapters, instead of a gateway, to enable arbitrary two-way communication.
Related
I currently have a TcpInboundGateway that takes in messages, does some processing on the message and then returns the appropriate response, all as a TcpInboundGateway should.
However, I am curious if this TcpInboundGateway can be configured in such a way that it will send an immediate response to the originating request but continue to process the request and send the post-processing response as well?
Think of this immediate response as an acknowledgement to the sender that the message was received.
Possible Solution:
After reviewing this post, I came up with what I believe to be a viable solution to this problem.
#Configuration
#EnableIntegration
public class Configuration {
#Bean
public AbstractServerConnectionFactory serverConnectionFactory() {
return new TcpNetServerConnectionFactory(2002);
}
#Bean
public TcpReceivingChannelAdapter inboundAdapter(AbstractServerConnectionFactory serverConnectionFactory) {
TcpReceivingChannelAdapter inboundAdapter = new TcpReceivingChannelAdapter();
inboundAdapter.setConnectionFactory(serverConnectionFactory);
inboundAdapter.setOutputChannelName("sendAcknowledgement");
return inboundAdapter;
}
#MessageEndpoint
public class InboundMessageHandler {
#Autowired
private OutboundMessageGateway gateway;
#ServiceActivator(inputChannel="sendAcknowledgement", outputChannel="doProcessing")
public Message<String> initialAck(Message<String> message) {
gateway.send("ACK", message.getHeaders().get(IpHeaders.CONNECTION_ID).toString());
return message;
}
#ServiceActivator(inputChannel="doProcessing", outputChannel="sendResponse")
public Message<String> mockDelay(Message<String> message) throws InterruptedException {
return message;
}
}
#MessagingGateway(defaultRequestChannel="sendResponse")
public interface OutboundMessageGateway {
void send(#Payload String message, #Header(IpHeaders.CONNECTION_ID) String connectionId);
}
#Bean
#ServiceActivator(inputChannel="sendResponse")
public TcpSendingMessageHandler outboundAdapter(AbstractServerConnectionFactory serverConnectionFactory) {
TcpSendingMessageHandler outboundAdapter = new TcpSendingMessageHandler();
outboundAdapter.setConnectionFactory(serverConnectionFactory);
return outboundAdapter;
}
}
For the use-case with the TcpInboundGateway and acking with later reply you need to use a PublishSubscribeChannel with an Executor injected to make a processing async.
The first subscriber should return some ack into the replyChannel header. This way your TcpInboundGateway will perform request-reply and return that ack into the socket connected.
At the same time as you want, the second subscriber can perform desired logic and build the real reply later. Only the point that we need to use the mention in the docs Collaborating Outbound and Inbound Channel Adapters (as you noticed already). So, since TcpInboundGateway populates an IpHeaders.CONNECTION_ID header into a request message, it is going to be available in your async process and subsequent TcpSendingMessageHandler will know where to send your processed reply:
private void handleMessageAsServer(Message<?> message) {
// We don't own the connection, we are asynchronously replying
String connectionId = message.getHeaders().get(IpHeaders.CONNECTION_ID, String.class);
TcpConnection connection = null;
if (connectionId != null) {
connection = this.connections.get(connectionId);
}
if (connection != null) {
try {
connection.send(message);
}
So, what you need is like this:
a PublishSubscribeChannel with an executor for your TcpInboundGateway
A simple handler to reply with an ack as a first subscriber
A sub-flow for processing a request
A TcpSendingMessageHandler to send a process response into the same TCP connection.
I'm working on an integration with a REST service, the idea is that it's polled by an outbound gateway marketingCategoryOutboundGateway implemented by HttpRequestExecutingMessageHandler. The gateway makes a request to the REST service and pushes its response to the marketingCategory channel. The gateway itself is triggered by a message created by marketingCategoryPollerMessageSource using the makeTriggeringMessage factory method.
The problem is that the service returns paginated results. I something which would listen on the marketingCategory channel, apart from the service activator I already have, check if the response and push a new message with an incremented page number created by makeTriggeringMessage to the marketingCategoryPoller channel, so that the code would spin in a loop until it fetches all the pages from the REST service.
Does Spring Integration allow to make such filters which receive one message on the input channel, test it against a condition and push a new message to the output channel if the condition is true?
The code:
//Responses from the REST service go to this channel
#Bean("marketingCategory")
MessageChannel marketingCategory() { return new PublishSubscribeChannel();}
//This channel is used to trigger the outbound gateway which makes a request to the REST service
#Bean
MessageChannel marketingCategoryPoller() {return new DirectChannel();}
//An adapter creating triggering messages for the gateway
#Bean
#InboundChannelAdapter(channel = "marketingCategoryPoller", poller = #Poller(fixedDelay = "15000"))
public MessageSource<String> marketingCategoryPollerMessageSource() { return () -> makeTriggeringMessage(1);}
//A factory for producing messages which trigger the gateway
private Message<String> makeTriggeringMessage(int page) {
//make a message for triggering marketingCategoryOutboundGateway
return MessageBuilder.withPayload("")
.setHeader("Host", "eclinic")
.setHeader("page", page)
.build();
}
//An outbound gateway, makes a request to the REST service and returns the response to marketingCategory channel
#Bean
#ServiceActivator(inputChannel = "marketingCategoryPoller")
public MessageHandler marketingCategoryOutboundGateway(#Qualifier("marketingCategory") MessageChannel channel) {
//make a request to the REST service and push the response to the marketingCategory channel
}
//handler for REST service responses
#Bean
#ServiceActivator(inputChannel = "marketingCategory")
public MessageHandler marketingCategoryHandler() {
return (msg) -> {
//process the categories returned by marketingCategoryOutboundGateway
};
}
I've found a solution based on this posting Read and download from a paginated REST-Services with spring integration:
Trigger the outbound gateway which talks to the REST service and pushes the response to a channel using an inbound channel adapter with a poller.
The inbound channel adapter is a message source which originally generates a message with a header indicating the page number to fetch from the REST API.
The page message header is used by the outbound gateway to generate a url specifying the desired page
The channel to which the outbound gateway pushes REST service responses has 2 subscribers:
2.1. a service activator which does something with the fetched data
2.2. a filter, which checks if this is the last page and if not, it sends the message further to another channel used by a header enricher
Having received a message, the header enricher increments its page header and pushes the message further to the channel which triggers the outbound gateway,
the gateway read the incremented page header and fetches the next page from the REST service
The loop keeps spinning until the REST service returns the last page. The filter doesn't let this message to pass through to the header enricher breaking the loop.
Full code:
#Configuration
public class IntegrationConfiguration {
private final ApiGateConfig apiGateConfig;
IntegrationConfiguration(ApiGateConfig apiGateConfig) {
this.apiGateConfig = apiGateConfig;
}
#Bean("marketingCategory")
MessageChannel marketingCategory() {
return new PublishSubscribeChannel();
}
#Bean
MessageChannel marketingCategoryPoller() {
return new DirectChannel();
}
#Bean
MessageChannel marketingCategoryPollerNextPage() {
return new DirectChannel();
}
#Bean
#InboundChannelAdapter(channel = "marketingCategoryPoller", poller = #Poller(fixedDelay = "15000"))
public MessageSource<RestPageImpl<MarketingCategory>> marketingCategoryPollerMessageSource() {
return () -> makeTriggeringMessage(0);
}
/**
* Build a gateway triggering message
*/
private Message<RestPageImpl<MarketingCategory>> makeTriggeringMessage(int page) {
return MessageBuilder.withPayload(new RestPageImpl<MarketingCategory>())
.setHeader("Host", "eclinic")
.setHeader("page", page)
.build();
}
#Bean
#ServiceActivator(inputChannel = "marketingCategoryPoller")
public MessageHandler marketingCategoryOutboundGateway(#Qualifier("marketingCategory") MessageChannel channel) {
String uri = apiGateConfig.getUri() + "/marketingCategories?page={page}";
//the type of the payload
ParameterizedTypeReference<RestPageImpl<MarketingCategory>> type = new ParameterizedTypeReference<>() {
};
//page number comes from the message
SpelExpressionParser expressionParser = new SpelExpressionParser();
var uriVariables = new HashMap<String, Expression>();
uriVariables.put("page", expressionParser.parseExpression("headers.page"));
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler(uri);
handler.setHttpMethod(HttpMethod.GET);
handler.setExpectedResponseTypeExpression(new ValueExpression<>(type));
handler.setOutputChannel(channel);
handler.setUriVariableExpressions(uriVariables);
return handler;
}
#Bean
#ServiceActivator(inputChannel = "marketingCategory")
public MessageHandler marketingCategoryHandler() {
return (msg) -> {
var page = (RestPageImpl<MarketingCategory>) msg.getPayload();
System.out.println("Page #" + page.getNumber());
page.getContent().forEach(c -> System.out.println(c.getMarketingCategory()));
};
}
#Filter(inputChannel = "marketingCategory", outputChannel = "marketingCategoryPollerNextPage")
public boolean marketingCategoryPaginationFilter(RestPageImpl<MarketingCategory> page) {
return !page.isLast();
}
#Bean
#Transformer(inputChannel = "marketingCategoryPollerNextPage", outputChannel = "marketingCategoryPoller")
HeaderEnricher incrementPage() {
Map<String, HeaderValueMessageProcessor<?>> headersToAdd = new HashMap<>();
Expression expression = new SpelExpressionParser().parseExpression("headers.page+1");
var valueProcessor = new ExpressionEvaluatingHeaderValueMessageProcessor<>(expression, Integer.class);
valueProcessor.setOverwrite(true);
headersToAdd.put("page", valueProcessor);
return new HeaderEnricher(headersToAdd);
}
}
I am new to spring boot and am trying to use the sample example from the spring integration in order to subscribe and publish using MQTT. I manage to integrate it with Thingsboard and the logger in the code below is able to receive the published message from Thingsboard.
public static void main(String[] args) {
SpringApplication.run(MqttTest.class);
}
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[] { "URI HERE" });
options.setUserName("ACCESS TOKEN HERE");
factory.setConnectionOptions(options);
return factory;
}
// consumer
#Bean
public IntegrationFlow mqttInFlow() {
return IntegrationFlows.from(mqttInbound())
.transform(p -> p)
.handle(logger())
.get();
}
private LoggingHandler logger() {
LoggingHandler loggingHandler = new LoggingHandler("INFO");
loggingHandler.setLoggerName("LoggerBot");
return loggingHandler;
}
#Bean
public MessageProducerSupport mqttInbound() {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("Consumer",
mqttClientFactory(), "v1/devices/me/rpc/request/+");
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
return adapter;
}
This is the console output. I am able to receive the published json message that was sent from the thingsboard dashboard. I am wondering if there is a call method to retrieve the json message string so that I can process it further. Thank you.
2019-02-01 14:06:23.590 INFO 13416 --- [ Call: Consumer] LoggerBot : {"method":"setValue","params":true}
2019-02-01 14:06:24.840 INFO 13416 --- [ Call: Consumer] LoggerBot : {"method":"setValue","params":false}
To handle the published messages, subscribe message handles to the flow to consume the messages.
MessageHandler
#Bean
public IntegrationFlow mqttInFlow() {
return IntegrationFlows.from(mqttInbound())
.transform(p -> p)
.handle( mess -> {
System.out.println("mess"+mess);
})
.get();
}
ServiceActivator
#Bean
public IntegrationFlow mqttInFlow() {
return IntegrationFlows.from(mqttInbound())
.transform(p -> p)
.handle("myService","handleHere")
.handle(logger())
.get();
}
#Component
public class MyService {
#ServiceActivator
public Object handleHere(#Payload Object mess) {
System.out.println("payload "+mess);
return mess;
}
}
Note: As we discussed, there are lot of different ways of achieving it.
This is just a sample for your understanding.
I create a nio socket server as follows.
How can I define a timeout that will automatically send some kind of error response back to the client, if the timeout is exceeded?
#MessageEndpoint
public class SocketEndpoint {
#ServiceActivator(inputChannel = "serverChannel", sendTimeout = "5000")
public String handleMessage(String message) {
TimeUnit.SECONDS.sleep(60);
//...TODO how to send some kind of "timeout exceeded" response?
}
}
#Bean
public TcpConnectionFactoryFactoryBean factory() {
TcpConnectionFactoryFactoryBean f = new TcpConnectionFactoryFactoryBean();
f.setType("server");
f.setPort(port);
f.setUsingNio(true);
f.setSingleUse(false);
f.setDeserializer(deserializer);
f.setSerializer(serializer);
return f;
}
#Bean
public TcpInboundGateway server(
TcpConnectionFactoryFactoryBean factory,
MessageChannel serverChannel) throws Exception {
TcpInboundGateway g = new TcpInboundGateway();
g.setConnectionFactory(factory.getObject());
g.setRequestChannel(serverChannel);
return g;
}
There is no way to do that.
On the client side you can set a socket timeout so the client will get an exception if a reply is not received within that time.
I have a Python TCP Socket server service which:
Allows only one client connection at time;
Its inputstream/outputstream operates independently.
On the other side, I have a Java Spring Boot client application using Spring Integration. My actual TCP Socket configurator
implementation uses:
#MessagingGateway(defaultRequestChannel = REQUEST_CHANNEL, errorChannel = ERROR_CHANNEL)
public interface ClientGtw {
Future<Response> send(Request request);
}
#Bean
#ServiceActivator(inputChannel = REQUEST_CHANNEL)
public MessageHandler outboundGateway(TcpNioClientConnectionFactory connectionFactory) {
TcpOutboundGateway gateway = new TcpOutboundGateway();
gateway.setConnectionFactory(connectionFactory);
gateway.setRequestTimeout(TimeUnit.SECONDS.toMillis(timeout));
gateway.setRemoteTimeout(TimeUnit.SECONDS.toMillis(timeout));
return gateway;
}
#Bean
public TcpNioClientConnectionFactory clientConnectionFactory(AppConfig config) {
Host host = getHost(config);
TcpNioClientConnectionFactory factory = new TcpNioClientConnectionFactory(host.name, host.port);
factory.setSingleUse(false);
factory.setSoTimeout((int) TimeUnit.SECONDS.toMillis(timeout));
SerializerDeserializer sd = new SerializerDeserializer();
factory.setDeserializer(sd);
factory.setSerializer(sd);
return factory;
}
This actual approach works fine, however, when a request is sent it hangs the connection until a response is received. This is a problem due the fact that some times a request can get too much time to receive a response and the system has other requests incomming whose response can be achieved faster. I would like to send and receive as much as possible requests and responses independetly (decoupled between them). The object transported (serialized and deserialized) contains a key pair that can do the correct correlation.
TL;DR: How to implement an Asynchronous requests/responses over the same connection?
The Spring TcpOutboundGateway javadoc mentions: Use a pair of outbound/inbound adapters for that use case.
So, in addition to the declaration above:
1st Attempt
#Bean
public TcpInboundGateway inboundGateway(AbstractServerConnectionFactory connectionFactory) {
TcpInboundGateway gateway = new TcpInboundGateway();
gateway.setConnectionFactory(connectionFactory);
gateway.setRequestTimeout(TimeUnit.SECONDS.toMillis(timeout));
return gateway;
}
#Bean
public AbstractServerConnectionFactory serverFactory(AppConfig config) {
Host host = getHost(config);
AbstractServerConnectionFactory connectionFactory = new TcpNetServerConnectionFactory(host.port);
connectionFactory.setSingleUse(true);
connectionFactory.setSoTimeout(timeout);
return connectionFactory;
}
The requests are blocked until a response is delivered as before.
2nd Attempt
#Bean
public TcpInboundGateway inboundGateway(TcpNioClientConnectionFactory connectionFactory) {
TcpInboundGateway gateway = new TcpInboundGateway();
gateway.setConnectionFactory(connectionFactory);
gateway.setRequestTimeout(TimeUnit.SECONDS.toMillis(timeout));
gateway.setClientMode(true);
return gateway;
}
org.springframework.integration.ip.tcp.connection.TcpNioClientConnectionFactory may only be used by one inbound adapter
Any clue?
Use a pair of channel adapters instead of an outbound gateway. Instead of using a MessagingGateway, you can do the correlation yourself in your application, or you can use the same technique as is used in the tcp-client-server-multiplex sample app. It uses an aggregator to aggregate a copy of the outbound message with an inbound message, replying to the gateway.
It's old, and uses XML configuration, but the same techniques apply.
<publish-subscribe-channel id="input" />
<ip:tcp-outbound-channel-adapter id="outAdapter.client"
order="2"
channel="input"
connection-factory="client" /> <!-- Collaborator -->
<!-- Also send a copy to the custom aggregator for correlation and
so this message's replyChannel will be transferred to the
aggregated message.
The order ensures this gets to the aggregator first -->
<bridge input-channel="input" output-channel="toAggregator.client"
order="1"/>
<!-- Asynch receive reply -->
<ip:tcp-inbound-channel-adapter id="inAdapter.client"
channel="toAggregator.client"
connection-factory="client" /> <!-- Collaborator -->
<!-- dataType attribute invokes the conversion service, if necessary -->
<channel id="toAggregator.client" datatype="java.lang.String" />
<aggregator input-channel="toAggregator.client"
output-channel="toTransformer.client"
expire-groups-upon-completion="true"
expire-groups-upon-timeout="true"
discard-channel="noResponseChannel"
group-timeout="1000"
correlation-strategy-expression="payload.substring(0,3)"
release-strategy-expression="size() == 2" />
<channel id="noResponseChannel" />
<service-activator input-channel="noResponseChannel" ref="echoService" method="noResponse" />
<transformer input-channel="toTransformer.client"
expression="payload.get(1)"/> <!-- The response is always second -->
(This simple sample correlates on the first 3 bytes).
Gary, thanks for your guidance.
To solve this issue is important to first understand Messaging Channel types.
So, in the configurer class:
#Bean(name = REQUEST_CHANNEL)
public DirectChannel sender() {
return new DirectChannel();
}
#Bean(name = RESPONSE_CHANNEL)
public PollableChannel receiver() {
return new QueueChannel();
}
#Bean
#ServiceActivator(inputChannel = REQUEST_CHANNEL)
public TcpSendingMessageHandler outboundClient(TcpNioClientConnectionFactory connectionFactory) {
TcpSendingMessageHandler outbound = new TcpSendingMessageHandler();
outbound.setConnectionFactory(connectionFactory);
outbound.setRetryInterval(TimeUnit.SECONDS.toMillis(timeout));
outbound.setClientMode(true);
return outbound;
}
#Bean
public TcpReceivingChannelAdapter inboundClient(TcpNioClientConnectionFactory connectionFactory) {
TcpReceivingChannelAdapter inbound = new TcpReceivingChannelAdapter();
inbound.setConnectionFactory(connectionFactory);
inbound.setRetryInterval(TimeUnit.SECONDS.toMillis(timeout));
inbound.setOutputChannel(receiver());
inbound.setClientMode(true);
return inbound;
}
This scratch #Singleton class illustrates how to operate the requests and responses (considering that requests and responses contains a UID to correlate them):
#Autowired
private DirectChannel sender;
#Autowired
private PollableChannel receiver;
private BlockingQueue<Request> requestPool = new LinkedBlockingQueue<>();
private Map<String, Response> responsePool = Collections.synchronizedMap(new HashMap<>());
#PostConstruct
private void init() {
new Receiver().start();
new Sender().start();
}
/*
* It can be called as many as necessary without hanging for a response
*/
public void send(Request req) {
requestPool.add(req);
}
/*
* Check for a response until a socket timout
*/
public Response receive(String key) {
Response res = responsePool.get(key);
if (res != null) {
responsePool.remove(key);
}
return res;
}
private class Receiver extends Thread {
#Override
public void run() {
while (true) {
try {
tcpReceive();
Thread.sleep(250);
} catch (InterruptedException e) { }
}
}
private void tcpReceive() {
Response res = (Message<Response>) receiver.receive();
if (res != null) {
responsePool.put(res.getUID(), res);
}
}
}
private class Sender extends Thread {
#Override
public void run() {
while (true) {
try {
tcpSend();
Thread.sleep(250);
} catch (InterruptedException e) { }
}
}
private void tcpSend() {
Request req = requestPool.poll(125, TimeUnit.MILLISECONDS);
if (req != null) {
sender.send(MessageBuilder.withPayload(req).build());
}
}
}
UPDATED
I forgot to mention this:
#Bean
public TcpNioClientConnectionFactory clientConnectionFactory(Config config) {
// Get host properties
Host host = getHost(config);
// Create socket factory
TcpNioClientConnectionFactory factory = new TcpNioClientConnectionFactory(host.name, host.port);
factory.setSingleUse(false); // IMPORTANT FOR SINGLE CHANNEL
factory.setSoTimeout((int) TimeUnit.SECONDS.toMillis(timeout));
return factory;
}
Feel free to make any considerations.