I am working with Spring Boot and Redis server for my web application. When a client sends a post, a message is sent to Redis on channel "chat". This works well. For recieving messages I created the class RedisMessageSubscriber, which recieves messages from Redis.
All clients have subscribed the STOMP Websocket client "topic/test".
My question is: How can I redirect the message I recieved in RedisMessageSubscriber to the client Websocket channel?
My first try below didn't worked, because the controller sent a message from the queue to the client before the message received from Redis.
DwittrController.class (Messages are sent to clients here):
#MessageMapping("/newPost")
#SendTo("/topic/test")
public Message message(ClientMessage message) {
log.debug("a message from client was recieved");
userRepository.sendMessage(message.getMessage());
String redisMessage = RedisMessageSubscriber.messageList.get(0);
RedisMessageSubscriber.messageList.remove(0);
return new Message("New post from " + redisMessage + " is available.");
}
UserRepositoryImpl.class:
public void sendMessage(String message) {
stringRedisTemplate.convertAndSend("chat", message);
}
RedisMessageSubscriber.class:
#Component
public class RedisMessageSubscriber implements MessageListener {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public static List<String> messageList = new ArrayList<String>();
public void onMessage(Message message, byte[] pattern) {
messageList.add(message.toString());
log.debug("Message received: " + message.toString());
}
}
In your RedisMessageSubscriber class you can autowire a SimpMessagingTemplate. Code example is not precise, but you get the idea.
#Component
public class RedisMessageSubscriber implements MessageListener {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public static List<String> messageList = new ArrayList<String>();
#Autowired
SimpMessagingTemplate messagingTemplate;
public void onMessage(Message message, byte[] pattern) {
messageList.add(message.toString());
log.debug("Message received: " + message.toString());
messagingTemplate.convertAndSend( "/topic/test", message);
}
}
Related
Im writing back front java code spring 2.2.5. The front is connected to the back via a websocket. I want to send notifications to the client without request sent by client only connection and subscrition events are received by the server.
I tried some solotion based on SimpMessagingTemplate but when i'm trying to send back to the client using this interface (without request) the data is null pointer.
Controller code :
private SimpMessagingTemplate messagingTemplate;
#Autowired
public void WebSocketController(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#RequestMapping(value = "/post/message", method = RequestMethod.POST)
public void PostMessage(#PathVariable String message) {
this.messagingTemplate.convertAndSend("/topic/myDest", message);
}
With the following config code i intercept connect and subscribe events, is there a way to save client parameter in order to send back notifications to it.
#Override
public void configureClientInboundChannel(ChannelRegistration registration){
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if(StompCommand.CONNECT.equals(accessor.getCommand())){
String sessionId = accessor.getSessionId();
System.out.println("Connect " + sessionId);
} else if(StompCommand.SUBSCRIBE.equals(accessor.getCommand())){
System.out.println("Subscribe ");
} else if(StompCommand.SEND.equals(accessor.getCommand())){
System.out.println("Send message " );
} else if(StompCommand.DISCONNECT.equals(accessor.getCommand())){
System.out.println("Exit ");
} else {
}
return message;
}
});
}
I finally find the solution: In my previous PreSend interceptor, i can save all subscribed clients :
xxxx.Channels.add(channel);
In xxx class : Channels is defined as :
public final ArrayList<MessageChannel> Channels = new ArrayList<MessageChannel>();
In Post function i created a SimpMessagingTemplate instance for each channel :
for (int i = 0; i < Channels.size(); i++) {
MessageChannel channel = Channels.get(i);
SimpMessagingTemplate messagingTemplate = new SimpMessagingTemplate(channel);
messagingTemplate.convertAndSend("/topic/myDest", data.toString().getBytes());
}
And the message is correctly sent.
I have Spring boot app
It is receiving Json formatted messages from ActiveMQ
#Component class Receiver {
#JmsListener(destination = "queue")
public void receiveMessage(BusMessage message) {
System.out.println("Received <" + message + ">");
}
The problem, is what sometimes Json can arrive without proper header
In this case i get exception
org.springframework.messaging.converter.MessageConversionException: Cannot convert from [java.lang.String] to [cam.melexis.minipcs2socketioserver.BusMessage] for org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#38bd4ff9, failedMessage=org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#38bd4ff9
One solution can be is to get message as string and deserialise it
#Component class Receiver {
#JmsListener(destination = "queue")
public void receiveScannerMessage(Message message, Session session) {
System.out.println("Received <" + message + ">");
}
But i want to do something more graceful
To simplify all #JmsListener
And to make my learning curve in Spring more curved :)
Knowing, what all messages will arrive in Json format, can i add missing header to all incoming messages?
Or it is another "better" way exist?
This made a job
#EnableJms
#Configuration class JmsListenerConfig implements JmsListenerConfigurer {
Logger logger = LoggerFactory.getLogger(Receiver.class);
#Bean
public DefaultMessageHandlerMethodFactory handlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(messageConverter());
return factory;
}
#Bean
public MessageConverter messageConverter() {
return new MappingJackson2MessageConverter();
}
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(handlerMethodFactory());
}
}
But i have no idea how
Maybe anybody can help me with this?
For refreshing configuration dynamically(without restart) on client with Spring Cloud Bus and kafka(spring cloud bus). In kafka, it seems pub/sub works normally from console. But the listener of client never be invoked.
output of kafka command: bin/kafka-console-consumer.sh --bootstrap-server host:9092 --topic springCloudBus --from-beginning
contentType
"text/plain"originalContentType "application/json;charset=UTF-8"{"type":"MyCustomRemoteEvent","timestamp":1524625355729,"originService":"application:db:8090","destinationService":"client:**","id":"9cb283d1-dc2f-4e63-80aa-03c9f52f773f","message":"<debug info>"}
contentType
"text/plain"originalContentType "application/json;charset=UTF-8"{"type":"AckRemoteApplicationEvent","timestamp":1524625355786,"originService":"client:8888","destinationService":"**","id":"dc4e42ee-ffba-427c-9c12-b493b262b29c","ackId":"9cb283d1-dc2f-4e63-80aa-03c9f52f773f","ackDestinationService":"client:**","event":"com.ifly.icourt.event.listener.MyCustomRemoteEvent"}
server project, publish message
String myUniqueId = context.getId();
// send message to service named "client"
MyCustomRemoteEvent event = new MyCustomRemoteEvent(this, myUniqueId, "client", "<debug info>");
context.publishEvent(event);
client project, subscribe message, application.properties
# specify application context id
spring.application.name=client
event
#SuppressWarnings("serial")
public class MyCustomRemoteEvent extends RemoteApplicationEvent {
private String message;
public MyCustomRemoteEvent() {
}
public MyCustomRemoteEvent(Object source, String originService, String destinationService, String message) {
super(source, originService, destinationService);
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Why is the listener never invoked?
#Component
public class NewListener implements ApplicationListener<MyCustomRemoteEvent> {
private static final Logger logger = LoggerFactory.getLogger(NewListener.class);
#Override
public void onApplicationEvent(MyCustomRemoteEvent event) {
System.out.println("<debug info> listener is invoked");
String message = event.getMessage();
logger.info("<debug info> get message: " + message);
}
}
Add
After adding eureka, listener works well and can be invoked. Is eureka necessary to make Spring Cloud Bus and kafka work?
The Spring framework support tcp connection as well , i wrote code below to setup a simple socket server , i am confused about adding below futures to my socket server :
authorizing clients based on a unique identifier ( for example a client secret received from client, maybe using TCP Connection Events )
send a message directly to specific client (based on identifier)
broadcast a message
UPDATE :
Config.sendMessage added to send message to single client
Config.broadCast added to broadcast message
authorizeIncomingConnection to authorize clients , accept or reject connections
tcpConnections static filed added to keep tcpEvent sources
Questions !
is using tcpConnections HashMap good idea ?!
is the authorization method i implemented a good one ?!
Main.java
#SpringBootApplication
public class Main {
public static void main(final String[] args) {
SpringApplication.run(Main.class, args);
}
}
Config.java
#EnableIntegration
#IntegrationComponentScan
#Configuration
public class Config implements ApplicationListener<TcpConnectionEvent> {
private static final Logger LOGGER = Logger.getLogger(Config.class.getName());
#Bean
public AbstractServerConnectionFactory AbstractServerConnectionFactory() {
return new TcpNetServerConnectionFactory(8181);
}
#Bean
public TcpInboundGateway TcpInboundGateway(AbstractServerConnectionFactory connectionFactory) {
TcpInboundGateway inGate = new TcpInboundGateway();
inGate.setConnectionFactory(connectionFactory);
inGate.setRequestChannel(getMessageChannel());
return inGate;
}
#Bean
public MessageChannel getMessageChannel() {
return new DirectChannel();
}
#MessageEndpoint
public class Echo {
#Transformer(inputChannel = "getMessageChannel")
public String convert(byte[] bytes) throws Exception {
return new String(bytes);
}
}
private static ConcurrentHashMap<String, TcpConnection> tcpConnections = new ConcurrentHashMap<>();
#Override
public void onApplicationEvent(TcpConnectionEvent tcpEvent) {
TcpConnection source = (TcpConnection) tcpEvent.getSource();
if (tcpEvent instanceof TcpConnectionOpenEvent) {
LOGGER.info("Socket Opened " + source.getConnectionId());
tcpConnections.put(tcpEvent.getConnectionId(), source);
if (!authorizeIncomingConnection(source.getSocketInfo())) {
LOGGER.warn("Socket Rejected " + source.getConnectionId());
source.close();
}
} else if (tcpEvent instanceof TcpConnectionCloseEvent) {
LOGGER.info("Socket Closed " + source.getConnectionId());
tcpConnections.remove(source.getConnectionId());
}
}
private boolean authorizeIncomingConnection(SocketInfo socketInfo) {
//Authorization Logic , Like Ip,Mac Address WhiteList or anyThing else !
return (System.currentTimeMillis() / 1000) % 2 == 0;
}
public static String broadCast(String message) {
Set<String> connectionIds = tcpConnections.keySet();
int successCounter = 0;
int FailureCounter = 0;
for (String connectionId : connectionIds) {
try {
sendMessage(connectionId, message);
successCounter++;
} catch (Exception e) {
FailureCounter++;
}
}
return "BroadCast Result , Success : " + successCounter + " Failure : " + FailureCounter;
}
public static void sendMessage(String connectionId, final String message) throws Exception {
tcpConnections.get(connectionId).send(new Message<String>() {
#Override
public String getPayload() {
return message;
}
#Override
public MessageHeaders getHeaders() {
return null;
}
});
}
}
MainController.java
#Controller
public class MainController {
#RequestMapping("/notify/{connectionId}/{message}")
#ResponseBody
public String home(#PathVariable String connectionId, #PathVariable String message) {
try {
Config.sendMessage(connectionId, message);
return "Client Notified !";
} catch (Exception e) {
return "Failed To Notify Client , cause : \n " + e.toString();
}
}
#RequestMapping("/broadCast/{message}")
#ResponseBody
public String home(#PathVariable String message) {
return Config.broadCast(message);
}
}
Usage :
Socket Request/Response Mode
notify single client
http://localhost:8080/notify/{connectionId}/{message}
broadCast
http://localhost:8080/broadCast/{message}
The TcpConnectionOpenEvent contains a connectionId property. Each message coming from that client will have the same property in the IpHeaders.CONNECTION_ID message header.
Add a custom router that keeps track of the logged-on state of each connection.
Lookup the connection id and if not authenticated, route to a challenge/response subflow.
When authenticated, route to the normal flow.
To use arbitrary messaging (rather than request/response) use a TcpReceivingChannelAdapter and TcpSendingMessageHandler instead of an inbound gateway. Both configured to use the same connection factory. For each message sent to the message handler, add the IpHeaders.CONNECTION_ID header to target the specific client.
To broadcast, send a message for each connection id.
I can't get the Spring Message Sample to work in Unit tests using the SimpMessagingTemplate to send messages to the Endpoints.
I followed the instructions here:
https://spring.io/guides/gs/messaging-stomp-websocket/
So far my Controller looks like:
#Data #NoArgsConstructor #AllArgsConstructor
public static class Message {
private Long id;
private String value;
private long time;
}
#MessageMapping("/message")
#SendTo("/topic/response")
public Message slowEndpont(Message message) throws Exception {
Thread.sleep(3000); // simulated delay
System.err.println("Message Received: " + message);
return new Message(message.id, "Hello Client", System.currentTimeMillis());
}
My Unit Test now tries to send a message:
#Autowired
SimpMessagingTemplate messageTemplate;
#Test
public void sendMessage() throws Exception {
System.err.println("** Sending messages...");
messageTemplate.convertAndSend("/app/message",
new MessageController.Message(1L, "Hello Server", System.currentTimeMillis()));
messageTemplate.convertAndSend("/topic/message",
new MessageController.Message(1L, "Hello Server", System.currentTimeMillis()));
messageTemplate.convertAndSend("/queue/message",
new MessageController.Message(1L, "Hello Server", System.currentTimeMillis()));
messageTemplate.convertAndSend("/message",
new MessageController.Message(1L, "Hello Server", System.currentTimeMillis()));
System.err.println("** Messages send!");
Thread.sleep(1500);
}
Full Code sample is here:
https://github.com/puel/training/tree/master/messaging
So far so good. Messages are all send. But never received. I traced it down and the registry of the MessageTemplate is empty. But why?
This problem seems to be close too:
Send Message to all clients via SimpMessagingTemplate in ServletContextListener
But using MessageSendingOperations doesn't help either.
Thanks,
Paul