What is the correct way to pipe a Ktor client response to a server response? - ktor

I'm trying to pipe a client response from the Ktor client library as a Ktor server response using the following code:
serviceClient.retrieveCourseZip(id, version).execute { response ->
call.respond(object : OutgoingContent.ReadChannelContent() {
override val contentType = response.contentType()
override val contentLength = response.contentLength()
override val status = response.status
override val headers = headersOf(
HttpHeaders.ContentDisposition to listOf("attachment; filename=\"course-$courseId.zip\"")
)
override fun readFrom() = response.content
})
}
response is a io.ktor.client.statement.HttpResponse returned by HttpStatement.execute.
The reason I'm doing this is that the backend service I'm developing with Ktor is a gateway that enforces authentication for clients coming from the open internet towards services that are behind this gateway.
Most of the time this code works nicely but I'm starting to have some weird behavior where clients that are downloading large amounts of data abruptly disconnect in the middle of the download due to unexpected end of stream errors on the client side. On the server side there are no error messages whatsoever and it looks like a graceful connection close.
Is there anything that is fundamentally broken with this code? I'm running this code in Amazon EKS environments where some have zero problems while others are absolutely broken.

From your code, it looks like your execute method is asynchronous. Ktor, on the other hand, is built around coroutines, so you need to make the coroutine wait for execute to finish. This can be done with suspendCoroutine, for example:
val result = suspendCoroutine<OutgoingContent.ReadChannelContent> { continuation ->
serviceClient.retrieveCourseZip(id, version).execute {
continuation.resume(object : OutgoingContent.ReadChannelContent() { /*...*/ })
}
}
call.respond(result)
Another option is to make execute a suspend function that will return the result itself. If asynchronous code is used inside - this can be done exactly the same way with suspendCoroutine, but you say you are using a Ktor client that has to do all requests with coroutines, so your code can probably be simplified, but it is hard to tell without looking at it.

Related

Kotlin for Volley, how can I check the JSON request for newer data in the API?

I'm working on an app that gets a list of documents/source URL from an api. I'd like to periodically check for new or updated contents within that API so users can update saved items in the database. I'm at a loss on the correct wording to search, thus Google and Stack Overflow have both failed me. My fetching function is below:
The URL for the API is https://api.afiexplorer.com
private fun fetchPubs() {
_binding.contentMain.loading.visibility = View.VISIBLE
request = JsonArrayRequest(
Request.Method.GET,
Config.BASE_URL,
JSONArray(),{ response ->
val items: List<Pubs> =
Gson().fromJson(response.toString(), object : TypeToken<List<Pubs>>() {}.type)
val sortedItems = items.sortedWith(compareBy { it.Number })
pubsList?.clear()
pubsList?.addAll(sortedItems)
// Hardcoded pubs moved to Publications Gitlab Repo
// https://gitlab.com/afi-explorer/pubs
_binding.contentMain.recyclerView.recycledViewPool.clear()
adapter?.notifyDataSetChanged()
_binding.contentMain.loading.visibility = View.GONE
setupData()
Log.i("LENGTH OF DATA", "${items.size}")
},
{error ->
println(error.printStackTrace())
Toasty.error(applicationContext, getString(string.no_internet), Toast.LENGTH_SHORT, true).show()
}
)
MyApplication.instance.addToRequestQueue(request!!)
}
private fun setupData(){
adapter = MainAdapter(applicationContext, pubsList!!, this)
_binding.contentMain.recyclerView.adapter = adapter
}
I tried using ChatGPT to see if that would get me started and that failed miserably. Also searched Google, Reddit and Stack Overflow for similar projects, but mine is a unique scenario I guess. I'm just a hobbyist and intermediate dev I guess. First time working with Volley, everything works, but I would like to find a way to send a notification (preferably not Firebase) if there is updated info within the API listed above. I'm not sure if this is actually doable.
Are you asking if you can somehow find if the remote API has changed its content? If so, how would that service advise you? If the service provider provides a web hook or similar callback you could write a server-based program to send a push notification to your Android app.
Perhaps you intent to poll the API periodically, and then you want to know if there is a change?
If you use a tool such as Postman or curl to easily see the headers of the API https://api.afiexplorer.com you will see, unfortunately, there is no Last-Modified header or ETag header which would allow you to easily determine if there was a change.
Next looking at the content of the API, the author does not provide an obvious version/change date, so no luck there.
What you could do is receive the content as a String, and perform a checksum operation on it, and if it differs you know there has been a change
or if you are deserialising the received JSON in Kotlin data classes, then out of the box, Kotlin will enable you to perform an equality operation on a previous copy of the data to know if there was a change.
This looks like an android app; if so, why don't you create a background service that makes requests to the API and updates the data as needed? You can use an AlarmManager class to set the interval threshold for polling by using the setInexactRepeating() method.
Most apps are updated in this fashion; sometimes, a separate table is created to catalog changesets.
Let me know if this helps.

Is it possible to use websockets to send a request and get a response right after (without the publisher/subscriber structure)?

I am using spring-websocket together with Kotlin Multiplatform, SockJS and StompJS.
I've got my application up and running, mainly using this tutorial from Spring. The frontend is able to send messages to the backend (via the #MessageMapping annotation), which responds back to frontend (via the #SendTo annotation).
The frontend code currently looks like this:
val socket = SockJS("http://localhost:8080/my-app")
val stompClient = Stomp.over(socket)
stompClient.connect(json(), { frame ->
stompClient.subscribe("/topic/a-topic", { message ->
// message handling code
})
})
stompClient.send("/app/hello", null, "")
The subscription approach is useful when an action from some user needs to affect all users (like sending a message in a group chat). However, there are times when some actions should affect only the user who started the action, and that is my problem.
Ideally, I would like to do something to the likes of this:
val socket = SockJS("http://localhost:8080/my-app")
val stompClient = Stomp.over(socket)
stompClient.connect(json(), {})
stompClient.send("/app/hello", null, "").then { response ->
// response handling code
}
But I don't know if it is possible.
I also thought about having a REST API handle this kind of request, but it doesn't seem optimal since there's the existing websocket connection already.
Any enlightment on the matter is appreciated.

Kotlin ktor library don't wait for all coroutines to finish in websockets handler

Recently I was trying to implement simple web-socket application with Kotlin and ktor library. My server just has single web-socket handler which I implement like the following:
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(WebSockets)
routing {
webSocket("/handle") {
// ... more domain specific logic which uses coroutines ...
launch {
for (message in incoming) {
// process message
}
}
}
}
}
Original code contains more logic which include starting a bunch of another coroutines - so launch-ing the incoming queue processing in separate coroutine doesn't look weird for me.
Unfortunally, the ktor closes the underlying web-socket connection as soon, as inner block function finished. This was unexpected behaviour for me, because I though that webSocket function will behave similar to coroutineScope standard function, which waits for all attached coroutines to finish before continue execution.
This bug was very hard to spot for me, so I want to understand the design of webSocket function more deeply. So, I have the following questions:
Why webSocket function don't wait for attached coroutines? Is it a bug or a intentional decision?
What is the conventions about functions that deal with coroutineScope-s? Should I always guard my coroutines with known standard library functions (like coroutineScope)? Or should library writers follow some guidelines - for example, always wait for all attached coroutines in scope to finish?
This behavior was implemented long ago without structured concurrency in mind. I'd say it's a problem that can be fixed on the Ktor side so I've created this issue to address it. A fix will make it easier to write Websocket handlers and will improve readability.
It depends on whether library maintainers consider supporting structured concurrency or not for a particular case.

NoSuchMethod exception when requesting file using Ktor

I'm using Ktor Client 1.6.5 to ask for files to an API. Every time I tried to GET the resource, I get this exception:
java.lang.NoSuchMethodError: java.nio.ByteBuffer.limit(I)Ljava/nio/ByteBuffer;
It seems to be related to this issue.
I'm changing a lot the code (mostly the get response) but mainly it is like that:
val client = HttpClient(CIO)
val response = client.get<HttpResponse>("url") {
headers {
append(HttpHeaders.Accept, "application/zip")
}
}
Basically I can't even handle the response (or its content).
EDIT: the files exposed by the API are not large (usually less than 1Mb)

How to write request and response code with Kotlin Flows?

Problem Description
My job is very async IO heavy so a lot of what we do is requesting a value and then listening for the response. Something like
connection.send(GetServices(...)))
connection.receive<GetServicesResponse>()
Now if I did this in Kotlin with suspend functions, I could get incorrect results because the message might have been received to quickly.
connection.send(GetServices(...)))
// Recevied GetServiceResponse Here <---------------
connection.receive<GetServicesResponse>()
// Times out because it never got to see the response
However, if I flip it now I will never send the request lol. So that just straight up won't work.
connection.receive<GetServicesResponse>() // Timeout because we never send the request
connection.send(GetServices(...)))
So then you go "OK I will just launch/async" but no matter which way you do that you still have this problem that you can never be sure the listener is actually listening. There is no way to tell that a suspend function is at the point it should be and is listening.
val response = async { connection.receive<GetServicesResponse>() }
connection.send(GetServices(...)))
response.await()
This can still fail because when we call async it doesn't guarantee the job has ran. So the job could still be scheduled to run by the time we receive the request.
launch { connection.send(GetServices(...))) }
connection.receive<GetServicesResponse>()
This can still fail because when we call launch it could run almost immediately if there are not a lot of jobs and multiple CPUs. Meanwhile, the thread running this code could get suspended by the OS. Kotlin Coroutines are nice but the OS can still stop any thread it feels like.
To fix this, I use UNDISPATCHED so that I guarantee that a launch runs until it suspends.
val response = async(start = CoroutineStart.UNDISPATCHED) {
connection.receive<GetServicesResponse>()
}
connection.send(GetServices(...)))
response.await()
This works but only in simple cases. If an engineer does something that causes a suspend before the listen then I am right back to same problem. The code above is both an example of working and not working code at the same time depending on the implementation of connection.receive. This gets worse when I start trying to use flows to receive data. Flow operations like merge or launchIn end up launching coroutines. So you can have coroutines launching coroutines so something like UNDISPATCHED doesn't appear to be sufficient. The only reason I know that is I actually tested it. Then again my code could be wrong.
Question
So the question is how do I guarantee listening? It seems like I can't with Kotlin Coroutines and flows?
Attempts
It seems like with RxJava I could, because I know when subscribe is called then it went up the entire chain. Once subscribe returns, that Observable is live. However, flows do not work like that in this regard. collect aka subscribe both suspends and eventually the flow starts listening so you have no way to know for sure.
I have thought of literally sending a "START" element on a flow to say it is live. However, you can get into the exact same situation.
flow {
emit("START")
emitAll(realFlow)
}
The OS can suspend my thread between the "START" and the emitAll(realFlow).
chanelFlow {
launch { realFlow.collect { send(it) } }
send("START")
}
Is right back to the same problem above. The job might not have run. So you launch undispatched.
chanelFlow {
launch(start = CoroutineStart.UNDISPATCHED) {
realFlow.collect { send(it) }
}
send("START")
}
But again, this is brittle. For all I know the realFlow has merges of it's own that are going to be scheduled and not executed. This has almost lead me back to using listeners. With a listener, I know I added them to the list of other listeners. No suspension. That seems like a huge step backwards and would make me wonder why I didn't just use RxJava.
If you got to the end. Thank you for reading my problem. I appreciate any attempt to help me.
I have been facing this problem several times as well. If the API is designed this way, there is unfortunately little you can do about it.
Just like you I ended up using UNDISPATCHED in simple cases where I knew one suspension point would be sufficient:
val response = async(start = CoroutineStart.UNDISPATCHED) {
connection.receive<GetServicesResponse>()
}
connection.send(GetServices(...)))
response.await()
When you need to make sure it can go through several suspension points, there is always the option of adding a small delay, but it's dirty, slow and doesn't 100% guarantee anything either:
val response = async {
connection.receive<GetServicesResponse>()
}
delay(50)
connection.send(GetServices(...)))
response.await()
But honestly the best is to have a better API. For instance, in my own STOMP library Krossbow, I have designed the subscribe() as suspend functions that return Flow. It might be unconventional but the API guarantees that after resuming the subscription has been made. The user can then send a request, and collect the Flow afterwards anyway without losing events.