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.
Related
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.
I'm testing a Ocpp Server comunication using Spring Websocket. The handshake works well, I can interact with the client when a station send a message (BootNotification,StatusNotification...). But sometimes I need to send things with the server (request remote transaction, get informations, etc), without the station send first.
How can I access a open session (example: ws:localhost:8080/central/station01) with another service?
My Wesocket config:
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/central/**")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
My WebSocket Handler:
public class MyHandler extends TextWebSocketHandler implements SubProtocolCapable {
private final String[] subProtocols = {"ocpp1.6", "ocpp2.0"};
#Autowired
private ClientRepository clientRepo;
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
Global.id="";
Global.session="";
Global.client="Close";
System.out.print("\n Connection Close \n"+"Session: "+session.getId()+"\n");
session.getHandshakeHeaders();
System.out.print("session enabled"+session);
}
#Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception{
Global.id=session.getId();
Global.session=session.getUri().toString();
Global.client="ok";
Client aa= new Client(null,session.getId(),session.getUri().toString(),"ok","");
System.out.print("\n Connected \n"+"Session: "+session.getId()+"\n");
System.out.print(session.getUri());
}
#Override
public void handleMessage(WebSocketSession session,WebSocketMessage<?> message) throws Exception {
//WebSocketHttpHeaders expectedHeaders = new WebSocketHttpHeaders();
System.out.print("\n "+message.getPayload());
Integer id=0;
Global.ocpp=Global.ocpp+" \n "+message.getPayload().toString();
ZonedDateTime data = ZonedDateTime.now();
BootNotificationResponse stat=new BootNotificationResponse("Accepted",data.toString().substring(0,data.toString().length()-"[America/Sao_Paulo]".length()),300);
JSONArray mm=new JSONArray((message.getPayload()).toString());
id=(int )mm.get(0)+1;
// session.sendMessage(new TextMessage(message.getPayload().toString()));
// System.out.print("\n Remote: "+session.getRemoteAddress()+"\n");
JSONObject ss=new JSONObject(stat);
System.out.print(session.getHandshakeHeaders());
JSONArray ja = new JSONArray();
ja.put(3);
ja.put(mm.get(1));
//
ja.put(ss);
// System.out.print("\n"+message.getPayload()+"\n");
// System.out.print(mm.get(2)+"\n");
Client dados=new Client(null,Global.id,Global.session,Global.client,message.getPayload().toString());
clientRepo.save(dados);
if(mm.get(2).equals("Authorize")) {
JSONArray nob = new JSONArray();
JSONObject iii=new JSONObject(new Auth(new AuthorizeResponse("1233434","ddfd","Accepted")));
nob.put(3);
nob.put(mm.get(1));
nob.put(iii);
System.out.print(nob);
//[2,"4","Authorize",{"idToken":{"idToken":"111111","type":"ISO14443"},"evseId":[1]}]
session.sendMessage(new TextMessage(nob.toString()));
}
if(mm.get(2).equals("BootNotification")) {
System.out.print("Boot \n");
session.sendMessage(new TextMessage(ja.toString()));
}
}
#Override
public List<String> getSubProtocols() {
System.out.print(Arrays.asList(subProtocols));
return Arrays.asList(subProtocols);
}
}
You need to do plenty of things to send command from your side to station.
When station connect to your server, you need to keep that session
in a Map with chargepointId probably.
If you would like to send
command then use that session then use websocket instance to send
command to station.
To initiate Step-2, you need to have service or
API to initiate it.
Should you need information, follow this URL: https://github.com/ChargeTimeEU/Java-OCA-OCPP
In my application, I need to send real time notifications to a specific user.
My WebSocketConfig class is as below,
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/websocket-example")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
}
}
Most of the time information will be sent by the server side. So I have not set the application destination.
In the client side, I am subscribing to the destination '/topic/user`,
function connect() {
var socket = new SockJS('/websocket-example');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/user', function (greeting) {
// showGreeting(JSON.parse(greeting.body).content);
console.log("Received message through WS");
});
});
}
In one of my RestController I have a method which broadcasts the message to all connected clients.
#GetMapping("/test")
public void test()
{
template.convertAndSend("/topic/user", "Hurray");
}
Until this part everything works fine. I receive the message and is logging to the console.
Now If I want to send the notification only to specific users, I have to use template.convertAndSendToUser(String user, String destination, String message). But I am not understanding what I should pass to the user parameter. Where and when will I get the user?
I went through couple of questions related to this, but I am not understanding the concepts clearly.
Before sending any messages to a user you need to authenticate it by the server first. There are different ways for doing this. Spring Security is a key phrase here
https://docs.spring.io/spring-security/site/docs/current/guides/html5/helloworld-boot.html
When authentication is completed you can simply get user name by calling:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
https://www.baeldung.com/get-user-in-spring-security
This username is part of a java.security.Principal interface. Each StompHeaderAccessor or WebSocket session object has an instance of this principle and you can get the username from it. it is not generated automatically. It has to be generated manually by the server for every session.
You can check here for more info about generating a unique id for every session.
then use like this:
#MessageMapping('/test')
public void test(SimpMessageHeaderAccessor sha)
{
String userName = sha.session.principal.name;
template.convertAndSend(userName, '/topic/user', "Hurray");
}
I'm facing the following problem and I found no working solution yet.
I have 3 different applications that should communicate with each other:
the UI part (1)
the backend application (2)
the microservice "in the cloud" (3)
The backend application provides a Webservice (REST) for the UI to get and put information from/to the microservice.
Everything I want to grab from the microservice works fine, but:
If I want to put data to the microservice, the specs require a websocket connection. This works fine too, but the microservice returns a message after the (un-)successful command, like
{"statusCode":200,"messageId":"1234567890"}
The problem now is: How can I grab this message in my application and send it back to the UI, so the user knows if the command was successful?
For the moment I tried this:
WebSocketClient.java
#OnMessage
public void onMessage(Session session, String msg) {
if (this.messageHandler != null) {
this.messageHandler.handleMessage(msg);
}
}
public void addMessageHandler(MessageHandler msgHandler) {
this.messageHandler = msgHandler;
}
public static interface MessageHandler {
public String handleMessage(String message);
}
MyTotalAwesomeController.java
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
...
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
...
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
return message;
}
});
return ResponseEntity.ok("worked");
}
I can see the console output from the MessageHandler return, but I don't know how I can pass this to the parent method for return insted of just returning the ResponseEntity.ok().
I'm not very used to WebSocket connections in Java yet, so please don't judge me ;-)
Thank you for your help.
The code below will work under the assumption that the #OnMessage method is executed in a thread managed by the WebSocket client runtime. Please inspect the thread that runs the #OnMessage method.
If the above premise is true, the putDataToMicroservice() method, executed by a thread in the global scope, will wait until the WebSocket response arrives at the WS client thread, which will repass the message to the global scope thread. Then the execution in your controller class will continue.
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
// Queue for communication between threads.
private BlockingQueue<String> queue;
#PostConstruct
void init() {
queue = new SynchronousQueue<>(true);
// This callback will be invoked by the WebSocket thread.
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
#Override
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
// Pass message to the controller thread.
queue.put(message);
// Note that the return value is not necessary.
// You can take it out of the interface as well.
return null;
}
});
}
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
// At this point you make a WebSocket request, is that right?
doWebSocketRequest();
// This poll call will block the current thread
// until the WebSocket server responds,
// or gives up waiting after the specified timeout.
//
// When the WebSocket server delivers a response,
// the WS client implementation will execute the
// #OnMessage annotated method in a thread
// managed by the WS client itself.
//
// The #OnMessage method will pass the message
// to this thread in the queue below.
String message = queue.poll(30, TimeUnit.SECONDS);
if (message == null) {
// WebSocket timeout.
}
return ResponseEntity.ok("worked");
}
}
I'd like to use websockets with at HttpServer. Here is the HttpHandler I came up with ... and the corresponding EchoServer
public class WebSocketHandler implements HttpHandler {
#Override
public void handle(final HttpExchange exchange) throws IOException {
String requestMethod = exchange.getRequestMethod();
if (requestMethod.equalsIgnoreCase("GET")) {
System.out.println("Well formed websocket Upgrade request");
/*
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
* ''Sec-WebSocket-Protocol: chat
*/
Headers responseHeaders = exchange.getResponseHeaders();
responseHeaders.set("Upgrade", "websocket");
responseHeaders.set("Connection", "Upgrade");
responseHeaders.set("Sec-WebSocket-Accept", "XXXX");
exchange.sendResponseHeaders(101, 0);
exchange.getResponseBody().write("ok".getBytes());
ServerEndpointConfig.Builder.create(EchoServer.class, "/echo").build();
} else {
System.out.println("Server: non-GET websocket upgrade request....");
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().write("ok".getBytes());
}
}
}
The EchoServer class is:
#ServerEndpoint(value = "/echo")
public class EchoServer {
private Logger logger = Logger.getLogger(this.getClass().getName());
int id = 0;
#OnOpen
public void onOpen(Session session) {
logger.info("Connected ... " + session.getId());
}
#OnMessage
public String onMessage(String message, Session session) {
switch (message) {
case "quit":
try {
session.close(new CloseReason(CloseCodes.NORMAL_CLOSURE, "Game ended"));
} catch (IOException e) {
throw new RuntimeException(e);
}
break;
}
id++;
return String.format("%d:%s", id, message);
}
#OnClose
public void onClose(Session session, CloseReason closeReason) {
logger.info(String.format("Session %s closed because of %s", session.getId(), closeReason));
}
}
I don't have much hope that this will work because I don't think the connection IDs are exchanged.
How would you complete this code to get a functional websocket connection?
Using com.sun.net.httpserver.HttpExchange for WebSocket isn't going to work.
That API for that doesn't allow for a true upgraded connection. In other words, a connection where there is no HTTP connection encoding or encapsulation (such as chunked, or gziped, etc)
Also, your protocol handling of the WebSocket upgrade isn't valid, no browser out there will find that response to be valid and continue the upgrade.
2 examples in your code.
Sec-WebSocket-Accept must be provided, and be correctly calculated from the incoming request headers
Sec-WebSocket-Protocol must be provided, if the incoming request has one defined