I've been working on building a multiplatform libr...
# ktor
b
I've been working on building a multiplatform library targeting JVM and JS but for some reason whenever I submit requests using Ktor's JS client the response body is always empty. The server is also using Ktor and has the server Content Negotiation plugin configured with Jackson serialization, and I can confirm the server is sending seemingly valid responses, if I submit a request through just the browser's fetch command I get the expected response body but the JS client gets an empty body regardless of whether I install client Content Negotiation. Even removing content negotiation on the server and client and just trying to send String messages back and forth results in empty response bodies. This is on Kotlin 2.2.0 and Ktor 3.2.2
a
Can you please share the code where you receive the response body?
b
Copy code
private class CustomConverter(): ContentConverter {
    override suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any? {
        return JSON.parse(content.readRemaining().readText(charset))
    }

    override suspend fun serialize(
        contentType: ContentType,
        charset: Charset,
        typeInfo: TypeInfo,
        value: Any?
    ): OutgoingContent? {
        return value?.let { TextContent(JSON.stringify(value), contentType.withCharsetIfNeeded(charset)) }
    }
}

... {
	HttpClient(Js) {
    	install(ContentNegotiation) {
        	register(ContentType.Application.Json, CustomConverter())
    	}
    	install(Logging) {
        	level = LogLevel.ALL
    	}
	}.use { httpClient ->
                val response = httpClient.get {
                    url {
                        this.protocol = URLProtocol.HTTP
                        this.host = host
                        this.port = port
                        appendPathSegments(CHECKIN_URL)
                    }
                }
		if (response.status == HttpStatusCode.OK) {
                    val clientIdAndKey: ClientIdAndKey = response.body()
                    return ApiClient(clientIdAndKey)
        } else {
		    return null
		}
	}
}
a
Does it work if you receive the response body as a String with
HttpResponse.bodyAsText()
. Can you print out the response headers?
b
No,
bodyAsText()
still just returns an empty String. The
ContentType
header is applied correctly as
application/json
and it will even log as much in the
deserialize
function but
content
is always just an empty
SourceByteReadChannel
. Checking it's closed cause just returns null.
a
Can you share the endpoint's URL if it's a public server?
b
It isn't, but I've also just been running this locally on my own workstation. The endpoint itself is effectively just returning a serialized
ClientIdAndKey
which is a custom data class with a String and ByteArray.
Changing the endpoint to just return a raw String, or even a simpler serialized object (having assumed the ByteArray might've caused issues) still yielded the empty response
a
Do you have the CORS plugin installed on the server?
b
Yes
a
Did you make the request with
fetch
from the same origin as the server or from a different one?
b
From the same origin.
a
Can you please try testing it from a different origin to better compare with the Ktor/JS client?
b
Changing the origin still succeeds for the regular
fetch
for context the KtorJS client request does still succeed, it just returns an empty response. Unless CORS configurations would somehow affect the response body?
a
The headers sent by the CORS plugin may affect the browser behavior. Is there a way for me to reproduce the problem?
b
I did some further testing and modified the
ktor-samples/client-mpp
project to target my local dev server with the same endpoints I've been testing and can confirm that works when just running purely in kotlin-js by running the
jsBrowserDevelopmentRun
gradle task, but if I try and package it as a JS library and import it into a JS/TS project that's when the requests start receiving empty response bodies. So I'm left wondering if the compilation to javascript is causing the client's response parsing functions to not properly suspend or something?
Here's the JS target gradle config that should potentially reproduce it
Copy code
js(IR) {
    browser()
    binaries.library()
    useEsModules()
    generateTypeScriptDefinitions()
}