How to build a websocket HttpHandler - java

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

Related

How can enter in a open session using Spring Websocket Handler?

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

Working with Minishift watchers in Java applications

I've been working on a Java application that utilizes the openshift api. Specifically OpenShift deployment configuration
I have tried to set up a watcher, but my response body is never called. I am already able to get a response from the 'non watcher' APIcalls. I am using the groovy httpbuilder library to fulfill my request
def http = new HTTPBuilder(<<URL TO OPENSHIFT>>)
try {
http.get(path: '/oapi/v1/watch/namespaces/myproject/deploymentconfigs', contentType: "application/json") { resp, reader ->
println(resp)
return reader
}
} catch (HttpResponseException e) {
System.out.println(e)
}
Please Advise on a path forward to set up OpenShift watchers in my application.
An error message is never thrown. minishift logs -f are not providing any feedback either.
Also note that I have gotten this to work with the curl command, documented in the api
You can use the OKHttpClient to handle the http websocket upgrade protocol for you. Note legacy versions of minishift require the query parameter "access_token" when trying to make a websocket connection request
OkHttpClient client = new OkHttpClient();
def token = token
Request request = new Request.Builder()
.get()
.url("https://<<IP>>/oapi/v1/watch/namespaces/<<namespace>>/deploymentconfigs?watch=true&access_token=<<token>>")
.addHeader("Accept", "application/json")
.addHeader("Connection", "close")
.addHeader("Sec-WebSocket-Protocol",'base64url.bearer.authorization.k8s.io.' + Base64.getEncoder().encodeToString(token.getBytes()))
.addHeader('Origin', 'https://<<IP>>')
.build()
WebSocketListener websocketListener= new WebSocketListenerImpl()
client.newWebSocket(request, websocketListener)
WebSocketListenerImpl Class
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;
public class WebSocketListenerImpl extends WebSocketListener {
public WebSocketListenerImpl() {
super();
}
#Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
print "WEBSOCKET OPEN"
}
#Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
print "WEBSOCKET RECEIVED"
}
#Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
print "WEBSOCKET OPEN"
}
#Override
public void onClosing(WebSocket webSocket, int code, String reason) {
super.onClosing(webSocket, code, reason);
print "WEBSOCKET CLOSING"
}
#Override
public void onClosed(WebSocket webSocket, int code, String reason) {
super.onClosed(webSocket, code, reason);
print "WEBSOCKET CLOSED"
}
#Override
public void onFailure(WebSocket webSocket, Throwable t, #javax.annotation.Nullable Response response) {
super.onFailure(webSocket, t, response);
println "WEBSOCKET FAILED"
}
}

How to get Session Id in Spring WebSocketStompClient?

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

Java Websocket / MessageHandler return to global scope?

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");
}
}

How to use Netty to handle Http Keep-Alive connections

I'm trying to write a HTTP client that uses HTTP keep-alive connections. When I connection from the ClientBoostrap I get the channel. Can I reuse this for sending multiple HTTP requests? Is there any examples demonstrating the HTTP Keep Alive functionality?
Also I have another question. Now my client works without keep-alive connections. I'm calling the channel.close in the messageReceived method of the ClientHandler. But it seems the connections are not getting closed and after some time the sockets run out and I get a BindException. Any pointers will be really appreciated.
Thanks
As long as the Connection header is not set to CLOSE (and possible the HttpVersion is 1.1, though uncertain) by a line of code similar to this...
request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);
...your channel should remain open for multiple request/response pairs.
Here is some example code that I whipped up today to test it. You can bounce any number of requests off of Google prior to the channel closing:
public class TestHttpClient {
static class HttpResponseReader extends SimpleChannelUpstreamHandler {
int remainingRequests = 2;
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
HttpResponse response = (HttpResponse) e.getMessage();
System.out.println("Beginning -------------------");
System.out.println(new String(response.getContent().slice(0, 50).array()));
System.out.println("End -------------------\n");
if(remainingRequests-- > 0)
sendRequest(ctx.getChannel());
else
ctx.getChannel().close();
}
}
public static void main(String[] args) {
ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory());
bootstrap.setPipeline(Channels.pipeline(
new HttpClientCodec(),
new HttpResponseReader()));
// bootstrap.setOption("child.keepAlive", true); // no apparent effect
ChannelFuture future = bootstrap.connect(new InetSocketAddress("google.com", 80));
Channel channel = future.awaitUninterruptibly().getChannel();
channel.getCloseFuture().addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
// this winds up getting called immediately after the receipt of the first message by HttpResponseReader!
System.out.println("Channel closed");
}
});
sendRequest(channel);
while(true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void sendRequest(Channel channel) {
// Prepare the HTTP request.
HttpRequest request = new DefaultHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, "http://www.google.com");
request.setHeader(HttpHeaders.Names.HOST, "google.com");
request.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
channel.write(request);
}
}

Categories