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
Related
I am new to both springboot and websockets so please be gentle and this is my last question chance on this account. I have a websocket jar that sends messages from two urls ws:localhost/operations and ws:localhost/prices from 8080 port. My task is to read those messages. The Jar file streams messages like:
For operation:
"data":{ "description":"lorem ipsum", "id":"OJ1136453723" },
"type":"DELETE"
For price:
"data":{ "price":1384.1685, "id":"WN6427148286" }, "type":"PRICE"
I have spring-boot-starter-websocket, spring-boot-starter-security and lombok as dependencies.
I seem to be connecting to the jar file, because powershell says "Socket connected
Frame TEXT (fin=true, buffer len = 48)" when I run my code but it only hits the Application breakpoints whenever I debug, it doesnt even go to controller or config classes. It may be an obvious mistake but I am a bit lost so any help would be appriciated. Also, my port for this client is 8443 which is specified in application.yml and it starts there.
My Client:
public class WSClient{
private static String URL = "ws://localhost:8080";
public static void main(String[] args) throws Exception {
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
StompSessionHandler sessionHandler = new MyStompSessionHandler();
stompClient.connect(URL, sessionHandler);
new Scanner(System.in).nextLine();
}
Controller:
#Controller
public class MsgController {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
#MessageMapping("/operations")
public Operations operations(Operations operations)throws Exception{
System.out.println(operations);
simpMessagingTemplate.convertAndSend("/operations", operations);
return operations;
}
StompSessionHandler:
public class MyStompSessionHandler extends StompSessionHandlerAdapter {
private Logger logger = LogManager.getLogger(MyStompSessionHandler.class);
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
logger.info("New session established : " + session.getSessionId());
session.subscribe("/operations", this);
System.out.println("Subscribed to /operations");
logger.info("Subscribed to /operations");
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
logger.error("Got an exception", exception);
}
#Override
public Type getPayloadType(StompHeaders headers) {
return Operations.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
Operations ops = (Operations) payload;
System.out.println(ops);
}
}
Operations:
#lombok.Data
public class Operations{
public String description;
public String id;
public String type;
}
EDIT:
Thanks to help of #OkanKonur the problem is that StompHandler's afterConnected is not called even though it seems like they are connected. We tried it with the github link he's given in the comments and it seems to work. The jar file is most probably not a spring project so is there a way to solve this? It still can't read anything.
So i'm trying to send an object from client->server & server->client with WebSocket. Sending object from client->server works fine, meanwhile server->client throw an exception
org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Can not deserialize instance of java.lang.String out of START_OBJECT token
Here is the class i'm trying to send
#Data
#AllArgsConstructor
#NoArgsConstructor
public class TextMessage {
private String sender;
private String room;
private String message;
}
and this is the code on the client-side
public class TelepatiClient {
public static void main(String[] args) {
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
stompClient.setTaskScheduler(new ConcurrentTaskScheduler());
String url = "ws://localhost:8000/connect";
StompSessionHandler handler = new TelepatiSessionHandler();
stompClient.connect(url, handler);
new Scanner(System.in).nextLine();
}
}
public class TelepatiSessionHandler extends StompSessionHandlerAdapter {
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/room/global", this);
session.send("/test", new TextMessage("test", "test", "test"));
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
System.out.println(payload.toString());
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
exception.printStackTrace();
super.handleException(session, command, headers, payload, exception);
}
}
and this is message controller on the server-side
#Controller
public class TelepatiController {
#MessageMapping("/test")
#SendTo("/room/global")
public TextMessage getMessage(TextMessage message) {
System.out.println("get message :" + message.toString());
return new TextMessage("test2", "test2", "test2");
}
}
i was able to run System.out.println("get message :" + message.toString());, but get message convertion exception on the client-side when returning new TextMessage("test2", "test2", "test2");. From my test before, returning a String object works fine, why returning TextMessage object not working? How can i send any object (in this case TextMessage) from server->client? Thanks!
Well the problem is the content. In this line:
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
You indicate that the client uses a json converter. So, your client is always expecting a JSON object.
But in your test, in this line:
return new TextMessage("test2", "test2", "test2");
You are sending plain text. Due the StompClient is thrown an exception
org.springframework.messaging.converter.MessageConversionException
Because the message in text plain is not JSON object.
I hope someone helps you, I had the same problem, what I did was tell the topler handler, to which I subscribe, the type of payload that will return.
This is the handler of my stompClient:
public class TelepatiSessionHandler extends StompSessionHandlerAdapter {
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.send("/test", new TextMessage("test", "test", "test"));
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
exception.printStackTrace();
super.handleException(session, command, headers, payload, exception);
}
}
And this is the handler for the topic to which I subscribe
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
stompClient.setTaskScheduler(new ConcurrentTaskScheduler());
String url = "ws://localhost:8000/connect";
StompSessionHandler handler = new TelepatiSessionHandler();
StompSession session = stompClient.connect(url, handler).get();
session.subscribe("/room/global", new StompFrameHandler() {
#Override
public Type getPayloadType(StompHeaders headers) {
return TextMessage.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
TextMessage textMessage = (TextMessage) payload;
System.out.println(textMessage.toString());
}
});
Here is a complete example:
https://github.com/jaysridhar/spring-websocket-client/blob/master/src/main/java/sample/Application.java
How to get session id in Java Spring WebSocketStompClient?
I have WebSocketStompClient and StompSessionHandlerAdapter, which instances connect fine to websocket on my server. WebSocketStompClient use SockJsClient.
But I don't know how get session id of websocket connection. In the code with stomp session handler on client side
private class ProducerStompSessionHandler extends StompSessionHandlerAdapter {
...
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
...
}
stomp session contains session id, which is different from session id on the server.
So from this ids:
DEBUG ... Processing SockJS open frame in WebSocketClientSockJsSession[id='d6aaeacf90b84278b358528e7d96454a...
DEBUG ... DefaultStompSession - Connection established in session id=42e95c88-cbc9-642d-2ff9-e5c98fb85754
I need first session id, from WebSocketClientSockJsSession.
But I didn't found in WebSocketStompClient or SockJsClient any method to retrieve something like session id...
You can use #Header annotation to access sessionId:
#MessageMapping("/login")
public void login(#Header("simpSessionId") String sessionId) {
System.out.println(sessionId);
}
And it works fine for me without any custom interceptors
To get session id you need to define your own interceptor as below and set the session id as a custom attribute.
public class HttpHandshakeInterceptor implements HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession();
attributes.put("sessionId", session.getId());
}
return true;
}
Now you can get the same session id in the controller class.
#MessageMapping("/message")
public void processMessageFromClient(#Payload String message, SimpMessageHeaderAccessor headerAccessor) throws Exception {
String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
}
There is a way to extract sockjs sessionId via Reflection API:
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
// we need another sessionId!
System.out.println("New session established : " + session.getSessionId());
DefaultStompSession defaultStompSession =
(DefaultStompSession) session;
Field fieldConnection = ReflectionUtils.findField(DefaultStompSession.class, "connection");
fieldConnection.setAccessible(true);
String sockjsSessionId = "";
try {
TcpConnection<byte[]> connection = (TcpConnection<byte[]>) fieldConnection.get(defaultStompSession);
try {
Class adapter = Class.forName("org.springframework.web.socket.messaging.WebSocketStompClient$WebSocketTcpConnectionHandlerAdapter");
Field fieldSession = ReflectionUtils.findField(adapter, "session");
fieldSession.setAccessible(true);
WebSocketClientSockJsSession sockjsSession = (WebSocketClientSockJsSession) fieldSession.get(connection);
sockjsSessionId = sockjsSession.getId(); // gotcha!
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (StringUtils.isBlank(sockjsSessionId)) {
throw new IllegalStateException("couldn't extract sock.js session id");
}
String subscribeLink = "/topic/auth-user" + sockjsSessionId;
session.subscribe(subscribeLink, this);
System.out.println("Subscribed to " + subscribeLink);
String sendLink = "/app/user";
session.send(sendLink, getSampleMessage());
System.out.println("Message sent to websocket server");
}
Can be seen here: tutorial
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