I am attempting to create a microservice which acts as both server and client for testing purposes.
My client-side will connect as client-mode connected to a remote microservice which connects at the same time with my server-side, so I can send messages being both client or server and get their replies.
Client side of μs1 <-> Server side of μs2 <-> Client side of μs2 <-> Server side of μs1
I have tried to make an incoming and outgoing integration flow for each side (see the client one below) with TcpSendingMessageHandler and TcpReceivingChannelAdapter but it's not possible to retrieve the reply sent by their counterparts as they are one-way component and don't wait for any replies to produce to the replyChannel header for my TcpClientGateway, so there is not response back.
#Bean
public IntegrationFlow incomingClient(final TcpReceivingChannelAdapter tcpReceivingChannelAdapter,
TcpServerEndpoint tcpServerEndpoint) {
return IntegrationFlows
.from(tcpReceivingChannelAdapter)
.handle(message -> { LOGGER.info("RECEIVING ON CLIENT: {}", tcpServerEndpoint.processMessage((byte[]) message.getPayload()));})
.get();
}
#Bean
public IntegrationFlow outgoingClient(final MessageChannel outboundChannel, final TcpSendingMessageHandler tcpSendingClientMessageHandler) {
return IntegrationFlows
.from(outboundChannel)
.handle(tcpSendingClientMessageHandler)
.get();
}
As far as I know i need to use TcpInboundGateway and TcpOutboundGateway components as they can manage these replies I need to get.
¿How could I implement this so once each side is connected with each other I can start sending a message with my server side and get the reply? ¿Is it possible to send a message being a server with an InboundGateway?
I need to send messages from any side of this flow no matter who starts the communication because it could be anyone.
Thanks.
You can't do that with gateways (unless you have two sets); for arbitrary peer to peer communication over a single connection, you have to use collaborating channel adapters. https://docs.spring.io/spring-integration/docs/current/reference/html/ip.html#ip-collaborating-adapters
When you receive a message, you will need to decide if it's a request or a reply. If it's a reply, you can send it to an aggregator, where you previously sent a copy of the request.
You will need some mechanism to correlate replies to requests; since TCP has no concept of a header, it would have to be something in the data.
There's a partial solution in the samples repo https://github.com/spring-projects/spring-integration-samples/tree/main/intermediate/tcp-client-server-multiplex - it is old and uses XML configuration, but the concepts are the same.
Related
I am using Spring to setup Stomp server endpoints (extending AbstractWebSocketMessageBrokerConfigurer)
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableStompBrokerRelay("/topic","/queue")
.setRelayHost(<rmqhost>);
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/myapp/websockets").setAllowedOrigins("*");
}
The objective is that I can have multiple servers, and a client will connect to any one of them for a specific topic: /topic/topic-id-1
Any of the server (at a time) can send a message for this topic using Spring's SimpMessagingTemplate
messagingTemplate.convertAndSend(destination, message);
where destination = "/topic/topic-id-1".
For ex: I have 2 server nodes and a client connecting to each one of them, subscribing to the same topic (/topic/topic-id-1). The objective is that if server 1 sends a message for topic-id-1, it should relay via rabbitmq to both clients subscribing to the same topic. I see a queue being created with routing key as "topic-id-1", but only the client connecting to the server sending out the message explicitly receives it. Am I missing something here? Isn't RMQ stomp broker supposed to relay the message send by one server for a subscription, across all the subscriptions for the same topic? Does the server need to do something else to get messages sent by other node?
I met the same problem. After a whole day explored, I found the solution finally!! It's easy to configure though.
registry.enableStompBrokerRelay("/topic/", "/queue/", "/exchange/")
.setUserDestinationBroadcast("/topic/log-unresolved-user")
.setUserRegistryBroadcast("/topic/log-user-registry")
The only thing you need to do is configure setUserDestinationBroadcast and setUserRegistryBroadcast when you enable the StompBrokerRelay. And it works!
I found the solution from here. Thinks that guy!
I'm not sure if this is exactly the same thing but I just solved a very similar problem. I posted my answer here: Sending STOMP messages from other layers of an application
I decided to split the implementation of the relay server into it's own setup and then manually forward messages between the rabbitmq server and the websocket subscribers on each of the servers.
Hopefully this can be of some use for you.
I am using spring-amqp 1.4.4 and after queue contains too much messages and it is above watermark memory, RabbitTemplate receive method don't response if it was called after send method. It is wait indefinitely. And in spring xml I set reply-timeout="10" to rabbit:template. If i not call send method and simply call receive it work good. What's wrong?
template.convertAndSend("test message");
String msg = (String) template.receiveAndConvert("log.queue"); // receiveAndConvert not response
The rabbitmq guys recommend using separate connections for publishers and consumers, for exactly this reason.
The spring amqp CachingConnectionFactory shares a single connection for all users.
We are looking at providing an option to use two connections but, in the meantime, you can configure two connection factories (and templates), one for sends and the other for receives.
I am using spring/stomp/websocket framework to notify users of messages asynchronously. I have done this successfully. However, I would be get ACK from the client so that some server side action can take place when this is done.
The flow is roughly as flows:
Service notifies a specific user about a decision and updates a record in the DB with status = "notified"
Client receives the message (using stompClient.subscribe(...))
Client acknowledges that the message was received.
The service "knows" that this message was acknowledged and updates the status to "ACK" in the DB.
stompClient.connect({login:'guest', passcode:'guest'},
function(frame) {
setConnected(true);
**var headers = {ack: 'client'};**
...
stompClient.subscribe('/user/guest/response',function(notification) {
//doSomething
}), **headers**);
}
In the service, the message is sent:
this.messagingTemplate.convertAndSendToUser(user, "/response",msg, map);
Is there a way to handle the client ACK on the server side?
Alternatively, I tried to do a
stompClient.send("/app/response/ack/"+messageId);
on the client, in the method that handles the subscription, but in vain.
Can someone please tell me what is standard way to handle acknowledgments? I have been struggling with this for a a couple of days and any thoughts would be very helpful.
Thanks!
Use the ACK frame as per spec. The server sends an ack:some_id header, the client uses that some_id in the ACK frame.
The answer is no for simple broker.
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html
The simple broker is great for getting started but supports only a
subset of STOMP commands (e.g. no acks, receipts, etc.), relies on a
simple message sending loop, and is not suitable for clustering. As an
alternative, applications can upgrade to using a full-featured message
broker.
I need suggestion how to implement, if it is possible, with the Spring integration the following TCP flow:
Only the server side is need.
The TCP server waits for the incoming connection
On connection of the client, server sends data to the client
Client replies with response
Server may reply immediately with the new data or wait for external application events to send new packages to the client.
In groovy the code could be demonstrated as follow:
def serverSocket = new ServerSocket(...)
def connSocket = serverSocket.accept()
connSocket.outputStream.write(...)
while(true) {
def readBuffer = new byte[256]
connSocket.inputStream.read(readBuffer)
if(needToSendBack(readBuffer)) {
connSocket.outputStream.write(...)
}
}
def sendByDemand(def data) {
connSocket.outputStream.write(data)
}
The method sendByDemand could be invoked from the separate thread.
Here is a list of problems which I marked for myself, which prevents me to implement it with the Spring Integration (2.x version):
As far as I understand, the standard "Service Activator" approach cannot work in this scenario, since it is "connection events" driven. So when the application decides to send the new data to the client it cannot use the Service Activator
I have no "On TCP connection" event. I found that version 3.0 comes with the events support in this area, but since I cannot upgrade to 3.0, I implemented the connection check with the interceptors on the connection factory. However, when I know that client is connected, trying using the Direct Channels to send message fails with "no subscribers" error.
If someone could post possible Spring configuration for this scenario or point to the similar flow example it may be very helpful.
Your use case is possible, but it would make your life easier if you could upgrade to 3.0.
'Dispatcher has no subscribers' means there is no consumer subscribed to that channel.
You need to show your configuration; you must use collaborating channel adapters for this (not a gateway).
You need to capture the connectionId of the connection when it is established, and use it to populate the ip_connectionId header so the outbound channel adapter knows which socket to which to write the message.
I'm designing a distributed network of sensing device. These devices will generate logs and send them to a central database. I'm using JMS for transporting log messages. The main database server will be running MDB(Message Driven Bean) to process incoming messages. The clients are sending data with GPRS. The problem is I don't want my clients to process network problems. I need some relay service that runs locally on client machine and gets the message from client immediately without blocking it and try on behalf of it.(if network is down, try sending again after some time).
message is a simple java object:
public class Message {
public int x;
public int y;
public int z;
}
client:
Message msg = new Message();
while (True) {
/* sense data */
msg = get_data_from_environment();
/* send data to local relay service
* This is non blocking call */
relay_service.send(msg);
}
local relay service:
while (True) {
/* get message from local client */
msg = get_message_from_local_client();
result = send_msg_to_JMS_server(msg);
/* if not successful, persist it on a local queue and try some other time */
if (result.is_sent() != True)
store_msg_on_disk(msg);
}
Is there a message service like this or I should write relay service myself?
is this good to use JMS in this case? Should I design my own socket level protocol to send messages?
EDIT
Is there a message service like this or I should write relay service myself?
Typically these type of relay services you have to code your self, unless you are able to find a software that does exactly what you want it to do. This is not unusual to be done in these cases.
Is this good to use JMS in this case?
Yes, JMS is a very good solution to use as a middleware. You can have many clients connect to JMS and send messages to it. While you have a server program running reading the messages off the JMS and processing it and handling network problems if there are any. Also as a bonus the server program and send back messages to the client in case of complete failure.
Should I design my own socket level protocol to send messages?
I still do not know what kind of messages you want to send. If you are using a standard transport like SMTP or SMS or HTTP or something like that, there are libraries to help you send and verify delivery. If you have to send using a custom protocol then you would have to write you own socket level code.
Seeing your code examples shows me that you want to know if your client was successful in sending his message to the JMS. If it was not sent then save to disk and try again later.
JMS server will auto-acknowledge if the message received. You can check this from the JMS message or if it fails you will get a JMSException. If you save messages on disk you will need to know when to re-send them. You would need a timer or re-send on next message to send.