The following situation currently needs to be resolved:
On the one hand, I have an Angular app that connects to my Java application via WebSocket. I have already implemented this by creating a *.war Maven project in which a service endpoint is defined (see Code sample).
#ServerEndpoint("/web/alarm")
public class WebAlarmSocket {
private static final Logger LOG = LogManager.getLogger(WebAlarmSocket.class);
#OnOpen
public void onOpen(Session session) {
LOG.info("onOpen");
}
#OnClose
public void onClose(Session session) {
LOG.info("onClose");
}
#OnMessage
public void onMessage(String message, Session session) {
LOG.info("onMessage");
}
#OnError
public void onError(Throwable t) {
LOG.info("onError");
}
}
Now there is the requirement that a connection to a PLC (TCP/IP socket) must be established. What would be the best way to do this? Because if I understood correctly, socket connections should not be made at EJB level.
Finally, a description of the process and the function of the software:
PLC sends data via the TCP/IP socket connection. Then this data should be partially stored in the database and then forwarded to the front end (Angular App) via the existing WebSocket connection.
Related
I am using spring boot 2.1.6 RELEASE, trying to use Stomp websockets for push notifications. I have taken reference from here : https://github.com/netgloo/spring-boot-samples/tree/master/spring-boot-web-socket-user-notification
Things work fine in my local. When deployed to server with an HTTPS connection, all I see is this in the log.
Handshake failed due to invalid Upgrade header: null
and on the browser
Websocket.js:6 WebSocket connection to 'wss://dev.myserver.in/ws/055/chbvjkl4/websocket' failed
I went through dozens of stackoverflow posts and almost everyone is using proxy server. I am not using any proxy servers. (Please let me know if I should be using one and why)
The code snippets:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
}
Here is how I have allowed the websocket requests for now
#Override
public void configure(WebSecurity web) throws Exception {
// Tell Spring to ignore securing the handshake endpoint. This allows the
// handshake to take place unauthenticated
web.ignoring().antMatchers("/ws/**");
}
The Push notification service which will be invoked on a particular action:
#Service
public class PushNotificationService {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
/**
* Send notification to users subscribed on channel "/user/queue/notify".
* The message will be sent only to the user with the given username.
*
* #param notification The notification message.
* #param username The username for the user to send notification.
*/
public void sendPushNotification(Notifications notification, String username) {
simpMessagingTemplate.convertAndSendToUser(username, "/queue/notify", notification);
return;
}
}
On the front end:
function connect() {
// Create and init the SockJS object
var socket = new SockJS('/ws');
var stompClient = Stomp.over(socket);
// Subscribe the '/notify' channel
stompClient.connect({}, function(frame) {
stompClient.subscribe('/user/queue/notify', function(notification) {
notify(JSON.parse(notification.body));
});
});
And here is the notify
function notify(message) {
let notificationTitle = message.status;
let body = message.createdOn;
let link = message.url;
if(Notification.permission === "granted") {
showPushNotification(notificationTitle,body,link);
}
else {
Notification.requestPermission(permission => {
if(permission === 'granted') {
showPushNotification(notificationTitle,body,link);
}
});
}
}
if you not use like nginx proxy, you should configure spring boot support https.
The following code is from spring mvc documentation:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
#Controller
public class GreetingController {
#MessageMapping("/greeting") {
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
}
The client connects to http://localhost:8080/portfolio to establish WebSocket connection, I wonder what's the exact url of client sending request?
http://localhost:8080/portfolio/app
or
http://localhost:8080/app?
and in actual WebSocket frame, does the destination header contain relative url like /app, /topic or absolute url?
[Android] https://github.com/NaikSoftware/StompProtocolAndroid
[Spring] https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/web.html#websocket-stomp
Just set the end point by using
addEndpoint("/portfolio");
Use the following Url to connect to websocket
ws://localhost:8080/portfolio
But remember you have to connect to socket only once and after that just invoke the endpoints without URL. Beacause socket is streamline connection and you have to establish connection only once.
setApplicationDestinationPrefixes("/app");
Above line will set the end point /app using this you can only publish over the socket. However all who has subscribed to this topic will get notified.
enableSimpleBroker("/topic");
Broker are responsible for handling subscribe and publish for both as they listen and send data in dual way means publish and subscribe both unlike /app.
private var mStompClient: StompClient? = null
mStompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, "ws://localhost:8080/portfolio")
Connect to websocket using the above line. since we have to connect to socket end point only once write this in singleton.
val response = stomp.topic("/topic")
.subscribe { topicMessage -> }
Now above line will subscribe to your socket client means anytime you pushed the data from /topic this will this response variable will notified.
stompClient.send(StompMessage(StompCommand.SEND,
listOf(StompHeader(StompHeader.DESTINATION, "/topic")),
gson.toJson(myDataModel)))?
.subscribe()
Using above line you will you will you will send data to the socket which is specified as /topic.
#MessageMapping("/action")
fun performDeviceAction(#Payload myDataModel: MyDataModel) {}
Use the above line to receive the data from client on socket /action
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketTextHandler(), "/user");
}
In order to tell Spring to forward client requests to the endpoint , we need to register the handler. Above snipplet will register a client.
Use below link and download source code for more information
https://www.javainuse.com/spring/boot-websocket
I have a Echo server example from Official Netty
Echo Server
How to add ability to connect and streaming to it from websocket?
here is my ServerHandler code:
public class ServerHandler extends ChannelInboundHandlerAdapter
{
#Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
super.channelRegistered(ctx);
// !!!!! Think here should be WebSocket Handshake?
}
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
{
System.out.println(msg);
ctx.write(msg);
}
#Override
public void channelReadComplete(ChannelHandlerContext ctx)
{
ctx.flush();
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{
// Close the connection when an exception is raised.
cause.printStackTrace();
}
}
Right now Chrome connection says: WebSocket connection to 'ws://127.0.0.1:8080/' failed: Error during WebSocket handshake: Invalid status line
Netty servers do not automatically handle all protocols, so you would need to add support for WebSockets.
I find the best place to start is to examine the relevant examples in Netty's xref page. Scroll down the package list until you get to the io.netty.example packages. In that list you will find a package called io.netty.example.http.websocketx.server. There is a fairly simple and well laid out example on how to implement a websocket server, or just the handler.
Websocket servers are slightly more complicated than other servers in that they must start life as an HTTP server because the protocol specifies that websockets must be initiated by "upgrading" an HTTP connection, but as I said, the example referenced above makes this fairly clear.
So, I found solution! It is not compliant to native documentation of web-socket, but who cares it works as I expected!
public void channelRead(ChannelHandlerContext ctx, Object msg)
{
DefaultHttpRequest httpRequest = null;
if (msg instanceof DefaultHttpRequest)
{
httpRequest = (DefaultHttpRequest) msg;
// Handshake
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://127.0.0.1:8080/", null, false);
final Channel channel = ctx.channel();
final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(httpRequest);
if (handshaker == null) {
} else {
ChannelFuture handshake = handshaker.handshake(channel, httpRequest);
}
}
}
Do not forget to add
p.addLast(new HttpRequestDecoder(4096, 8192, 8192, false));
p.addLast(new HttpResponseEncoder());
to your pipeline.
In my application I have a sessionhandler that stores all connected sessions when they connect and removes them if they disconnects properly. If they do not I keep the sessions and if they reconnect I replace the old session with the new one. (The code below seems stupid, but it is just to make my point)
#ServerEndpoint(value = "/games")
public class GameEndPoint {
#Inject
private GameSessionHandler sessionHandler;
#OnOpen
public void open(Session session) {
sessionHandler.addSession(session);
}
#OnClose
public void close(Session session, CloseReason closeReason) {
sessionHandler.sessionClosed(session, closeReason.getCloseCode());
}
#OnError
public void onError(Throwable error) {
LOGGER.log(Level.SEVERE, "Socket connection error", error);
}
#OnMessage
public String handleMessage(String payload, Session session) {
return payload;
}
}
#Singleton
public class GameSessionHandler implements Serializable {
private final Set<Session> sessions = new HashSet<>();
public void addSession(Session session) {
for(Session existing: sessions) {
if(existing.equals(session)) {//DOES NOT WORK
//client reconnected!
sessions.remove(existing);
break;
}
}
sessions.add(session);
}
public void removeSession(Session session) {
if (CloseReason.CloseCodes.NORMAL_CLOSURE.equals(reason)) {
sessions.remove(session)
}
}
}
The problem is: how can I check that the session is for a client that was connected earlier, so that I can replace the session with the new open one?
Edit:
This is needed because my client is a html5 application where the user can refresh/navigate on the page, and then the connection is lost. Whenever he attempts to reconnect I want to know which game he was currently playing. If the user is on a unstable connection (ie. on a mobile phone), also the connection will be lost from time to time.
I am not able to make a solid enough check for this, so ended up sending a unique string (uuid) to the client upon connecting. If client connects with a query param giving his old uuid, I use this to figure out who he is. I just trust the client to be who he says he is, and anyhow he can only connect with this uuid if the old session with this uuid has disconnected.
I have not considered security at all, and if I had I should possibly use something like diffie hellman key exchange so the only two parties that know the shared uuid is the server and the client.
Is there any mechanism/api by which I can control TPS hits from http client?
From HTTP client, I need to control numbers of hits to rest services (my HTTP client will hit to server in controlled manner).
you can close it immediately by adding a Netty's IpFilterHandler to server pipeline as the first handler. It will also stop propagating the upstream channel state events for filtered connection too.
#ChannelHandler.Sharable
public class FilterIPHandler extends IpFilteringHandlerImpl {
private final Set<InetSocketAddress> deniedIP;
public filter(Set<InetSocketAddress> deniedIP) {
this.deniedIP = deniedIP;
}
#Override
protected boolean isAnAccpetedIP(ChannelHandlerContext ctx, ChannelEvent e, InetSocketAddress inetSocketAddress) throws Exception {
return !deniedIP.contains(inetSocketAddress);
}
}