How to make grpc StreamObserver object serializable in Java - java

I am using gprc bidirectional streaming for chat application in Spring boot and since StreamObserver<T> object is used to send message back from sever to client. So, I want to serialize StreamObserver<T> object and convert it into stream of bytes to store it in redis or some other database. But since, StreamObserver<T> is a interface which doesn't implement or extend serializable. So, I am looking for a solution to how to serialize it since there would be around thousands of user which be using the chat application and storing StreamObserver <T> in some Map<String, StreamObserver<T>> won't be good idea.
Currently, I am storing StreamObserver<T> objects in map.
Map<String, StreamObserver<T>>
Here, key of map is chat application's user's id and value of is StreamObserver object which contains onNext, onError, onCompleted functions to send message from server to client
// Storing StreamObserver object with user Id
public static Map<String, StreamObserver<Chat.ChatMessageFromServer>> observersMap = new HashMap<String, StreamObserver<Chat.ChatMessageFromServer>>();
#Override
public StreamObserver<Chat.ChatMessage> chat(final StreamObserver<Chat.ChatMessageFromServer> responseObserver) {
// responseObserver -> Storing it into a map. So, server could send message back to the client
String user = grpcServerInterceptor.contextKey.get();
System.out.println("");
System.out.println("User : " + user);
if (observersMap.get(user) == null) {
System.out.println("New User : " + user);
System.out.println("Adding User to observers map");
System.out.println("");
observersMap.put(user, responseObserver);
} else {
System.out.println("This User already exists in observersMap : " + user);
System.out.println("By the way, Updating it");
observersMap.put(user, responseObserver);
}
// This function sends message to client from Server
public void sendMessageFromServerToClient(String user, String message) {
// Fetching StreamObserver from observersMap as defined above
observersMap.get(user).onNext(Chat.ChatMessageFromServer.newBuilder().setMessage(Chat.ChatMessage.newBuilder().setTo(user).setFrom("Server").setMessage(message)).build());
System.out.println("Pushed message to user : " + user);
System.out.println("");
}

StreamObserver corresponds to a stream on a real TCP connection. That resource can't be transferred to a DB. There's no way to serialize it to a DB to reduce memory usage.

Related

Connect to pusher in android app, multiple or once?

In my app (streaming series, movies) I have a section for users that can set Reminder for the series or movies. And I implement Pusher to receive server message for reminding data.
Is it true that I connect to channel for each item in the reminder list?? or I should connect to the pusher once and in the pusher event get related series/ movies message?(Server-side implemented pusher for each reminder list items, should we change server-side implementation or I can connect to pusher for each items? )
This is my Implementation for pusher:
public Pusher getPusher() throws Exception {
if (pusher == null) {
HttpAuthorizer auth = new HttpAuthorizer(BuildConfig.PUSHER);
HashMap<String, String> authHeader = new HashMap<>();
authHeader.put("Authorization", SharedPref.INSTANCE.read(AUTH_TOKEN, ""));
auth.setHeaders(authHeader);
PusherOptions option = new PusherOptions();
option.setCluster(BuildConfig.PUSHER_CLUSTER);
option.setAuthorizer(auth);
pusher = new Pusher(BuildConfig.PUSHER_KEY, option);
pusher.subscribePrivate("private-app_ch." + serialId, new PrivateChannelEventListener() {
#Override
public void onAuthenticationFailure(String s, Exception e) {
Timber.i("pusher onAuthenticationFailure " + e.getMessage());
}
#Override
public void onSubscriptionSucceeded(String s) {
Timber.i("pusher onSubscriptionSucceeded: " + s);
}
#Override
public void onEvent(String s, String s1, String result) {
Timber.i("pusher onEvent" + s + ":" + s1);
Timber.i("pusher onEvent" + result);
}
}, "App\\Events\\AppBroadcastEvent");
}
return pusher;
}
The best practice for this would be to maintain one connection to Channels but make a subscription for each item in the reminder list.
So you would call pusher.subscribePrivate for each item in the reminder list and then on the server side publish to each individual Channel when a reminder needs to be sent.
For example if a user wanted to be reminded about 'Stranger Things' and 'Orange is the new black' you would subscribe to both:
pusher.subscribePrivate("private-app_ch.strangerthings"
and
pusher.subscribePrivate("private-app_ch.orangeisthenewblack"
Your server would then publish reminders about 'Stranger Things' to the Stranger things channel and OISTNB to the OISTNB channel and so on.
This way only relevant updates are sent to the client (server-side filtering). If you only subscribe to one channel the client will get messages they may not want updates about and you would have to filter these out on the client side.
This is also explained here: https://support.pusher.com/hc/en-us/articles/360025398514-Should-i-subscribe-to-lots-of-Channels-
One additional point that is worth considering is that Channels will only maintain an active connection when the app is open. The connection will be closed when the app is backgrounded/closed. This means for reminders to be sent the user would always have to be in your app. You may want to consider also sending push notifications when the app is closed so the user does not miss reminders.

Spring framework web sockets unique #SendTo annotation

I have a web app that has registered users and i would like to push messages to users who have unique user ids. To be able to send a message to the clients, i need to know how to pass unique user id in the #sendto annotation
This is the annotation
#SendTo("/topic/uniqueuserid")
public Greeting greeting(HelloMessage message) throws Exception {
int uniqueuserid;
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + message.getName() + uniqueuserid "!");
}
and this the stomp js
stompClient.subscribe('/topic/uniqueuserid', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
How can i pass a unique user id in #SendTo("/topic/uniqueuserid")
You can use #DestinationVariable annotation in a method argument, like that:
#MessageMapping("/mychat/{uniqueUserId}")
#SendTo("/topic/{uniqueUserId}")
public Message message(#DestinationVariable("uniqueUserId") Long uniqueUserId, HelloMessage message) {
logger.info("Unique user id: {}", uniqueUserId);
}

AngularJS ngWebscoket not send message to all user

I am using ngWebsocket for listening user actions and update all users page according to current action not just page that who send action.
And I make a end point in java who catch all actions and send message all open sessions. but when i testing, end point find sessions and send message to all of them but message just come to person who send action.
my java code like
#OnMessage
public String onMessage(Session session, String message) { Gson gson = new Gson();
SocketMessage sm = gson.fromJson(message, new SocketMessage().getClass());
if (sm.getEvent().equals("teklif")) {
Set<Session> openSessions = session.getOpenSessions();
for (Session openSession : openSessions) {
try {
openSession.getBasicRemote().sendText("{\"event\":\"teklif\",\"data\":" + sm.getData() + "}");
} catch (Exception ioe) {
System.out.println(ioe.getMessage());
}
}
}
return message;
}`
when i debug Set<Session> openSessions = session.getOpenSessions(); it show me two session and send message to all remote. And I listen in my controller
$rootScope.ws.$on('teklif', function (data) { console.log(data);
});
it is shown only person who emit the message
note : I send message like this -->$rootScope.ws.$emit('teklif', data.content);
How can I make this socket that all user listen all actions ?
Thanks in advance.
Your are using Session.getOpenSessions(). The Javadoc states:
Return a copy of the Set of all the open web socket sessions that
represent connections to the same endpoint to which this session
represents a connection. The Set includes the session this method is
called on. These sessions may not still be open at any point after the
return of this method. For example, iterating over the set at a later
time may yield one or more closed sessions. Developers should use
session.isOpen() to check.
So it does not give you the set of all client sessions connected to your endpoint.
Instead you need to keep track of all session connected to your endpoint for yourself and iterate over that set. Here is an example.
I found my problem what is it .
#OnMessage
public String onMessage(Session session, String message) {
Gson gson = new Gson();
SocketMessage sm = gson.fromJson(message, new SocketMessage().getClass());
if (sm.getEvent().equals("teklif")) {
//SoncketTestMessage fromJson = gson.fromJson(test.getData(), SoncketTestMessage.class);
Set<Session> openSessions = session.getOpenSessions();
for (Session openSession : openSessions) {
try {
SocketResponse rsp = new SocketResponse();
rsp.setEvent("teklif");
rsp.setData(gson.toJson(sm.getData()));
//openSession.getBasicRemote().sendText("{\"event\":\"teklif\",\"data\":" + sm.getData() + "}");
openSession.getBasicRemote().sendText(gson.toJson(rsp, SocketResponse.class));
} catch (Exception ioe) {
System.out.println(ioe.getMessage());
}
}
}
return null;
}
i made a mistake at
openSession.getBasicRemote().sendText("{\"event\":\"teklif\",\"data\":" + sm.getData() + "}");
i just changed sm.getData and send right json format then it send to all user.
It send just to owner before because of that function return message and it is at right format and only owner get the return. Now all user are getting the message.

How to retrieve all results (more than 100) using batch on gmail

I am using Gmail API with the java client library.
I want to batch a bulk of queries to get a list of messages.
I make a test for only one query.
Here is the snippet of the code :
Gmail client = getClient();
BatchRequest batch = client.batch();
// Create the callback.
JsonBatchCallback<ListMessagesResponse> callback = new JsonBatchCallback<ListMessagesResponse>() {
public void onSuccess(ListMessagesResponse listMessagesResponse, HttpHeaders responseHeaders) {
System.out.println(listMessagesResponse.getMessages().size());
System.out.println(listMessagesResponse.getNextPageToken());
}
#Override
public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) {
System.out.println("Error Message: " + e.getMessage());
}
};
client.users().messages().list("me#gmail.com").queue(batch, callback);
batch.execute();
The problem is that it retrieves only 100 messages. I don't want to get the last token and reuse it for the next query, it does not make a sense since I want to use batch.
So How is it posssible to bypass the limitation (100) in order to retrieve all messages in one batch request ?
Thanks

Handle android server communication after errors

My android app is using a http connection to send data to a server. If the server received the data the app marks the data as successfully transmitted in its own database.
Sometimes external problems may occur, so that the transmission cannot be completed. The app should try sending the data again later.
What is a good possibility to make sure, that the data reaches the server? I only can think of a service, which checks periodically for not transmitted data, but I don't like this approach.
Heres some psuedo code..
1.Create a AcknowledgementManager which waits for acknowledgement of each request.
2.The acknowledgement manager posts a runnable which will run after TIME_OUT interval.
public void startListeningForTimeOut(CallContext callContext) {
TimeOutRunnable timeOutRunnable = new TimeOutRunnable(callContext);
mMapRunnables.put(callContext, timeOutRunnable);
mHandler.postDelayed(timeOutRunnable, TIMEOUT_DURATION);
Slog.d(TAG,
"started listening for timeout for token: " + callContext + " at: "
+ System.currentTimeMillis());
}
3.1 If the AcknowledgementManager receives acknowledgement for the data it cancels the runnable for that data and make necessary updates in database.
public void stopListeningForTimeOut(CallContext callContext) {
mHandler.removeCallbacks(mMapRunnables.get(callContext));
mMapRunnables.remove(callContext);
Slog.d(TAG,
"stopped listening for timeout for token: " + callContext + " at: "
+ System.currentTimeMillis());
}
3.2 If the acknowledgement is not received the runnable raises a "timeout" to the AcknowledgementManager.The AcknowledgementManager retries sending data.
public class TimeOutRunnable implements Runnable {
private static final String TAG = "TimeOutThread rupesh";
CallContext mToken;
// String mCallback;
public TimeOutRunnable(CallContext callContext) {
mToken = callContext;
// mCallback = callbackTBD;
// FIXME send proper callback class
}
#Override
public void run() {
Slog.d(TAG, "timeout occured for data id: " + mToken + " at: " + System.currentTimeMillis());
mToken.onTimeOutOccurred();
}
}
Please note:
1.Before syncing the data please make the entry of data in db so that the data does not get lost between app restart.
2.Before you start the syncing process query the db to get the data which is not being transmitted.Based on the data size,make sure you keep the data to be transmitted in memory to avoid hitting the db everytime.
3.Once you get acknowledgement for a data item,make necessary changes for that entry in db.
4.You can also have 'retry' flag in db so that you keep track of no of retries.

Categories