NoSuchMethod exception when requesting file using Ktor - kotlin

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)

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.

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

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.

How to ignore request body and redirect request using akka-http

Using latest akka-http I want to implement an endpoint which will redirect all incoming upload file requests without consuming request entity.
Current implementation is using :
put {
extractRequest { r: HttpRequest =>
onComplete(r.discardEntityBytes().future) { done =>
redirect(Uri("http://example.com"), TemporaryRedirect)
}
}
}
The problem is that it waits until whole http request body is received (discarded) and only after that sends redirect response. From client perspective it means uploading file twice. I tried to add withSizeLimit(0) to request entity, but it introduces early response problem.
Related documentation:
http://doc.akka.io/docs/akka/2.4.11/scala/http/implications-of-streaming-http-entity.html
http://doc.akka.io/docs/akka/2.4.11/scala/http/routing-dsl/directives/route-directives/redirect.html#description
I did something similar, today. Are you sure that the discarding needs to be completed prior to the redirect?
How about this:
put {
extractRequest { r: HttpRequest =>
r.discardEntityBytes() // runs wild
redirect(Uri("http://example.com"), TemporaryRedirect)
}
}
.discardEntityBytes is used to try to "cancel" unnecessary network traffic that the receiver won't need. Here, we set it up asap. It runs on the background (I hope), and returning the redirect would happen also asap.
I'd be interested to hear, whether this changed anything...

Handling of Thumbnails in Google Drive Android API (GDAA)

I've run into the following problem when porting an app from REST API to GDAA.
The app needs to download some of (thousands of) JPEG images based on user selection. The way this is solved in the app is by downloading a thumbnail version first, using this construct of the REST API:
private static InputStream getCont(String rsid, boolean bBig){
InputStream is = null;
if (rsid != null) try {
File gFl = bBig ?
mGOOSvc.files().get(rsid).setFields("downloadUrl" ).execute():
mGOOSvc.files().get(rsid).setFields("thumbnailLink").execute();
if (gFl != null){
GenericUrl url = new GenericUrl(bBig ? gFl.getDownloadUrl() : gFl.getThumbnailLink());
is = mGOOSvc.getRequestFactory().buildGetRequest(url).execute().getContent();
}
} catch (UserRecoverableAuthIOException uraEx) {
authorize(uraEx.getIntent());
} catch (GoogleAuthIOException gauEx) {}
catch (Exception e) { }
return is;
}
It allows to get either a 'thumbnail' or 'full-blown' version of an image based on the bBig flag. User can select a thumbnail from a list and the full-blown image download follows (all of this supported by disk-base LRU cache, of course).
The problem is, that GDAA does not have an option to ask for reduced size / thumbnail version of an object (AFAIK), so I have to resort to combining both APIs, which makes the code more convoluted then I like (bottom of the page). Needles to state that the 'Resource ID' needed by the REST may not be immediately available.
So, the question is: Is there a way to ask GDAA for a 'thumbnail' version of a document?
Downloading thumbnails isn't currently available in the Drive Android API, and unfortunately I can't give a timeframe to when it will be available. Until that time, the Drive Java Client Library is the best way to get thumbnails on Android.
We'd appreciate if you go ahead and file a feature request against our issue tracker: https://code.google.com/a/google.com/p/apps-api-issues/
That gives requests more visibility to our teams internally, and issues will be marked resolved when we release updates.
Update: I had an error in the discussion of the request fields.
As Ofir says, you can't get thumbnails with the Drive Android API and you can get thumbnails with the Drive Java Client Library. This page has is a really good primer for getting started:
https://developers.google.com/drive/v3/web/quickstart/android
Oddly, I can't get the fields portion of the request to work as it is on that quick start. As I've experienced, you have to request the fields a little differently.
Since you're doing a custom field request you have to be sure to add the other fields you want as well. Here is how I've gotten it to work:
Drive.Files.List request = mService.files()
.list()
.setFields("files/thumbnailLink, files/name, files/mimeType, files/id")
.setQ("Your file param and/or mime query");
FileList files = request.execute();
files.getFiles(); //Each File in the collection will have a valid thumbnailLink
A sample query might be:
"mimeType = 'image/jpeg' or mimeType = 'video/mp4'"
Hope this helps!