How to make my Java SpringBoot application to proxy MQTT Broker WebSocket 8083 port? - spring

Let's suppose we have an Spring Boot application called A. And we have a MQTT Broker that supports MQTT WebSocket Service.
The device used to connect directly to the MQTT Broker's 8083 port to get MQTT WebSocket Service. But now I want the device to connect to the A's 80 port to get the same MQTT WebSocket Service. For example, the device connect to the ws://A_HOST:80/mqtt, it should work the same as it connect to ws://MQTT_BROKER_HOST:8083/mqtt.
My solution is to use the org.springframework.web.reactive.socket.WebSocketHandler in A. When device connect to A, I would use org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient in A to connect to MQTT Broker at the same time. Whatever data the device sends to A, the A should send the same data to the MQTT Broker. And Whatever the data MQTT Broker responses(or sends), the A should send the same data to device. I think the solution can implement my needs.
I am using WebFlux.
I write the code below, but unfortunately it doesn't work:
public class ReactiveWebSocketHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession serverSession) {
WebSocketClient client = new ReactorNettyWebSocketClient();
client.execute(
URI.create("ws://xxx.0.0.1:8083/mqtt"), // xxx.0.0.1:8083 is the MQTT Broker Address
new WebSocketHandler() {
#Override
public List<String> getSubProtocols() {
return Collections.singletonList("mqtt");
}
#Override
public Mono<Void> handle(WebSocketSession clientSession) {
return clientSession.send(serverSession.receive()) // Whatever data the device sends to A, the A should send the same data to the MQTT Broker
.and(
serverSession.send(clientSession.receive())); // Whatever the data MQTT Broker responses(or sends), the A should send the same data to device
}
});
return Mono.empty(); // Since I have written the logic above, I don't know what should I write here
}
#Override
public List<String> getSubProtocols() {
return Collections.singletonList("mqtt");
}
}
Could you tell me how to write the code that can implements such logic? Thanks!

Related

Spring Integration - Multicast UDP message not updating

I try to listen periodic udp message with Multicast ip with spring integration, but my code get the same udp message all the time even if the udp message updated. When I stop my program and restart it, the message updates.
Here is my config config.java:
#Bean
public IntegrationFlow udpIn() {
return IntegrationFlows.from(Udp.inboundMulticastAdapter(16343, "239.0.12.1"))
.channel("inboundChannel")
.get();
}
and here is the method handle messages service.java:
#ServiceActivator(inputChannel = "inboundChannel")
public void handleMessage(Message message) {
log.info("message.getPayload());
byte[] values = (byte[]) message.getPayload();
//some irrelevant code
}
where is wrong about the code?
Thanks...

How do I throttle the amount of data sent to Stomp queue (handling websockets) so that I can guarantee that I don't overflow the buffer?

I have two Java processes and I am connecting them using a websocket in spring boot. One process acts as the client and connects like this:
List<Transport> transports = new ArrayList<Transport>(1);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
WebSocketClient client = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
StompSessionHandler firstSessionHandler = new MyStompSessionHandler("Philip");
stompClient.connect("ws://localhost:8080/chat", firstSessionHandler);
The session handler extends StompSessionHandlerAdapter and provides these methods (I am subscribing by username so each client can receive its own messages):
#Override
public void afterConnected(
StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/user/" + userName + "/reply", this);
session.send("/app/chat", getSampleMessage());
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
Message msg = (Message) payload;
// etc.....
}
On the server side I have a Controller exposed and I am writing data by calling the endpoint from a worker thread.
#Autowired
private SimpMessagingTemplate template;
#MessageMapping("/chat")
public void send(
Message message)
throws Exception {
template.convertAndSendToUser(message.getFrom(),
"/reply",
message);
}
In the websocket config I am overriding the method to set the limits:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/user");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(500 * 1024);
registration.setSendBufferSizeLimit(1024 * 1024);
registration.setSendTimeLimit(20000);
}
My question is this, if the load on the server gets high enough and I overrun the limit, the websocket fails catastrophically, and I want to avoid this. What I would like to do is for the controller to have the ability to ask the message broker "will this message fit in the buffer?", so that I can throttle to stay under the limit. I searched the API documentation but I don't see any way of doing that. Are there any other obvious solutions that I am missing?
Thanks.
Actually I found a solution, so if anyone is interested, here it is.
On the server side configuration of the websockets I installed an Interceptor on the Outbound Channel (this is part of the API), which is called after each send from the embedded broker.
So I know how much is coming in, which I keep track of in my Controller class and I know how much is going out through the Interceptor that I installed, and this allows me to always stay under the limit.
The controller, before accepting any new messages to be queued up for the broker first determines if enough room is available and if not queues up the message in external storage until such time as room becomes available.

Spring Integration TcpInboundGateway Read exception resulting in SocketException:Connection reset

I am using spring boot as per examples for TcpInboundGateway,so different devices send data to this Gateways,things works fine but in between in logs it showing following exception:
2015-12-29 18:42:19.455 ERROR 3465 --- [ool-3-thread-47] o.s.i.i.tcp.connection.TcpNetConnection : Read exception 106.221.159.216:38170:8765:934c050d-c4b5-4466-98ab-ee87714c3d00 SocketException:Connection reset
If this exception is resetting connection then how to avoid this reset?What is the cause of this error?
My code as follows
#SpringBootApplication
#IntegrationComponentScan
public class SpringIntegrationApplication extends SpringBootServletInitializer{
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext ctx = SpringApplication.run(SpringIntegrationApplication.class, args);
System.in.read();
ctx.close();
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringIntegrationApplication.class);
}
private static Class<SpringIntegrationApplication> applicationClass = SpringIntegrationApplication.class;
#Bean
TcpNetServerConnectionFactory cf(){
TcpNetServerConnectionFactory connectionFactory=new TcpNetServerConnectionFactory(8765);
return connectionFactory;
}
#Bean
TcpInboundGateway tcpGate(){
TcpInboundGateway gateway=new TcpInboundGateway();
gateway.setConnectionFactory(cf());
gateway.setRequestChannel(requestChannel());
return gateway;
}
#Bean
public MessageChannel requestChannel(){
return new DirectChannel();
}
#MessageEndpoint
public class Echo {
#ServiceActivator(inputChannel="requestChannel")
public byte[] echo(byte[] in,#SuppressWarnings("deprecation") #Header("ip_address") String ip){
byte[] rawbytes = gosDataSerivce.byteArrayToHex(in,ip);//Process bytes and returns result
return rawbytes;
}
}
}
After setting singleUse to true now exception message is changed slightly.
2015-12-31 06:09:00.481 ERROR 16450 --- [ool-3-thread-10] o.s.i.i.tcp.connection.TcpNetConnection : Read exception 106.221.146.40:9195:8765:1b4755e8-5b0c-44b9-b4e6-b3aacc25e228 SocketException:Connection reset
Use Case:
I have several clients that established GPRS connection to TcpInboundGateWay and sends login packet,our server will reply to this login packet.If client receives server reply to login packet then it will send data packets at regular interval. Server needs to reply to these packet also if server fails to send reply to those data packets then client GPRS connection is terminated and client will try to establish connections again.Let me know if this use case can be handle with TcpInboundGateWay
Network Trace Analysis
General flow of communication between client and server is as follows:Client sends login packet from ip say 106.221.148.165 so at server connection named 106.221.148.165:63430:8765:cc105da2-dae4-494b-af9c-d1ba268f34f1 is created, that client sends subsequent packets from that ip only.So everything works fine,but after some time same client sends its login packet from another ip say 106.221.142.204.And subsequent packets from new ip.But in logs following error comes that for previous connection exception occurred.
2016-01-05 05:16:14.871 ERROR 6819 --- [pool-3-thread-5] o.s.i.i.tcp.connection.TcpNetConnection : Read exception 106.221.148.165:63430:8765:cc105da2-dae4-494b-af9c-d1ba268f34f1 SocketException:Connection reset
I have set singleUse true and I am using spring integration 4.2.1
This message is emitted when the client closes the socket - if your client only sends one message then closes the socket, you can set singleUse to true and it will suppress this message (as long as the socket is closed normally - between messages).
With Spring Integration version 4.2 and later, the message is not emitted on a normal close, even if singleUse is false.

Spring websocket Client to Client communication

I have a requirement where my Websocket session should be able to communicate with each other.I am creating a Request Response model where my Client A would send a request on a Queue on which I have multiple subscriber agents (Ag1 and Ag2). I would expect that my requests would round robin between these 2 subscribers. Unfortunately, the event is broadcasted to both the agents rather than it being a one to one communication.
My Spring config
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/queue", "/topic");
}
Client JS Code
requestResponse = new RequestResponse({
outgoingChannel : "/queue/clients",
incomingChannel : "/topic/broadcast/clients",
callbackFn : widget3eventHandler
},session);
Agent Subscriber Code
requestResponse = new RequestResponse({
outgoingChannel : "/topic/broadcast/clients",
incomingChannel : "/queue/clients",
callbackFn : widget3eventHandler,
processAll : true
},session);
Is this a bug in SIMP Broker or am i doing something wrong.
You can check this sample chat application if you want to know how to achieve client to client communication.

Check auth while sending a message to a specific user by using STOMP and WebSocket in Spring

I'm developing a realtime notification system in Spring 4 by using a build-in Message Broker, and STOMP over WebSocket.
I would like to be able to send messages to a specific user, according with his username.
In order to achieve this goal, I'm using the convertAndSendToUser method of org.springframework.messaging.simp.SimpMessagingTemplate class, as follows:
private final MessagingTemplate messagingTemplate;
#Autowired
public LRTStatusListener(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#Scheduled(fixedDelay=5000)
public void sendMessages(Principal principal)
messagingTemplate
.convertAndSendToUser(principal.getName(), "/horray", "Horray, " + principal.getName() + "!");
}
As configuration:
#Configuration
#EnableScheduling
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/notifications").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue", "/user");
}
}
Client-side (via JavaScript), I should subscribe to a channel by specifing the username (according with another very similar question: Sending message to specific user on Spring Websocket).
stompClient.subscribe('/user/' + username + '/horray, ...)
This last point sounds weird...
Supposing that I'm logged as w.white on my webapp, by subscribing:
stompClient.subscribe('/user/w.white/horray, ...)
... I will be able to see messages sent to w.white, and this is awesome... But subscribing:
stompClient.subscribe('/user/j.pinkman/horray, ...)
... I will be able to see also messages sent to j.pinkman, despide that I'm currently logged as w.white.
It is a way to overcome this problem?
Update
Below there is the log about the connection over WebSocket:
Opening Web Socket...
Web Socket Opened...
>>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000
<<< CONNECTED
user-name:w.white
heart-beat:0,0
version:1.1
connected to server undefined
Connected: CONNECTED
version:1.1
heart-beat:0,0
user-name:w.white
>>> SUBSCRIBE
id:sub-0
destination:/topic/lrt
>>> SUBSCRIBE
id:sub-1
destination:/user/lrt
I found the solution.
First of all, it is important to know that the /user channel is already managed by Spring STOMP, and by the way, no registration is required.
So:
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
}
Then, I setup the destination channel as /queue/horray:
#Scheduled(fixedDelay=5000)
public void sendMessages(Principal principal)
messagingTemplate
.convertAndSendToUser(principal.getName(), "/queue/horray", "Horray, " + principal.getName() + "!");
}
At last, on client:
stompClient.subscribe('/user/queue/horray', '...');
Now, it works fine! Messages are sent only to the specified recipient, according to the Principal fetched by the security context.
Since users on my application are not authenticated I just used the session Id to differenciate the various topics
on the server:
template.convertAndSend("/topic/warnings/" + sessionId, ...)
And the client is pretty straightforward
stompClient.subscribe('/topic/warnings/${pageContext.session.id}', ...
Maybe not the cleanest way but it works, and without authentication I couldn't make use of /user channel

Resources