Wait for Response on REST Request JAVA - java

I created a java project with glassfish and posted a simple REST GET service like this:
#Path("/get")
public class Rest {
#Path("test/{user}/")
#GET
public String getTest(#PathParam("user") String id) throws IOException {
//send message to websocket client and wait for response
//return "websocket client response";
}
}
this works fine.
I also have a websocket server implementation in the same project. This implementation allows me to send data to the connected clients.
This is my WebSocket implementation:
#ServerEndpoint("/websocket")
public class WebSocketServer {
#OnOpen
public void onOpen(Session userSession){
System.out.println("Se conecto un nuevo cliente");
Modelo.getInstance().users.add(userSession);
}
#OnMessage
public void onMessage(String message,Session userSession) throws IOException{
String username=(String) userSession.getUserProperties().get("username");
if(username==null){
userSession.getUserProperties().put("username", message);
userSession.getBasicRemote().sendText(Modelo.getInstance().buildJsonData("Servidor","nuevo cliente conectado como: "+message));
}else{
Iterator<Session> iterator=Modelo.getInstance().users.iterator();
while(iterator.hasNext()){
iterator.next().getBasicRemote().sendText(Modelo.getInstance().buildJsonData(username,message));
}
}
}
#OnClose
public void onClose(Session userSession){
Modelo.getInstance().users.remove(userSession);
}
#OnError
public void onError(Throwable t){
t.printStackTrace();
}
}
this works fine too.
When the REST method is called i can send successfully a message to one of my websockets clients.
The thing is that i want to return as the REST response, the data that the WebSocket client sends me.
So...
1)Receive REST GET request in Java Server
2)Send via websocket to the client i want to get the info from
3)Respond the REST GET request with the message the websocket client send me.
How can i accomplish this?
[SOLVED]?
I found a way to do this, please i would like to know what do you think.
I found this article: here about async rest reponses.
So i implemented, its the first thing come to my mind, i save the websocket client message in an array, and the REST request is responded when the array has a message.
#Path("/resource")
#GET
public void asyncGet(#Suspended final AsyncResponse asyncResponse) throws IOException {
Modelo.getInstance().enviarMensaje("5", "escenas");
new Thread(new Runnable() {
#Override
public void run() {
String result = veryExpensiveOperation();
asyncResponse.resume(result);
}
private String veryExpensiveOperation() {
while(Modelo.getInstance().responses.size()==0){
}
String result=Modelo.getInstance().responses.get(0);
Modelo.getInstance().responses.clear();
return result;
// ... very expensive operation
}
}).start();
}
I know there a more things to validate this reponses, but at first it works.
I also edit the websockerserver.java to save in the array the response.
Thank you very much

REST works over HTTP which is a request/response model of communication. Which means you need to send a request in order to get a response. Web Sockets is a full duplex socket model. This means the client or the server can send a message as long as the connection is up. The challenge is you're trying to send a response with REST without a request. You could queue the response from the web socket and then send it back with the next REST response. This would however require the REST client to poll the server periodically since you would not have an indication of when the Web Socket client responded.

Related

How to send the result to the client via the gRPC StreamObserver in a different thread?

I have an exciting situation in my application.
In the typical scenario, the client sends a gRPC request to the Server. When the server(RequestHandler) receives the request, it will save the streamObserver in a ThreadLocal variable and send a ProcessRequest event using the ApplicationEventPublisher to process the request. The RequestHandler is listening to another event RequestProcessed to get the result. At this time, the streamObserver will be accessed from the ThreadLocal variable, and using the onNext method the result will be sent to the Client.
The special case happens when the result is coming from a different thread where I don't have the access to the streamObserver. What is the best way to handle such a scenario?
The solution I have in my mind is to introduce another gRPC client to the Server and a gRPC server to the client. This way, the server can initiate a new gRPC request to the client. I am not sure if this is the best solution since I am going to have 2 gRPC servers in both the client application and the server application.
I am using 2 spring boot microservices as the client application and the server application.
Client code
#RequiredArgsConstructor
public class GrpcClient {
private final ClientServiceGrpc.ClientServiceGrpc serviceStub;
private final StreamObserver<ProcessedResult> streamObserver;
#ServiceActivator
public void send(ProcessRequest request) {
serviceStub.process(request, streamObserver);
}
}
Server code
#Component
#RequiredArgsConstructor
public class ProcessRequestHandler extends AbstractRequestHandler<ProcessRequest, ProcessedResult> {
private final ApplicationEventPublisher publisher;
private final ThreadLocal<StreamObserver<ProcessedResult>> localObserver = new ThreadLocal<>();
#Transactional
public void handleRequest(ProcessRequest request, StreamObserver<ProcessedResult> response) {
try {
localObserver.set(response);
publisher.publishEvent(new ProcessRequest(request));
} catch (Throwable t) {
response.onError(t);
localObserver.remove();
throw t;
}
}
#TransactionalEventListener()
public void handleResponseComingFromDifferentThread(RequestProcessed result) {
// accessing response by localObserver.get() is not possible here since this is a different thread. How can I use this result to invoke the response.onNext(result) ?
}
}

How to implement a request-response pattern with paho mqtt java?

I'd like to use Paho MQTT Java and implement some "request-response" pattern. What I mean by that is that in some instances the client has to talk to the server and expects a specific answer to a specific request. It feels like this lib has everything needed to match a request and a response, but I can't quite put it together.
I found I can retrieve a token via deliveryComplete, and that I can do a setActionCallback on this token. But first, I'm not entirely sure what an "action" means. Then, if it means what I think it means, how to get the actual response to my request from there?
sampleClient.setCallback(new MqttCallback() {
#Override
public void connectionLost(Throwable cause) {
}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
System.out.println(topic);
System.out.println("setCallback: "+ message.toString());
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println(token.getMessageId());
token.setActionCallback(new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
var response = asyncActionToken.getResponse();
try {
System.out.println(new String(response.getPayload()));
} catch (MqttException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
}
});
}
});
MQTT is overkill for any kind of 1 Request - 1 Response....that is what APIs are for. MQTT is best for one node sending out data to one or more nodes who are tapped into the Topic. The sending node (Publisher) doesn't need to know about any of the receiving nodes (Subscribers)...they just all have to agree on the Topic used.
That being said, you could implement a "Query" Publish, and look for a "Reply" MQTT message back that you would subscribe to. I have done this it the past where multiple nodes can benefit from knowing what the Reply is. So it might look something like this: Publish - q/system/status and Subscribe to r/system/status or r/system/#.
This can be done using MQTT5.
This is how it works:
Suppose you have two MQTT clients, one of which acts as a server.
client1, client2(server).
client1 publishes a message with a response topic in the message property.
client2 receives the message reads the message properties and publishes the response on that topic
client 1 gets the message on the response topic.
Here is a small implementation:
Use the following dependency:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-paho-mqtt5</artifactId>
<version>3.17.0</version>
</dependency>
Client 1 Publish:
public void sendMessage(String messageStr){
MqttMessage message = new MqttMessage();
MqttProperties properties = new MqttProperties();
properties.setResponseTopic("response");
properties.setCorrelationData("correlation".getBytes());
message.setProperties(properties);
message.setPayload(messageStr.getBytes());
message.setQos(1);
message.setRetained(true);
try {
client.publish("test",message);
} catch (MqttException e) {
e.printStackTrace();
}
}
Client 2 (Server) Subscriber MessageListener:
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.mqttv5.client.IMqttClient;
import org.eclipse.paho.mqttv5.client.IMqttMessageListener;
import org.eclipse.paho.mqttv5.common.MqttMessage;
import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
#Slf4j
#Component
public class MessageListener implements IMqttMessageListener {
#Autowired
private IMqttClient client;
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
log.info("Topic : {}, Message: {}", topic,message.toString());
MqttProperties properties = new MqttProperties();
if(message.getProperties()!=null && StringUtils.hasText(message.getProperties().getResponseTopic())){
MqttMessage responseMessage = new MqttMessage();
properties.setCorrelationData(message.getProperties().getCorrelationData());
responseMessage.setProperties(properties);
responseMessage.setPayload("Response".getBytes());
responseMessage.setQos(1);
responseMessage.setRetained(true);
client.publish(message.getProperties().getResponseTopic(),responseMessage);
}
}
}
Then Client 1 should subscribe to topic "response" to receive the response from client 2.
Here is more on this:
http://www.steves-internet-guide.com/mqttv5-request-response/
There is NO end to end delivery notification in the MQTT protocol. The delivery complete callback is only called to notify that the message has travelled between the publishing client and the broker, it says nothing about if the message has been delivered to any down stream subscribers (there could 0 to many other clients subscribed to a given topic).
Once a message reaches the broker it may even be queued for an offline client with a persistent session.
Even assuming just 1 client receives the message, any reply would be a totally separate message not linked to the first request.
MQTT is not natively a request/response system like say HTTP, it's a way to send messages to 0 to many clients that have subscribed to a give topic.
MQTT v5 starts to introduce the concepts of requests/response to MQTT, but even then it's just an extra optional slot in the header to include a topic to reply on. And you can set a collation id in the message header to link the response message to the request.

How to handle socket disconnection and heartbeat messages?

What I am trying to do
I have a lobby with players and when someone leaves the lobby I want to update it for every client so the actual list of players is displayed.
What I have done
To avoid cyclical requests being sent from frontend to backend I decided to use web sockets. When someone leaves the lobby then request is sent to REST api and then backend, upon receiving this request, does all the business logic and afterwards "pokes" this lobby using socket in order to update all clients in the lobby.
My problem
Everything works fine except the case when user closes the browser or the tab because I can't send a request in this scenario. (as far as I know this is impossible to do using javascript and beforeunload event, onDestroy() methods, etc..)
My question
Is it possible to check on the server side whether any socket disconnected and if yes then how can I do this? I also tried to use heartbeat which is being sent from frontend to backend but I don't know how to handle this heartbeat message on the server side.
Server side (Spring boot)
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguartion implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/api/socket")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
ThreadPoolTaskScheduler te = new ThreadPoolTaskScheduler();
te.setPoolSize(1);
te.setThreadNamePrefix("wss-heartbeat-thread-");
te.initialize();
config.enableSimpleBroker("/lobby")
.setHeartbeatValue(new long[]{0, 1000})
.setTaskScheduler(te);
}
}
#Controller
public class WebSocketController {
private final SimpMessagingTemplate template;
WebSocketController(SimpMessagingTemplate template) {
this.template = template;
}
public void pokeLobby(#DestinationVariable String lobbyName, SocketMessage message) {
this.template.convertAndSend("/lobby/"+lobbyName.toLowerCase(), message);
}
}
Client side
connectToLobbyWebSocket(lobbyName: string): void {
const ws = new SockJS(this.addressStorage.apiAddress + '/socket');
this.stompClient = Stomp.over(ws);
// this.stompClient.debug = null;
const that = this;
this.stompClient.connect({}, function () {
that.stompClient.subscribe('/lobby/' + lobbyName, (message) => {
if (message.body) {
that.socketMessage.next(message.body); // do client logic
}
});
});
}
You can listen for SessionDisconnectEvent in your application and send messages to other clients when you receive such an event.
Event raised when the session of a WebSocket client using a Simple Messaging Protocol (e.g. STOMP) as the WebSocket sub-protocol is closed.
Note that this event may be raised more than once for a single session and therefore event consumers should be idempotent and ignore a duplicate event.
There are other types of events also.

Push Notification in Web Server

I just have a quick question.
Is it possible to send a push notification upon receiving a request in the web server?
For example, user A sends a message to user B
user A's request goes to the web server (Tomcat in this case)
Upon receiving request from user A, the server sends request to APNs to send notification to user B (user B's token has been retrieved earlier).
I am trying to use jdk9.incubator.http to send a push notification to the user B's device in doPost() method when user A sends a request to the web server.
My class extends HttpServlet
public class AuthenticationController extends HttpServlet
Trying to send push notification upon authenticating the user
#SuppressWarnings("exports")
public void doPost(HttpServletRequest req, HttpServletResponse resp) {
try {
LOGGER.debug("POST requested from a client");
Authenticate(req, resp);
sendNotification();
} catch (Exception e) {
LOGGER.error("Oops",e);
}
}
Please give me advice to do it correctly.
EDIT:
I learned that the socket closes after the response is flushed in HttpServlet, so I tried to send another request to send a notification before flushing the response.
private void sendResponse(
HttpServletResponse resp, Gson gson ,Object obj) throws IOException, NoSuchAlgorithmException {
sendTestRequest()
resp.getOutputStream().print(gson.toJson(obj));
resp.getOutputStream().flush();
}//sendResponse()
I tested out JDK9.incubator in the main thread of another project that I worked on, which worked beautifully; however, I cannot somehow have it work in my tomcat server environment.
When I tried to send a simple request to just test out jdk9.incubator.request, nothing happens. sendTestRequest() method simply sends out a http request. Example can be shown here.
Below code snippet is an example of what my http/2 request looks like
final HttpRequest request = setHttpRequest(notification);
NotificationResponse resp = new NotificationResponse(null);
try {
HttpResponse<String> response =
getHttpClient().send(request, BodyHandler.asString());
resp.setResult(response.statusCode(), response.body());
if (resp.isAccepted()) {
System.out.println("Push Notification was accepted by APNs");
} else {
System.out.println("Request rejected reason: " + resp.getFailureReason());
}
} catch (IOException | InterruptedException e) {
System.out.println("Exception was thrown; check the log");
LOGGER.fatal("Failed to send request synchronously", e);
}

Netty, how to implement an HTTP connection limiter which sends a response (503) prior to closing channel

Currently in my pipeline I have a simple handler to reject connections when my server gets overloaded:
public class RequestFilter extends SimpleChannelHandler {
#Override
public void channelConnected(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception {
requestLimiter(ctx, e);
super.channelConnected(ctx, e);
}
}
private void requestLimiter(final ChannelHandlerContext ctx, final ChannelStateEvent e) {
if(threshold < counter) {
ctx.getChannel().close();
}
}
When the counter exceeds the threshold, the channel is closed, that all seems to work fine.
Now I'd like to enhance this by first sending an HTTP 503 response prior to closing the channel. What i've tried so far is this method below, instead of closing the channel immediatly I try to write a response to the channel and then handle closing it with a channelfuture listener so it's closed when the write is complete. However whats happening is I get a ton of exceptions about "already sent a response, can't send more than 1" followed by stack overflow.
protected void sendResponse(Channel channel, HttpResponse response) {
if (channel.isConnected()) {
channel.write(response).addListener(ChannelFutureListener.CLOSE);
log.trace("response sent");
} else if (!channel.isConnected()) {
log.trace("attempted to send response, but the channel was closed");
} else {
log.trace("Not sure why this would happen");
}
}
Any thoughts or examples I could look at? thanks
Edit: stacktrace
java.lang.IllegalStateException: cannot send more responses than requests
at org.jboss.netty.handler.codec.http.HttpContentEncoder.writeRequested(HttpContentEncoder.java:104)
at org.jboss.netty.handler.timeout.WriteTimeoutHandler.writeRequested(WriteTimeoutHandler.java:152)
at org.jboss.netty.handler.stream.ChunkedWriteHandler.flush(ChunkedWriteHandler.java:262)
at org.jboss.netty.handler.stream.ChunkedWriteHandler.handleDownstream(ChunkedWriteHandler.java:119)
at org.jboss.netty.handler.execution.ExecutionHandler.handleDownstream(ExecutionHandler.java:165)
at org.jboss.netty.channel.Channels.write(Channels.java:605)
at org.jboss.netty.channel.Channels.write(Channels.java:572)
at org.jboss.netty.channel.AbstractChannel.write(AbstractChannel.java:245)
at com.test.RequestFilter.sendResponse(RequestFilter.java:98)
I don't think it is sending multiple responses. I think that it's [trying] to send one, which is one more than the number of requests which is zero because the the event is being triggered by the connect channel event and the pipeline has yet to see any http requests.
I would change your code to not do this on connect, but rather, trigger the 503 response on the first request. If the channel is then closed, adios client, but if the client's first request sneaks in under the threshold, then remove the bouncer from the pipeline (assuming that once a client is in, they're in for good).
Make sense ?

Categories

Resources