Ktor - kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed - kotlin

I am receiving back this kotlinx.coroutines.channels.ClosedReceiveChannelException upon about 50% of my api calls to a post url through Ktor HttpClient.
Our code looks like
Module.kt
bind<ServiceClient>() with singleton {
val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
ServiceClient( client, instance() )
}
and our implementation of the call is
suspend fun post(request: RequestObject): List<ResponseObjects> =
client.post(endpoint) {
contentType(ContentType.Application.Json)
body = request
}
Sometimes I am able to receive back the expected results and other times I get the client closed exception. What am I doing wrong here?
I am on Ktor version 1.6.4

Related

How to place the JSON data in a list in a Ktor server?

I have been trying to get Json data from dummyjson and place them into an Array in the ktor server. But it just won't work.
Application.kt:
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(ContentNegotiation) {
gson {
setDateFormat(DateFormat.LONG)
setPrettyPrinting()
}
}
val client = HttpClient(CIO)
val products: Array<Product> = client.get("https://dummyjson.com/products").body()
configureRouting()
}.start(wait = true)
}
I could create the client but I am not allowed to use the get method because it says Suspend function 'get' should be called only from a coroutine or another suspend function.
Then, in configureRouting.kt I defined the client and used the get method. It works and returns the String to the client.
fun Application.configureRouting() {
val client = HttpClient(CIO)
routing {
get("/get-all-products"){
val product: String = client.get("https://dummyjson.com/products").body()
call.respond(product)
}
}
However, what I want to achieve is without the client's request, the server can automatically load all the JSON file from dummyjson, and place it in a list. But it just keep giving me the same error if I place this get method outside the routing.

Ktor how to get http code from request without body

I make a request to the server, but there is no body in the response.
Accordingly, the return value type of response is Unit.
suspend fun foo(
url: String,
id: Long
) {
val requestUrl = "$url/Subscriptions?id=${id}"
val response = httpApiClient.delete<Unit>(requestUrl) {
headers {
append(HttpHeaders.Authorization, createRequestToken(token))
}
}
return response
}
How in this case to receive the code of the executed request?
HttpResponseValidator {
validateResponse { response ->
TODO()
}
}
using a similar construction and throwing an error, for example, is not an option, since one http client is used for several requests, and making a new http client for one request is strange. is there any other way out?
You can specify the HttpResponse type as a type argument instead of Unit to get an object that allows you to access the status property (HTTP status code), headers, to receive the body of a response, etc. Here is an example:
import io.ktor.client.HttpClient
import io.ktor.client.engine.apache.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
suspend fun main() {
val client = HttpClient(Apache)
val response = client.get<HttpResponse>("https://httpbin.org/get")
// the response body isn't received yet
println(response.status)
}

Any way to detect client disconnet in Ktor server

I'm trying to make a proxy server by ktor, it proxy a http-flv infinite stream, after client close, it should do something to release resource. like
client.get<HttpStatement>(url.value.url).execute { response ->
val channel: ByteReadChannel = response.receive()
while (!channel.isClosedForRead) {
logger.info("status ${call.request.receiveChannel().isClosedForRead}")
val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
logger.info("flushing...")
withContext(Dispatchers.IO) {
write(packet.readBytes())
flush()
}
}
// do something to clean resource
}
try to use router event and flush writer, didn't work

Ktor http client - request progress

How do I monitor request progress in Ktor http client?
For example: I have request like this:
val response = HttpClient().get<String>("https://stackoverflow.com/")
and I want to monitor request progress with progress bar like this:
fun progress(downloaded: Long, contentLength: Long) {
// Update progress bar or whatever
}
How do I set progress() to be called by HttpClient?
edit: This is Kotlin Multiplatform project. Relevant dependencies are:
implementation 'io.ktor:ktor-client-core:1.2.5'
implementation 'io.ktor:ktor-client-cio:1.2.5'
Starting with Ktor 1.6.0, you can react to download progress change using the onDownload extension function exposed by HttpRequestBuilder:
val channel = get<ByteReadChannel>("https://ktor.io/") {
onDownload { bytesSentTotal, contentLength ->
println("Received $bytesSentTotal bytes from $contentLength")
}
}
There is also the onUpload function that can be used to display upload progress:
onUpload { bytesSentTotal, contentLength ->
println("Sent $bytesSentTotal bytes from $contentLength")
}
Here are runnable examples from the Ktor documentation:
download-file
upload-file
How to emit the download progress into a Flow?
I want to observe the download progress by a Flow, so I write a function like this:
suspend fun downloadFile(file: File, url: String): Flow<Int>{
val client = HttpClient(Android)
return flow{
val httpResponse: HttpResponse = client.get(url) {
onDownload { bytesSentTotal, contentLength ->
val progress = (bytesSentTotal * 100f / contentLength).roundToInt()
emit(progress)
}
}
val responseBody: ByteArray = httpResponse.receive()
file.writeBytes(responseBody)
}
}
but the onDownload will be called only once. If I remove the emit(progress) it will work.
#andrey-aksyonov

kotlin: retrofit2 getting 404 url not found error

Getting Response{protocol=http/1.1, code=404, message=Not Found, url=https://test.test.com/service/one}
The url is correct as postman works fine.
I have tried looking into this error but most things come back with URL was in correct. and the error itself is vague.
code that starts it. the builder is a json string that is valid. I have tested it in postman.
CoroutineScope(Dispatchers.Default).launch {
val call = submitService.submitCarton(builder.toString())
Log.d("submit", "begining")
withContext(Dispatchers.Main) {
if (call.isSuccessful) {
Log.d("submit",call.body() as String)
} else {
Log.d("submit", "else....")
}
}
}
service factory:
fun makeSubmitService() : SubmitService{
val url = "https://test.test.com/service/"
return Retrofit.Builder().baseUrl(url)
.client(okHttpClient).addConverterFactory(GsonConverterFactory.create())
.build().create(SubmitService::class.java)
}
interface:
interface SubmitService {
#POST("one")
suspend fun submitCarton(#Body json: String): Response<myModel>
}
Expected results are a json response however I am not getting that far.
edit: I created a okhttpclient and did a request manual and I get a message 200 ok.
code for my test
val JSON = MediaType.parse("application/json; charset=utf-8")
val client = OkHttpClient()
val body = "some json"
val requestBody = RequestBody.create(JSON, body)
val request = Request.Builder()
.url("https://test.test.com/service/one")
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(request: Request, e: IOException) {
Log.e("test", e.toString())
}
#Throws(IOException::class)
override fun onResponse(response: Response) {
Log.d("test", response.toString())
}
})
Solved it myself.
Issue was dumb, retrofit2 was giving 404 even though the web service was returning a error message.
added
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1'
private val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
private val okHttpClient = OkHttpClient().newBuilder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.build()
found out retrofit was sending a very unformatted string
"{ \"all my json filled with \" }"
instead of
{ json }
fixed it by adding
.addConverterFactory(ScalarsConverterFactory.create())
to my service factory
for anyone wondering why I am basically creating the json as a string instead of using a JSON object is because the service I talk to really really wants it to be in a very specific order which JSON just don't care about it however it wants it to look like JSON as well...