ktor websocket listener for new message - ktor

Is there a way to run some code each time a ktor websocket receives a new message, kind of like onTouchEvent in Android views?
Currently I check for new messages and process them in a while loop as follows:
while (websocketIsOpen) {
val newMessage = session!!.incoming.receive()
processMessage(newMessage)
}
where session is a DefaultClientWebsocketSession?, and processMessage is a method that has a Frame as a parameter.
I would prefer to set code that runs each time a new message is received, something like the following:
session!!.incoming.onNewMessage = { newMessage ->
processMessage(newMessage)
}

You can use consumeEach method of the incoming frames channel to run some code when a frame is received:
webSocket("/") {
incoming.consumeEach { frame ->
if (frame is Frame.Text) {
println("They say ${frame.readText()}")
}
}
}

Related

Ktor server, a correct way to receive messages from websocket

I'm new in Ktor-server and don't fully understand how web sockets receive messages. I found several solutions in different sources. (try\catch and webscoket blocks are omitted)
while(true) way
while(true){
val incoming = receiveDeserialized<IncomingDto>()
MessageService.newMessage(incoming)
}
consumeEach way
incoming.consumeEach { frame ->
// process frame
}
flow way
incoming.receiveAsFlow().filterIsInstance<Frame.Text>()
.collect{
// process frame
}
for way
for (frame in incoming){
frame as? Frame.Text ?: continue
// process frame
}
Which way is correct? Or do they do the same thing?
Second question. Should I need to use async{} inside the receive block so as not to block the receive channel. For example
while(true){
val incoming = receiveDeserialized<IncomingDto>()
async{
println("starting heavy task")
// heavy task
delay(500)
println("task complete")
}
}
These are different methods of working with ReceiveChannel so use one that better suits your needs. The difference between consumeEach and for loop is described here. In the while(true) example you receive and deserialize (using an installed content converter) a frame. In the flow example receiveAsFlow is used
to represent the given receive channel as a hot flow.
Yes. You can use async or launch to not block receiving other frames.

TCP/IP Client in Kotlin that does not wait forever for server message

I have simple TCP/IP client code in Kotlin below.
This code works.
The client opens the socket and loops forever, first sending a message to the server, and then waiting forever for a response form the server.
I know this code isn’t great, looping forever, keeping the socket connection open etc., but it is just for test purposes right now.
fun tcp_client() {
thread {
val client1 = Socket(SERVER_IP_ADDRESS, SERVER_IP_PORT)
val output1 = PrintWriter(client1.getOutputStream(), true)
val input1 = BufferedReader(InputStreamReader(client1.inputStream))
while (true) {
output1.println(str_user_text)
str_rcvd_data = input1.readLine()
}
}
client1.close()
}
The line:
str_rcvd_data = input1.readLine()
waits forever for a server response.
My question: Is it possible to modify this code so that the client does NOT wait forvever for a server response? Something like this:
If (server data received) {
// process the data
} else {
// do something else for now and check again in a short while
}
Thanks in advance for any suggestions
Garrett
I eventually worked this out - I am not sure how 'correct' this solution is, but it works for me:
Connecting to the server....
My old code would hang if it couldn't connect, because the call to Socket() with the IP address and Port is a Blocking call - i.e.e wait forever:
val client1 = Socket(SERVER_IP_ADDRESS, SERVER_IP_PORT)
So I replaced the code with this:
try {
client1 = Socket()
client1.connect(InetSocketAddress(SERVER_IP_ADDRESS, SERVER_IP_PORT), 3000)
output1 = DataOutputStream (client1.getOutputStream())
input1 = DataInputStream (client1.getInputStream())
} catch (ex : Exception) {
// do something
} finally {
// do something
}
This isn't perfect, but it works.
For reading the data, my old code called readline() which is blocking:
str_rcvd_data = input1.readLine()
Now, my code first checks if there is any data and then grabs each byte
iRxDataAvailable = input1.available()
while (iRxDataAvailable > 0)
{
iRxDataAvailable--
// Take a copy of the received byte
byRcvdByte = input1.readByte()
// Do something with this data byte...
}
Finally, to send data to the server, the data is placed in a byte array, and then:
output1.write(byArray)

How can I get a non-blocking infinite loop in a Kotlin Actor?

I would like to consume some stream-data using Kotlin actors
I was thinking to put my consumer inside an actor, while it polls in an infinite loop while(true). Then, when I decide, I send a message to stop the consumer.
Currently I have this:
while(true) {
for (message in channel){ <--- blocked in here, waiting
when(message) {
is MessageStop -> consumer.close()
else -> {}
}
}
consumer.poll()
}
The problem
The problem with this is that it only runs when I send a message to the actor, so my consumer is not polling the rest of the time because channel is blocking waiting to receive the next message
Is there any alternative?, someone with the same issue?, or something similar to actors but not blocked by channel in Kotlin?
Since the channel is just a Channel (https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html) you can first check if the channel is empty and if so start your polling. Otherwise handle the messages.
E.g.
while(true) {
while (channel.isNotEmpty()) {
val message = channel.receive()
when(message) {
is MessageStop -> consumer.close()
else -> {}
}
}
consumer.poll()
}
In the end I used AKKA with Kotlin, I'm finding much easier this way
You should use postDelayed(), for example:
final Runnable r = new Runnable() {
public void run() {
// your code here
handler.postDelayed(this, 1000)
}
}
You can change 1000 with the the millisecond delay you want. Also I highly recommend to put your code inside a thread (if you are not already have) to prevent ANR (App Not Responding)

How Do I get total number of records in Flux of server side events

I'm using reactive programming where the client receives a flux of event streams of Server side events then those events are consumed. Functionality wise it works. I've a problem when I try to log the count of total records in the the flux stream. Below are the code snippets.
Let's create an instance connected to the server
final WebClient client = WebClient
.builder()
.baseUrl(url)
.build();
And then we start the connection, subscribing to its topic
final Flux<ServerSentEvent<SomeEvent>> eventStream = client.get()
.uri("/bus/sse?id=" + subscription)
.accept(MediaType.TEXT_EVENT_STREAM)
.exchange()
.flatMapMany(response -> response.bodyToFlux(type))
.repeat();
log(eventStream, "connectSSE");
eventStream
.doOnError(throwable -> this.onError(throwable, eventStream))
.doOnComplete(() -> this.onComplete(eventStream));
subscribe = eventStream.subscribe(someServerSideEvent -> this.onEvent(someServerSideEvent , eventStream));
The below method handles the event
private void onEvent(final ServerSentEvent<SomeEvent> content, Flux<ServerSentEvent<SomeEvent>> eventStream) {
log(eventStream, "onEvent");
//Code for handling event
}
I've a issue with the below piece of code. Actually I want to log the count of records in the stream and I was expecting it will print some numbers but it prints something like below. Need some solution without using .block(). Any help is welcome.
"Counted values in this Flux: MonoMapFuseable, caller onEvent"
private void log1(Flux<ServerSentEvent<SomeEvent>> eventStream, final String caller) {
try {
eventStream.count().map(count -> {
LOGGER.info("Counted values in this Flux: {}, caller {}", count.longValue(), caller);
return count;
});
} catch (final Exception e) {
LOGGER.info("Counted values in this Flux failed", e);
}
}

Open Android activity automatically on receiving notification

I have to launch the app on receiving notification. The following piece of code works fine when the app is killed and notification is received (i.e the code inside if condition). But when the app is running in foreground or background, multiple instances of the activity gets created(i.e snippet in else condition). It's not the MainActivity that has to be launched on receiving the notification, instead it's some other activity containing the broadcast Receiver.
I have added the following lines in the onMessage of GCMintentService class.
if (currentPackage.equalsIgnoreCase(context.getPackageName()
.toString())) {
broadcastMessage(context, message);
} else {
Intent mIntent = new Intent(context, MainActivity.class);
mIntent.setAction(Intent.ACTION_MAIN);
mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mIntent);
}
In the activity, under onReceive method of BroadcastReceiver, i am starting the activity again.
private final BroadcastReceiver mHandleMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
startActivity(getIntent().setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));
}
};
I also had this requirement in one of my application. We can achieve it if we call
Intent mIntent = new Intent(context, MainActivity.class);
mIntent.setAction(Intent.ACTION_MAIN);
mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mIntent);
broadcastMessage(context, message);
In the main activity use the following in the broadcast receiver that would receive the broadcasted message above.
WakeLock wakeLock = null;
KeyguardManager kgMgr = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
boolean locked = kgMgr.inKeyguardRestrictedInputMode();
PowerManager pm = (PowerManager) context
.getSystemService(Context.POWER_SERVICE);
if (!pm.isScreenOn()) {
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK
| PowerManager.ACQUIRE_CAUSES_WAKEUP, "MyWakeLock");
wakeLock.acquire();
}
if (locked) {
Window mWindow = getWindow();
mWindow.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}
Personally I feel that this is not the best of the answers and also best of the ideas to open the app directly when received a notification as there will be many functions like onCreate onResume, will be triggered automatically, spoil the users work if they are in a really important work by opening another app directly, also we need to put a lot of flags or use any other method to manage the flow of the application, when user open the app, app comes from background, app opened due notification and all such cases. Avoid it as it spoils the whole user experience.