Changelog 3.0 version
3.0.3
released 19th December 2024
Client
Darwin: "IllegalStateException: Content-Length mismatch" on requesting gzipped content
We recently updated our ktor-client dependencies in our Kotlin Multiplatform project from 3.0.1 to 3.0.2.
One of our backend calls started failing only on iOS:
kotlin.IllegalStateException: Content-Length mismatch: expected 1231 bytes, but received 5043 bytes
This seems to be thrown from the checkContentLength function in ktor, called from SavedHttpCall.
It still works fine on Android.
I checked the call and the backend does return 5043 characters in this response, but the Content-Length header is 1231 because the content is gzipped:
It seems like the iOS check for the content length does not take gzip into account?
Sounds very similar to this issue:
https://youtrack.jetbrains.com/issue/KTOR-7934
"Module not found" errors when executing browserProductionWebpack task since 3.0.2
To reproduce run the ./gradlew frontendBrowserProductionWebpack
command on the attached sample project.
As a result, the following errors occur:
> Task :frontendBrowserProductionWebpack FAILED
Module not found: Error: Can't resolve 'zlib' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "zlib": require.resolve("browserify-zlib") }'
- install 'browserify-zlib'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "zlib": false }
Module not found: Error: Can't resolve 'stream' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Did you mean './stream'?
Requests that should resolve in the current directory need to start with './'.
Requests that start with a name are treated as module requests and resolve within module directories (node_modules).
If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "stream": require.resolve("stream-browserify") }'
- install 'stream-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "stream": false }
Module not found: Error: Can't resolve 'net' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'tls' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'crypto' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "crypto": require.resolve("crypto-browserify") }'
- install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "crypto": false }
Module not found: Error: Can't resolve 'stream' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Did you mean './stream'?
Requests that should resolve in the current directory need to start with './'.
Requests that start with a name are treated as module requests and resolve within module directories (node_modules).
If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "stream": require.resolve("stream-browserify") }'
- install 'stream-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "stream": false }
Module not found: Error: Can't resolve 'http' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "http": require.resolve("stream-http") }'
- install 'stream-http'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "http": false }
Module not found: Error: Can't resolve 'https' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "https": require.resolve("https-browserify") }'
- install 'https-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "https": false }
Module not found: Error: Can't resolve 'net' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'tls' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'crypto' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "crypto": require.resolve("crypto-browserify") }'
- install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "crypto": false }
Module not found: Error: Can't resolve 'https' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "https": require.resolve("https-browserify") }'
- install 'https-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "https": false }
Module not found: Error: Can't resolve 'http' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "http": require.resolve("stream-http") }'
- install 'stream-http'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "http": false }
Module not found: Error: Can't resolve 'net' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'tls' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'crypto' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "crypto": require.resolve("crypto-browserify") }'
- install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "crypto": false }
Module not found: Error: Can't resolve 'stream' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Did you mean './stream'?
Requests that should resolve in the current directory need to start with './'.
Requests that start with a name are treated as module requests and resolve within module directories (node_modules).
If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "stream": require.resolve("stream-browserify") }'
- install 'stream-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "stream": false }
Module not found: Error: Can't resolve 'url' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "url": require.resolve("url/") }'
- install 'url'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "url": false }
Module not found: Error: Can't resolve 'bufferutil' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'utf-8-validate' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':frontendBrowserProductionWebpack'.
> Module not found: Error: Can't resolve 'zlib' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "zlib": require.resolve("browserify-zlib") }'
- install 'browserify-zlib'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "zlib": false }
Module not found: Error: Can't resolve 'stream' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Did you mean './stream'?
Requests that should resolve in the current directory need to start with './'.
Requests that start with a name are treated as module requests and resolve within module directories (node_modules).
If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "stream": require.resolve("stream-browserify") }'
- install 'stream-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "stream": false }
Module not found: Error: Can't resolve 'net' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'tls' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'crypto' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "crypto": require.resolve("crypto-browserify") }'
- install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "crypto": false }
Module not found: Error: Can't resolve 'stream' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Did you mean './stream'?
Requests that should resolve in the current directory need to start with './'.
Requests that start with a name are treated as module requests and resolve within module directories (node_modules).
If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "stream": require.resolve("stream-browserify") }'
- install 'stream-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "stream": false }
Module not found: Error: Can't resolve 'http' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "http": require.resolve("stream-http") }'
- install 'stream-http'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "http": false }
Module not found: Error: Can't resolve 'https' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "https": require.resolve("https-browserify") }'
- install 'https-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "https": false }
Module not found: Error: Can't resolve 'net' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'tls' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'crypto' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "crypto": require.resolve("crypto-browserify") }'
- install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "crypto": false }
Module not found: Error: Can't resolve 'https' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "https": require.resolve("https-browserify") }'
- install 'https-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "https": false }
Module not found: Error: Can't resolve 'http' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "http": require.resolve("stream-http") }'
- install 'stream-http'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "http": false }
Module not found: Error: Can't resolve 'net' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'tls' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'crypto' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "crypto": require.resolve("crypto-browserify") }'
- install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "crypto": false }
Module not found: Error: Can't resolve 'stream' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Did you mean './stream'?
Requests that should resolve in the current directory need to start with './'.
Requests that start with a name are treated as module requests and resolve within module directories (node_modules).
If changing the source code is not an option there is also a resolve options called 'preferRelative' which tries to resolve these kind of requests in the current directory too.
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "stream": require.resolve("stream-browserify") }'
- install 'stream-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "stream": false }
Module not found: Error: Can't resolve 'url' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "url": require.resolve("url/") }'
- install 'url'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "url": false }
Module not found: Error: Can't resolve 'bufferutil' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Module not found: Error: Can't resolve 'utf-8-validate' in '/Users/Aleksei.Tirman/projects/ktor-samples/fullstack-mpp/build/js/node_modules/ws/lib'
Installing HttpCache before ContentEncoding prevents response body to be decoded
Hi! In my multiplatform project, I used both HttpCache
and ContentEncoding
gzip at the same time for the http client settings (the backend API that I called support gzip compression and use cache-control
private with max-age
non zero).
But I found that since upgrading from version 2.3.12 to 3.0.1 (I've also tried version 3.0.0 both has the same issue), the response body from cached content is not gzip decompressed anymore.
This is the screenshot of the printed response body that is failed to be decompressed
Below is the code that I use
private class ApiClient {
private val httpClient = HttpClient {
install(HttpCache)
install(ContentEncoding) {
gzip()
}
}
suspend fun request() {
val response = httpClient.get(API_ENDPOINT)
val responseBody = response.bodyAsText()
println(responseBody)
}
}
fun main() = runBlocking {
val api = ApiClient()
api.request() // first call, haven't been cached, body decompressed
api.request() // second call, from cache, body NOT decompressed
}
After trying to debug the code turns out that the decoding function in ContentEncoding.kt
inside on(ReceiveStateHook) { ... }
is never called if the returning response is cached response. Breakpoint in the image below is never called.
{width=70%}
Interestingly, if I reorder the plugin installation, by installing HttpCache
after ContentEncoding
, this issue seems to be fixed.
private val httpClient = HttpClient {
install(ContentEncoding) {
gzip()
}
// if cache is installed last, the issue is fixed
install(HttpCache)
}
Core
A Performance issue reading with ByteReadChannel.readUTF8LineTo request body
After migrating from Ktor 2.3.12 to 3.0.2 some of my tests fail with a request timeout.
The profiler revealed that most of the time is spent at the indexOf call in ByteReadChannel.readUTF8LineTo.
File is not commited after closing writeChannel() of the file
To reproduce run the following code:
val ch = File("abc.txt").writeChannel()
ch.writeStringUtf8("123\n")
ch.flushAndClose() // ch.close(null) in Ktor 2.3.12
println(File("abc.txt").length()) // prints 0 with Ktor 3.0.1 and 4 with Ktor 2.3.12
I expect that the file would have been commited after closing the channel.
Docs
Retrying failed requests: a broken link to HttpRequestRetry.Configuration
In the client retry page theres a broken link:
"You can learn more about supported configuration options from HttpRequestRetry.Configuration."
Infrastructure
Curl client 3.0.3 not available at maven central
Only the io.ktor:ktor-client-curl
dependency is available at maven central. The platform dependencies are only available with version 3.0.2
.
Maven central repository overview
Following dependencies are missing for version 3.0.3
:
io.ktor:ktor-client-curl-mingwx64
io.ktor:ktor-client-curl-macosx64
io.ktor:ktor-client-curl-macosarm64
io.ktor:ktor-client-curl-linuxx64
io.ktor:ktor-client-curl-linuxarm64
Test Infrastructure
TestApplication.stop() doesn't stop the application anymore
After calling TestApplication.stop()
, since Ktor 3.0.0, the application isn't completely stopped anymore:
Coroutine "coroutine#11":StandaloneCoroutine{Active}@5811b56, state: SUSPENDED
at io.ktor.server.engine.EngineContextCancellationHelperKt$launchOnCancellation$1.invokeSuspend(EngineContextCancellationHelper.kt:38)
at _COROUTINE._CREATION._(CoroutineDebugging.kt:30)
at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:161)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:43)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)
at io.ktor.server.engine.EngineContextCancellationHelperKt.launchOnCancellation(EngineContextCancellationHelper.kt:35)
at io.ktor.server.engine.EngineContextCancellationHelperKt.stopServerOnCancellation(EngineContextCancellationHelper.kt:21)
at io.ktor.server.testing.TestApplicationEngine.start(TestApplicationEngine.kt:147)
at io.ktor.server.engine.EmbeddedServer.start(EmbeddedServerJvm.kt:293)
at io.ktor.server.engine.EmbeddedServer.start$default(EmbeddedServerJvm.kt:266)
at io.ktor.server.testing.TestApplication.start(TestApplication.kt:76)
at io.ktor.server.testing.client.DelegatingTestClientEngine.execute(DelegatingTestClientEngine.kt:48)
Reproduction:
- Clone
https://gitlab.com/opensavvy/groundwork/prepared.git
- Branch
ktor-3
- Command
./gradlew check
The tests in ClientTest
time out, even though they finished successfully before Ktor 3.0.0. TestApplication.stop()
is called, but one coroutine is still suspended.
Other useful links:
Other
Client: Downloading the file returns before the file is fully written to disk
I am downloading a file onto the file system using this code:
internal suspend inline fun HttpClient.download(
url: String,
crossinline onProgress: (Float) -> Unit = {},
builder: HttpRequestBuilder.() -> Unit = {},
) = get(url.removePrefix("/")) {
skipSavingBody()
timeout {
requestTimeoutMillis = HttpTimeoutConfig.INFINITE_TIMEOUT_MS
socketTimeoutMillis = HttpTimeoutConfig.INFINITE_TIMEOUT_MS
connectTimeoutMillis = HttpTimeoutConfig.INFINITE_TIMEOUT_MS
}
builder()
val progress = ProgressHolder() // wrapper that maps sizes to percentages
onDownload { sent, total -> progress.update(sent, total, onProgress) }
}.bodyAsChannel()
internal suspend inline fun HttpClient.download(
url: String,
to: File,
crossinline onProgress: (Float) -> Unit = {},
builder: HttpRequestBuilder.() -> Unit = {},
) = download(url, onProgress, builder).map { channel ->
channel.copyAndClose(to.writeChannel(currentCoroutineContext() + Dispatchers.IO))
}
The function is used as-is in the code.
The file being downloaded is a ~240MB video file.
That function is supposed to return only when the entire channel is flushed to the stream, if we look at copyAndClose
implementation.
However, some code is trying to open and use the downloaded file immediately after the download
returns in the same coroutine context that invoked the download. That code is Android's MediaMetadataRetriever
, which is blocking by nature, so the call is shifted to Dispatchers.IO there.
However, the attempt to use the file fails because apparently the file was not fully written to disk yet, (the video EXIF metadata is corrupted).
Adding a simple delay(1.seconds)
right after the download
invocation fixes the problem. Using outputStream
and copyTo
, adding flush
calls or flushing the channel manually doesn't work.
It seems to me that the function copyAndClose
returns before the actual write is finished and file descriptor is closed.
Ktor: 3.0.3
OS: Android
FormFieldLimit is overwritten by default arg
It is impossible to assign a new limit using the attribute due to the default argument.
3.0.2
released 4th December 2024
Client
Digest Auth: Ktor 3.0.1 uses the wrong "nc" value to calculate digest
I haven't investigated to determine which change caused the breakage yet. 3.0.0 was working as expected, but 3.0.1 can no longer connect.
DefaultRequest: Content-Type header of default request is not overridable
- Create a http client with default request
var client = HttpClient(CIO) {
defaultRequest {
url(erpApiProperties.baseUrl)
contentType(ContentType.Application.Json)
}
}
2. Try to override the contentType
client.post("fscmRestApi/resources/11.13.18.05/salesOrdersForOrderHub/action/updateSchedulingAttribute") {
val lines = salesOrderScheduleUpdateMessage.items.map {
SalesOrderLineScheduleRequest(
FulfillLineId = it.FulfillLineId,
ScheduleArrivalDateTime = it.ScheduleArrivalDateTime,
ScheduleShipDateTime = it.ScheduleShipDateTime,
)
}
contentType(ContentType("application", "vnd.oracle.adf.action+json"))
}
3. The new content type is not taken uses Application.Json
ktorVersion = "2.3.5"
CIO: Response body truncated because read amount of bytes isn't compared against Content-Length
Ktor version: 2.3.12
Coroutines: 1.8.1
Env: IntelliJ Platform Plugin
Url: https://api.github.com/repos/paradigmxyz/reth/compare/f211aacf551afbf707b7fb6a40d17c8d2264110e...43c2c6c36af704e8a7d7bfa4c551e96d346b1a6a
Possibly a race condition when making multiple requests as cannot create a reproducer, also cannot test with ktor 3 due to it using coroutines 1.9.0 which is incompatible with a backwardly compatible intellij plugin.
You can see the Content-Length
is 467321
(verified 467,321 bytes) but the ByteBufferChannel.totalBytesWritten/totalBytesRead
is 321482
HttpCache: IndexOutOfBoundsException on malformed Cache-Control header
Using the HttpCache plugin for HttpClient, I get a IndexOutOfBoundsException when the server sends the following header:
cache-control: max-age: 120
Now, this is not a valid header - it should of course be max-age=120, but i think it should simply be ignored by the client instead of leading to crash.
The problematic line is in HttpCacheEntry.kt:
val maxAge = cacheControl.firstOrNull { it.value.startsWith(maxAgeKey) }
?.value?.split("=")
?.get(1)?.toLongOrNull()
A suggested fix would be to replace get(1) with getOrNull(1).
Support binary (Smile) encoding in JacksonConverter
When reading input, JacksonConverter
creates a character stream, using the request Charset
(default UTF-8
). This approach is not compatible with the binary format, the Smile encoding.
When attempting to read the binary encoded JSON structure with JacksonConverter
, Jackson fails either with an initialization error:
Can not create parser for character-based (not byte-based) source
java.lang.UnsupportedOperationException: Can not create parser for character-based (not byte-based) source
or with a JsonParseException
like the following:
com.fasterxml.jackson.core.JsonParseException: Unexpected character (':' (code 58)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 1]
In order to fix this problem, Jackson should operate over a byte-stream and not a character stream like it does today in JsonConverter#deserialize
.
ServiceLoader.load call is slow on Android
We have ServiceLoader.load
calls (like this), which are very slow on Android.
R8 is able to optimize such calls if they follow the pattern ServiceLoader.load(X.class, X.class.getClassLoader()).iterator()
, so we can change the code to follow this pattern to benefit from this optimization.
JS: "ReferenceError: require is not defined" when compiling to ES Module
When I run my Kotlin/JS app in the Node.js 18 runtime on AWS Lambda, the following error is thrown when trying to GET an endpoint:
{
"errorType": "ReferenceError",
"errorMessage": "require is not defined",
"stack": [
"ReferenceError: require is not defined",
" at eval (eval at AbortController_0 (file:///var/task/ktor-ktor-client-core.mjs:7357:22), <anonymous>:1:1)",
" at AbortController_0 (file:///var/task/ktor-ktor-client-core.mjs:7357:22)",
" at commonFetch (file:///var/task/ktor-ktor-client-core.mjs:7341:20)",
" at protoOf.uh (file:///var/task/ktor-ktor-client-core.mjs:6764:27)",
" at protoOf.c2f (file:///var/task/ktor-ktor-client-core.mjs:6877:14)",
" at protoOf.uh (file:///var/task/ktor-ktor-client-core.mjs:1468:38)",
" at protoOf.b2f (file:///var/task/ktor-ktor-client-core.mjs:1450:14)",
" at l (file:///var/task/ktor-ktor-client-core.mjs:1498:14)",
" at protoOf.uh (file:///var/task/kotlin-kotlin-stdlib-js-ir.mjs:11715:36)",
" at protoOf.th (file:///var/task/kotlin-kotlin-stdlib-js-ir.mjs:11624:31)"
]
}
The line from ktor-ktor-client-core.mjs
in question is:
var controller = eval('require')('abort-controller');
Kotlin version: 1.9.0
Binary type: Executable
Chunked transfer encoding failure not caught with retry
Using the HttpRequestRetry client plugin does not handle interrupted streams, i.e. when a chunk length is sent but the chunk is not sent in full.
See stacktrace:
Exception in thread "main" java.io.IOException: Invalid chunk: content block of size 8192 ended unexpectedly
at io.ktor.utils.io.CloseToken.getCause(CloseToken.kt:37)
at io.ktor.utils.io.ByteChannel.cancel(ByteChannel.kt:135)
at io.ktor.utils.io.ByteWriteChannelOperationsKt$writer$job$1.invokeSuspend(ByteWriteChannelOperations.kt:151)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:221)
at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:177)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:149)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:470)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core(CancellableContinuationImpl.kt:504)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core$default(CancellableContinuationImpl.kt:493)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:359)
at io.ktor.utils.io.ByteChannel$Slot$Task$DefaultImpls.resume(ByteChannel.kt:230)
at io.ktor.utils.io.ByteChannel$Slot$Read.resume(ByteChannel.kt:233)
at io.ktor.utils.io.ByteChannel.closeSlot(ByteChannel.kt:173)
at io.ktor.utils.io.ByteChannel.cancel(ByteChannel.kt:137)
at io.ktor.utils.io.ByteWriteChannelOperationsKt.close(ByteWriteChannelOperations.kt:92)
at io.ktor.http.cio.ChunkedTransferEncodingKt.decodeChunked(ChunkedTransferEncoding.kt:90)
at io.ktor.http.cio.ChunkedTransferEncodingKt$decodeChunked$2.invokeSuspend(ChunkedTransferEncoding.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:101)
at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:113)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:589)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:823)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:720)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:707)
Caused by: java.io.IOException: Invalid chunk: content block of size 8192 ended unexpectedly
at io.ktor.utils.io.CloseToken.<init>(CloseToken.kt:27)
at io.ktor.utils.io.ByteChannel.cancel(ByteChannel.kt:133)
... 24 more
Caused by: java.io.IOException: Invalid chunk: content block of size 8192 ended unexpectedly
at io.ktor.utils.io.CloseToken.getCause(CloseToken.kt:37)
at io.ktor.utils.io.ByteChannel.cancel(ByteChannel.kt:135)
... 11 more
Caused by: java.io.IOException: Invalid chunk: content block of size 8192 ended unexpectedly
at io.ktor.utils.io.CloseToken.<init>(CloseToken.kt:27)
at io.ktor.utils.io.ByteChannel.cancel(ByteChannel.kt:133)
... 11 more
Caused by: java.io.EOFException: Invalid chunk: content block of size 8192 ended unexpectedly
at io.ktor.http.cio.ChunkedTransferEncodingKt.decodeChunked(ChunkedTransferEncoding.kt:81)
... 9 more
HttpCookies: IllegalArgumentException when server returns a raw cookie with not allowed characters
When a server sets a raw cookie containing characters that are not allowed in raw cookies, on the next request to the server Ktor throws an IllegalArgumentException
when trying to include the cookie
Steps to reproduce
Full code is included as an attachment
- Set up an HTTP server that sets a raw cookie that contains characters like
{}":,
- Create a Ktor client instance with
install(HttpCookies) { AcceptAllCookiesStorage() }
- Send 2 requests to the server. The first should be fine, but the second one throws an exception
Exception in thread "main" java.lang.IllegalArgumentException: The cookie value contains characters that cannot be encoded in RAW format. Consider URL_ENCODING mode
at io.ktor.http.CookieKt.encodeCookieValue(Cookie.kt:171)
at io.ktor.http.CookieKt.renderCookieHeader(Cookie.kt:132)
The full stack trace is included as an attachment
Expected behavior
Client sends the malformed cookie anyway. This is how web browsers handle it.
Core
ByteReadChannel.{readShort/readInt/readLong} leads to infinite loop when required bytes distributed in flush and read buffers
Hi, I'm facing a similar issue as to https://youtrack.jetbrains.com/issue/KTOR-7571/ByteReadChannel.readShort-readInt-readLong-could-lead-to-CPU-bound-indefinite-loop-since-3.0.0 and the fix included in version 3.0.1 still hasn't resolved the problem. The issue can lead to infinite loops when trying to read exact sizes from the channel. After debugging my code for some time it appears to be an issue with the flush buffer in ByteChannel
. If I am requesting 4 bytes with readInt
but there is only 1 byte ready in the _readBuffer
, the awaitContent
method will loop infinitely because that method checks the _readBuffer
and flushBuffer
for available bytes.
override suspend fun awaitContent(min: Int): Boolean {
rethrowCloseCauseIfNeeded()
if (flushBufferSize + _readBuffer.size >= min) return true
sleepWhile(Slot::Read) {
flushBufferSize + _readBuffer.size < min && _closedCause.value == null
}
if (_readBuffer.size < CHANNEL_MAX_SIZE) moveFlushToReadBuffer()
return _readBuffer.size >= min
}
Since there is no opportunity to move the bytes in the flushBuffer
to the _readBuffer
, the readInt
method never has enough bytes to read so the loop never terminates.
public suspend fun ByteReadChannel.readInt(): Int {
awaitUntilReadable(Int.SIZE_BYTES)
return readBuffer.readInt()
}
private suspend fun ByteReadChannel.awaitUntilReadable(numberOfBytes: Int) {
while (availableForRead < numberOfBytes && awaitContent(numberOfBytes)) {
yield()
}
if (availableForRead < numberOfBytes) throw EOFException("Not enough data available")
}
From what I can see, the awaitContent
method should be updated to call moveFlushToReadBuffer
when early exiting without suspending to ensure that all bytes present in the flushBuffer
are moved to the _readBuffer
. Another option would be to remove flushBufferSize
from the first check so the sleep will exit before suspension and the flush method will be called before exiting.
I currently have a workaround where my application where I call ByteReadChannel.readByte
4 times and combine the values into a single Int
but the implementation in ByteChannel
should be fixed.
Docs
KTOR-3857 is hidden?, but linked from doc
Infrastructure
Missing AndroidNativeArm32 artefacts in 3.0.1
When using version 3.0.0, everything was fine. When updating to 3.0.1, Gradle fails and prints the following error:
FAILURE: Build completed with 4 failures.
1: Task failed with an exception.
-----------
* What went wrong:
Execution failed for task ':ktor:transformNativeMainCInteropDependenciesMetadataForIde'.
> Could not resolve all files for configuration ':ktor:androidNativeArm32CompilationDependenciesMetadata'.
> Could not find io.ktor:ktor-websocket-serialization-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-websocket-serialization:3.0.1
> Could not find io.ktor:ktor-sse-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-sse:3.0.1
> Could not find io.ktor:ktor-utils-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-http:3.0.1 > io.ktor:ktor-http-androidnativearm32:3.0.1 > io.ktor:ktor-utils:3.0.1
* Try:
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':ktor:transformNativeMainCInteropDependenciesMetadataForIde'.
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:38)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
Caused by: org.gradle.api.internal.artifacts.ivyservice.TypedResolveException: Could not resolve all files for configuration ':ktor:androidNativeArm32CompilationDependenciesMetadata'.
at org.gradle.api.internal.artifacts.ResolveExceptionMapper.mapFailures(ResolveExceptionMapper.java:56)
at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$DefaultResolutionHost.consolidateFailures(DefaultConfiguration.java:1995)
at org.gradle.api.internal.artifacts.configurations.ResolutionHost.rethrowFailuresAndReportProblems(ResolutionHost.java:75)
at org.gradle.api.internal.artifacts.configurations.ResolutionBackedFileCollection.maybeThrowResolutionFailures(ResolutionBackedFileCollection.java:87)
at org.gradle.api.internal.artifacts.configurations.ResolutionBackedFileCollection.visitContents(ResolutionBackedFileCollection.java:77)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:67)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:100)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:92)
at org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection$UnresolvedItemsCollector.visitContents(DefaultConfigurableFileCollection.java:594)
at org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection.visitChildren(DefaultConfigurableFileCollection.java:416)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:67)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:92)
at org.gradle.api.internal.file.DefaultFileCollectionFactory$ResolvingFileCollection.visitChildren(DefaultFileCollectionFactory.java:306)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.tasks.PropertyFileCollection.visitChildren(PropertyFileCollection.java:48)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.internal.fingerprint.impl.DefaultFileCollectionSnapshotter.snapshot(DefaultFileCollectionSnapshotter.java:47)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter$InputCollectingVisitor.visitInputFileProperty(DefaultInputFingerprinter.java:133)
at org.gradle.api.internal.tasks.execution.TaskExecution.visitRegularInputs(TaskExecution.java:324)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter.fingerprintInputProperties(DefaultInputFingerprinter.java:63)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.captureExecutionStateWithOutputs(AbstractCaptureStateBeforeExecutionStep.java:109)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.lambda$captureExecutionState$0(AbstractCaptureStateBeforeExecutionStep.java:74)
at org.gradle.internal.execution.steps.BuildOperationStep$1.call(BuildOperationStep.java:37)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.internal.execution.steps.BuildOperationStep.operation(BuildOperationStep.java:34)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.captureExecutionState(AbstractCaptureStateBeforeExecutionStep.java:69)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:62)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:43)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.executeWithNonEmptySources(AbstractSkipEmptyWorkStep.java:125)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:56)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:36)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:75)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:41)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.lambda$execute$0(AssignMutableWorkspaceStep.java:35)
at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:289)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:31)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:22)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:40)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:23)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.lambda$execute$2(ExecuteWorkBuildOperationFiringStep.java:67)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:67)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:39)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:46)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:34)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:48)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:35)
at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:61)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:127)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:116)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
Cause 1: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-websocket-serialization-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-websocket-serialization:3.0.1
Cause 2: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-sse-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-sse:3.0.1
Cause 3: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-utils-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-http:3.0.1 > io.ktor:ktor-http-androidnativearm32:3.0.1 > io.ktor:ktor-utils:3.0.1
==============================================================================
2: Task failed with an exception.
-----------
* What went wrong:
Execution failed for task ':ktor:transformAndroidNativeMainCInteropDependenciesMetadataForIde'.
> Could not resolve all files for configuration ':ktor:androidNativeArm32CompilationDependenciesMetadata'.
> Could not find io.ktor:ktor-websocket-serialization-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-websocket-serialization:3.0.1
> Could not find io.ktor:ktor-sse-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-sse:3.0.1
> Could not find io.ktor:ktor-utils-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-http:3.0.1 > io.ktor:ktor-http-androidnativearm32:3.0.1 > io.ktor:ktor-utils:3.0.1
* Try:
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':ktor:transformAndroidNativeMainCInteropDependenciesMetadataForIde'.
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:38)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.gradle.api.internal.artifacts.ivyservice.TypedResolveException: Could not resolve all files for configuration ':ktor:androidNativeArm32CompilationDependenciesMetadata'.
at org.gradle.api.internal.artifacts.ResolveExceptionMapper.mapFailures(ResolveExceptionMapper.java:56)
at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$DefaultResolutionHost.consolidateFailures(DefaultConfiguration.java:1995)
at org.gradle.api.internal.artifacts.configurations.ResolutionHost.rethrowFailuresAndReportProblems(ResolutionHost.java:75)
at org.gradle.api.internal.artifacts.configurations.ResolutionBackedFileCollection.maybeThrowResolutionFailures(ResolutionBackedFileCollection.java:87)
at org.gradle.api.internal.artifacts.configurations.ResolutionBackedFileCollection.visitContents(ResolutionBackedFileCollection.java:77)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:67)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:100)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:92)
at org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection$UnresolvedItemsCollector.visitContents(DefaultConfigurableFileCollection.java:594)
at org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection.visitChildren(DefaultConfigurableFileCollection.java:416)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:67)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:92)
at org.gradle.api.internal.file.DefaultFileCollectionFactory$ResolvingFileCollection.visitChildren(DefaultFileCollectionFactory.java:306)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.tasks.PropertyFileCollection.visitChildren(PropertyFileCollection.java:48)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.internal.fingerprint.impl.DefaultFileCollectionSnapshotter.snapshot(DefaultFileCollectionSnapshotter.java:47)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter$InputCollectingVisitor.visitInputFileProperty(DefaultInputFingerprinter.java:133)
at org.gradle.api.internal.tasks.execution.TaskExecution.visitRegularInputs(TaskExecution.java:324)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter.fingerprintInputProperties(DefaultInputFingerprinter.java:63)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.captureExecutionStateWithOutputs(AbstractCaptureStateBeforeExecutionStep.java:109)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.lambda$captureExecutionState$0(AbstractCaptureStateBeforeExecutionStep.java:74)
at org.gradle.internal.execution.steps.BuildOperationStep$1.call(BuildOperationStep.java:37)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.internal.execution.steps.BuildOperationStep.operation(BuildOperationStep.java:34)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.captureExecutionState(AbstractCaptureStateBeforeExecutionStep.java:69)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:62)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:43)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.executeWithNonEmptySources(AbstractSkipEmptyWorkStep.java:125)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:56)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:36)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:75)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:41)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.lambda$execute$0(AssignMutableWorkspaceStep.java:35)
at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:289)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:31)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:22)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:40)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:23)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.lambda$execute$2(ExecuteWorkBuildOperationFiringStep.java:67)
at java.base/java.util.Optional.orElseGet(Unknown Source)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:67)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:39)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:46)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:34)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:48)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:35)
at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:61)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:127)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:116)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Cause 1: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-websocket-serialization-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-websocket-serialization-androidnativearm32/3.0.1/ktor-websocket-serialization-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-websocket-serialization:3.0.1
Cause 2: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-sse-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-sse-androidnativearm32/3.0.1/ktor-sse-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-sse:3.0.1
Cause 3: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-utils-androidnativearm32:3.0.1.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- https://s01.oss.sonatype.org/content/repositories/releases/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- https://maven.pkg.jetbrains.space/public/p/ktor/eap/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
- file:/C:/Users/0/.m2/repository/io/ktor/ktor-utils-androidnativearm32/3.0.1/ktor-utils-androidnativearm32-3.0.1.pom
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-http:3.0.1 > io.ktor:ktor-http-androidnativearm32:3.0.1 > io.ktor:ktor-utils:3.0.1
==============================================================================
3: Task failed with an exception.
-----------
* What went wrong:
Execution failed for task ':ktor:transformNativeTestCInteropDependenciesMetadataForIde'.
> Could not resolve all files for configuration ':ktor:androidNativeArm32TestCompilationDependenciesMetadata'.
> Could not find io.ktor:ktor-websocket-serialization-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-websocket-serialization:3.0.1
> Could not find io.ktor:ktor-sse-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-sse:3.0.1
> Could not find io.ktor:ktor-utils-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-http:3.0.1 > io.ktor:ktor-http-androidnativearm32:3.0.1 > io.ktor:ktor-utils:3.0.1
* Try:
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':ktor:transformNativeTestCInteropDependenciesMetadataForIde'.
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:38)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.gradle.api.internal.artifacts.ivyservice.TypedResolveException: Could not resolve all files for configuration ':ktor:androidNativeArm32TestCompilationDependenciesMetadata'.
at org.gradle.api.internal.artifacts.ResolveExceptionMapper.mapFailures(ResolveExceptionMapper.java:56)
at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$DefaultResolutionHost.consolidateFailures(DefaultConfiguration.java:1995)
at org.gradle.api.internal.artifacts.configurations.ResolutionHost.rethrowFailuresAndReportProblems(ResolutionHost.java:75)
at org.gradle.api.internal.artifacts.configurations.ResolutionBackedFileCollection.maybeThrowResolutionFailures(ResolutionBackedFileCollection.java:87)
at org.gradle.api.internal.artifacts.configurations.ResolutionBackedFileCollection.visitContents(ResolutionBackedFileCollection.java:77)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:67)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:100)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:92)
at org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection$UnresolvedItemsCollector.visitContents(DefaultConfigurableFileCollection.java:594)
at org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection.visitChildren(DefaultConfigurableFileCollection.java:416)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:67)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:92)
at org.gradle.api.internal.file.DefaultFileCollectionFactory$ResolvingFileCollection.visitChildren(DefaultFileCollectionFactory.java:306)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.tasks.PropertyFileCollection.visitChildren(PropertyFileCollection.java:48)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.internal.fingerprint.impl.DefaultFileCollectionSnapshotter.snapshot(DefaultFileCollectionSnapshotter.java:47)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter$InputCollectingVisitor.visitInputFileProperty(DefaultInputFingerprinter.java:133)
at org.gradle.api.internal.tasks.execution.TaskExecution.visitRegularInputs(TaskExecution.java:324)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter.fingerprintInputProperties(DefaultInputFingerprinter.java:63)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.captureExecutionStateWithOutputs(AbstractCaptureStateBeforeExecutionStep.java:109)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.lambda$captureExecutionState$0(AbstractCaptureStateBeforeExecutionStep.java:74)
at org.gradle.internal.execution.steps.BuildOperationStep$1.call(BuildOperationStep.java:37)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.internal.execution.steps.BuildOperationStep.operation(BuildOperationStep.java:34)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.captureExecutionState(AbstractCaptureStateBeforeExecutionStep.java:69)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:62)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:43)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.executeWithNonEmptySources(AbstractSkipEmptyWorkStep.java:125)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:56)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:36)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:75)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:41)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.lambda$execute$0(AssignMutableWorkspaceStep.java:35)
at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:289)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:31)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:22)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:40)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:23)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.lambda$execute$2(ExecuteWorkBuildOperationFiringStep.java:67)
at java.base/java.util.Optional.orElseGet(Unknown Source)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:67)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:39)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:46)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:34)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:48)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:35)
at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:61)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:127)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:116)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Cause 1: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-websocket-serialization-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-websocket-serialization:3.0.1
Cause 2: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-sse-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-sse:3.0.1
Cause 3: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-utils-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-http:3.0.1 > io.ktor:ktor-http-androidnativearm32:3.0.1 > io.ktor:ktor-utils:3.0.1
==============================================================================
4: Task failed with an exception.
-----------
* What went wrong:
Execution failed for task ':ktor:transformAndroidNativeTestCInteropDependenciesMetadataForIde'.
> Could not resolve all files for configuration ':ktor:androidNativeArm32TestCompilationDependenciesMetadata'.
> Could not find io.ktor:ktor-websocket-serialization-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-websocket-serialization:3.0.1
> Could not find io.ktor:ktor-sse-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-sse:3.0.1
> Could not find io.ktor:ktor-utils-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-http:3.0.1 > io.ktor:ktor-http-androidnativearm32:3.0.1 > io.ktor:ktor-utils:3.0.1
* Try:
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':ktor:transformAndroidNativeTestCInteropDependenciesMetadataForIde'.
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:38)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.gradle.api.internal.artifacts.ivyservice.TypedResolveException: Could not resolve all files for configuration ':ktor:androidNativeArm32TestCompilationDependenciesMetadata'.
at org.gradle.api.internal.artifacts.ResolveExceptionMapper.mapFailures(ResolveExceptionMapper.java:56)
at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$DefaultResolutionHost.consolidateFailures(DefaultConfiguration.java:1995)
at org.gradle.api.internal.artifacts.configurations.ResolutionHost.rethrowFailuresAndReportProblems(ResolutionHost.java:75)
at org.gradle.api.internal.artifacts.configurations.ResolutionBackedFileCollection.maybeThrowResolutionFailures(ResolutionBackedFileCollection.java:87)
at org.gradle.api.internal.artifacts.configurations.ResolutionBackedFileCollection.visitContents(ResolutionBackedFileCollection.java:77)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:67)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:100)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:92)
at org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection$UnresolvedItemsCollector.visitContents(DefaultConfigurableFileCollection.java:594)
at org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection.visitChildren(DefaultConfigurableFileCollection.java:416)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:67)
at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:92)
at org.gradle.api.internal.file.DefaultFileCollectionFactory$ResolvingFileCollection.visitChildren(DefaultFileCollectionFactory.java:306)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:113)
at org.gradle.api.internal.tasks.PropertyFileCollection.visitChildren(PropertyFileCollection.java:48)
at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:113)
at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:360)
at org.gradle.internal.fingerprint.impl.DefaultFileCollectionSnapshotter.snapshot(DefaultFileCollectionSnapshotter.java:47)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter$InputCollectingVisitor.visitInputFileProperty(DefaultInputFingerprinter.java:133)
at org.gradle.api.internal.tasks.execution.TaskExecution.visitRegularInputs(TaskExecution.java:324)
at org.gradle.internal.execution.impl.DefaultInputFingerprinter.fingerprintInputProperties(DefaultInputFingerprinter.java:63)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.captureExecutionStateWithOutputs(AbstractCaptureStateBeforeExecutionStep.java:109)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.lambda$captureExecutionState$0(AbstractCaptureStateBeforeExecutionStep.java:74)
at org.gradle.internal.execution.steps.BuildOperationStep$1.call(BuildOperationStep.java:37)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.internal.execution.steps.BuildOperationStep.operation(BuildOperationStep.java:34)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.captureExecutionState(AbstractCaptureStateBeforeExecutionStep.java:69)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:62)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:43)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.executeWithNonEmptySources(AbstractSkipEmptyWorkStep.java:125)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:56)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:36)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:75)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:41)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.lambda$execute$0(AssignMutableWorkspaceStep.java:35)
at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:289)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:31)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:22)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:40)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:23)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.lambda$execute$2(ExecuteWorkBuildOperationFiringStep.java:67)
at java.base/java.util.Optional.orElseGet(Unknown Source)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:67)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:39)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:46)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:34)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:48)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:35)
at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:61)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:127)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:116)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Cause 1: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-websocket-serialization-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-websocket-serialization:3.0.1
Cause 2: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-sse-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-sse:3.0.1
Cause 3: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find io.ktor:ktor-utils-androidnativearm32:3.0.1.
Required by:
project :ktor > io.ktor:ktor-client-core:3.0.1 > io.ktor:ktor-client-core-androidnativearm32:3.0.1 > io.ktor:ktor-http:3.0.1 > io.ktor:ktor-http-androidnativearm32:3.0.1 > io.ktor:ktor-utils:3.0.1
==============================================================================
BUILD FAILED in 3m 12s
Server
Routing: ContentType.match doesn't match wildcard content types
I have a route that looks like this:
fun Routing.images1() = route("/images/{id}") {
accept(ContentType.Application.Json, JsonLd) {
get { call.respondWithImageJsonLd() }
}
accept(ContentType.Image.Any) {
get { call.respondWithImage() }
}
get { call.respond(HttpStatusCode.NotAcceptable) }
}
private val JsonLd = ContentType.parse("application/ld+json")
I'm getting inconsistent/unexpected behaviour of this compared to handling the accept header via the acceptItems()
method:
fun Routing.images2() = route("/images/{id}") {
get { call.get() }
}
private suspend fun ApplicationCall.get() {
request.acceptItems().forEach { item ->
val contentType = ContentType.parse(item.value)
if (contentType.match(ContentType.Application.Json) || contentType.match(JsonLd)) {
return@handleRequest respondWithImageJsonLd())
}
if (contentType.match(ContentType.Image.Any)) {
return@handleRequest respondWithImage()
}
}
respond(HttpStatusCode.NotAcceptable)
}
That is:
- Given
image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5
(Firefox accept header for images) image1 returns NotAcceptable, image2 returns the image. - Given
image/png;q=0.5
image1 returns NotAcceptable, image2 returns the image.
This looks as if the accept string parsing in the accept
DSL:
a) doesn't like the lone q=0.5
at the end, and
b) rejects the entire accept header if it doesn't like any of the ;
separated parts. IIUC, it should just ignore that section.
`FileItem.streamProvider` is deprecated and doesn't have implementation
So yeah the call to streamProvider crashed our application in production with ktor 3.0
Looking at the source code it's obvious to see why.
https://github.com/ktorio/ktor/blob/main/ktor-http/jvm/src/io/ktor/http/content/MultipartJvm.kt
This function only exists to crash.
I should have looked into the deprecation warnings more, i will take my share of the blame.
However i see no reason why it has to be like this.
My suggestion for the future is that if you want to deprecate something then either
- Mark it as deprecated and leave it as usable
- Remove the function entirely
Don't leave unusable functions in the library, it becomes a minefield for existing code bases looking to upgrade.
Side note:
I'm reporting this as a bug because there seems to be no other options.
I can only report things as bugs here and there is no way to report things in the github repo.
MergedApplicationConfig.toMap replaces nested configs completely without merging
Hello! I've encountered an issue with the MergedApplicationConfig
class. For our use case, we convert the ApplicationConfig to a custom class by using the map returned by ApplicationConfig.toMap()
. This works well for one configuration file, but won't work for multiple ones because of the way toMap
is implemented.
override fun toMap(): Map<String, Any?> = second.toMap() + first.toMap()
The plus operator is not overloaded, and thus it will default back to the Kotlin Standard Lib implementation which doesn't do a recursive, deep merge - it just adds / overwrites the key-value pairs from the 1st depth level of the maps.
So, say we have these 2 configuration files:
application.yaml
key1: first
nested:
nested-key1: first
nested-key2: first
application2.yaml
key2: second
nested:
nested-key2: second
nested-key3: second
When specifying to use both config files (-config=application.yaml -config=application2.yaml
) I would expect the resulting .toMap()
call to return this:
key1: first
key2: second
nested:
nested-key1: first
nested-key2: second
nested-key3: second
But instead it returns this:
key1: first
key2: second
nested:
nested-key2: second
nested-key3: second
Notice that nested.nested-key1
is not present, because the default plus operator replaced the whole nested
object from the 1st config with the nested
object from the 2nd config.
We've implemented a fix in our project by deep-merging the 2 maps and it works well. My question is, is this behaviour intended? If not, would you accept a PR? I would be happy to contribute.
Other
ServletResponseBody is corrupted due to the wrong offset
The offset is set wrong:
channel.read { buffer, start, end ->
val rc = end - start
copied += rc
if (copied > MAX_COPY_SIZE) {
copied = 0
yield()
}
awaitReady()
output.write(buffer, 0, rc) <--- 0 is mistake here
awaitReady()
rc
}
If the buffer has a positive offset, the content is corrupted
call.respondSource returns empty response but passes in tests
A fully working minimal reproduceable sample can be found here, including README: https://github.com/helico-tech/ktorize/tree/bugs/source
TLDR:
- Run the tests in
minimal-sample
, they pass - Start the actual application and navigate to
/resource/foo.txt
. You get an empty response. - Loading resources like this is just a minimal example to show that
respondeSource
behaves differently in tests and in the actual application. call.respondBytes(source.readByteArray(), contentType)
actually works in both cases.
Connections aren't released properly if there are multiple parallel connections to the same address
Created from PR: https://github.com/ktorio/ktor/pull/4482
We encountered a lot of crashes after updating to the Ktor v3 with the root causes:
Caused by: java.lang.IllegalStateException: The number of released permits cannot be greater than 100
The problem is that we request 4 calls to the same endpoint asynchronously. However, InetSocketAddress(host, port) could have different results (IP address could change) - the current solution stores only the last address.
3.0.1
released 30th October 2024
Build System Plugin
shadowJar-related tasks when relocating classes don't support JDK 21 and higher
You can use https://plugins.gradle.org/plugin/io.github.goooler.shadow for add support java 21
(and update other libsm like https://plugins.gradle.org/plugin/com.google.cloud.tools.jib)
Client
Digest Auth does not implement nc parameter correctly according to RFC 7616
RFC 7616 specifies, that the parameter nc
"MUST be exactly 8 hexadecimal digits", but ktor-client-auth
serves the value as decimal and not padded to 8 characters.
I am currently struggling with auth errors when connecting to a device with which neither Firefox nor curl have any problems, so I am trying to find where Ktor deviates from the standard/other software. While fixing this particular issue locally did not fix my problem, it could help others.
The fix should be simple, just turn the nonceCount
into string right after it is obtained:
val nonceCount = requestCounter.incrementAndGet().padStart(8, '0')
WebSockets logging: The plugin calls toString() unnecessarily on transformed response body
When WebSocket plugin is applied, due to the logging in the HttpResponsePipeline.Transform
phase, toString()
is called on transformed response body (for non-websocket requests). For huge responses this might leads to OutOfMemoryException
(which is happening for me).
Proposed solutions:
- Remove logging
session
- Use something else for logs - maybe
response.content
?
Remove space from the default client user agent
The product identifier in a user agent should not contain spaces, according to the RFC9110
Digest auth: `username` and `cnonce` parameters aren't surrounded with quotes
Hello.
I'm trying to access an API using digest auth in ktor-client-auth
and I'm having a problem: when the request is made again after the Auth plugin has assembled the Authorization
header, the server never responds until it times out.
I tried to use some tools like curl, ApiFox to make the request without this problem. And then after some tests and comparisons, I found that none of the string parameters assembled by the Auth plugin use quotes.
val client = HttpClient {
install(Logging) {
level = LogLevel.ALL
}
install(Auth) {
digest {
credentials {
DigestAuthCredentials(
username = "xxx",
password = "xxx"
)
}
}
}
}
client.use {
val resp = it.get("http://xxx.xxx.xxx.xxx:xxx/ISAPI/System/deviceInfo?format=json")
println(resp.bodyAsText())
}
When using the Auth plugin's digest { ... }
directly, the logs:
09:41:43.533 [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient -- REQUEST: http://xxx.xxx.xxx.xxx:xxx/ISAPI/System/deviceInfo?format=json
METHOD: HttpMethod(value=GET)
COMMON HEADERS
-> Accept: */*
-> Accept-Charset: UTF-8
CONTENT HEADERS
-> Content-Length: 0
BODY Content-Type: null
BODY START
BODY END
09:41:43.864 [DefaultDispatcher-worker-5] INFO io.ktor.client.HttpClient -- RESPONSE: 401 Unauthorized
METHOD: HttpMethod(value=GET)
FROM: http://xxx.xxx.xxx.xxx:xxx/ISAPI/System/deviceInfo?format=json
COMMON HEADERS
-> connection: keep-alive
-> content-length: 177
-> content-type: application/json
-> date: Fri, 11 Oct 2024 01:41:44 GMT
-> server: nginx
-> www-authenticate: Digest qop="auth", realm="DS-GWAS0101(12746)", nonce="4d6a566c4d6a49315a5449364e6a63774f4467794e54673d", stale="FALSE"
BODY Content-Type: application/json
BODY START
{"lockStatus":"unlock","requestURL":"/ISAPI/System/deviceInfo?format=json","retryLoginTime":7,"statusCode":401,"statusString":"Unauthorized","subStatusCode":"badAuthorization"}
BODY END
09:41:43.867 [DefaultDispatcher-worker-5] INFO io.ktor.client.HttpClient -- REQUEST: http://xxx.xxx.xxx.xxx:xxx/ISAPI/System/deviceInfo?format=json
METHOD: HttpMethod(value=GET)
COMMON HEADERS
-> Accept: */*
-> Accept-Charset: UTF-8
-> Authorization: Digest realm="DS-GWAS0101(12746)", username=xxx, nonce=4d6a566c4d6a49315a5449364e6a63774f4467794e54673d, cnonce=e5744d72fb5b9614, response=5c2406eae63fc3d93c117ed434996d50, uri="/ISAPI/System/deviceInfo?format=json", qop=auth, nc=1, algorithm=MD5
CONTENT HEADERS
-> Content-Length: 0
BODY Content-Type: null
BODY START
BODY END
09:46:43.898 [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient -- RESPONSE: 504 Gateway Timeout
METHOD: HttpMethod(value=GET)
FROM: http://xxx.xxx.xxx.xxx:xxx/ISAPI/System/deviceInfo?format=json
COMMON HEADERS
-> connection: keep-alive
-> content-length: 160
-> content-type: text/html
-> date: Fri, 11 Oct 2024 01:46:44 GMT
-> server: nginx
BODY Content-Type: text/html
BODY START
<html>
<head><title>504 Gateway Time-out</title></head>
<body>
<center><h1>504 Gateway Time-out</h1></center>
<hr><center>nginx</center>
</body>
</html>
BODY END
<html>
<head><title>504 Gateway Time-out</title></head>
<body>
<center><h1>504 Gateway Time-out</h1></center>
<hr><center>nginx</center>
</body>
</html>
You can see that it got a timeout after hanging for 5 minutes after the second request.
When I implemented a temporary provider myDigest
modelled on digest
and added quotes to the properties:
val client = HttpClient {
install(Logging) {
level = LogLevel.ALL
}
install(Auth) {
// temporary provider modelled on digest
myDigest {
credentials {
DigestAuthCredentials(
username = "xxx",
password = "xxx"
)
}
}
}
}
// Other elements remain unchanged ...
( KTOR-4318 was also processed in myDigest
)
The logs:
09:51:45.885 [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient -- REQUEST: http://xxx.xxx.xxx.xxx:xxx/ISAPI/System/deviceInfo?format=json
METHOD: HttpMethod(value=GET)
COMMON HEADERS
-> Accept: */*
-> Accept-Charset: UTF-8
CONTENT HEADERS
-> Content-Length: 0
BODY Content-Type: null
BODY START
BODY END
09:51:46.155 [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient -- RESPONSE: 401 Unauthorized
METHOD: HttpMethod(value=GET)
FROM: http://xxx.xxx.xxx.xxx:xxx/ISAPI/System/deviceInfo?format=json
COMMON HEADERS
-> connection: keep-alive
-> content-length: 177
-> content-type: application/json
-> date: Fri, 11 Oct 2024 01:51:46 GMT
-> server: nginx
-> www-authenticate: Digest qop="auth", realm="DS-GWAS0101(12746)", nonce="4d6a646c4e4449335a5451364e6a63774f446730596a493d", stale="FALSE"
BODY Content-Type: application/json
BODY START
{"lockStatus":"unlock","requestURL":"/ISAPI/System/deviceInfo?format=json","retryLoginTime":6,"statusCode":401,"statusString":"Unauthorized","subStatusCode":"badAuthorization"}
BODY END
09:51:46.163 [DefaultDispatcher-worker-1] INFO io.ktor.client.HttpClient -- REQUEST: http://xxx.xxx.xxx.xxx:xxx/ISAPI/System/deviceInfo?format=json
METHOD: HttpMethod(value=GET)
COMMON HEADERS
-> Accept: */*
-> Accept-Charset: UTF-8
-> Authorization: Digest realm="DS-GWAS0101(12746)", username="xxx", nonce="4d6a646c4e4449335a5451364e6a63774f446730596a493d", cnonce="4ca6925d315dd2bb", response="57c09581b50a18de4bff1c658b2a19a7", uri="/ISAPI/System/deviceInfo?format=json", qop="auth", nc=00000001, algorithm=MD5
CONTENT HEADERS
-> Content-Length: 0
BODY Content-Type: null
BODY START
BODY END
09:51:46.182 [DefaultDispatcher-worker-7] INFO io.ktor.client.HttpClient -- RESPONSE: 200 OK
METHOD: HttpMethod(value=GET)
FROM: http://xxx.xxx.xxx.xxx:xxx/ISAPI/System/deviceInfo?format=json
COMMON HEADERS
-> connection: keep-alive
-> content-length: 299
-> content-security-policy: default-src 'self' * 'unsafe-inline' 'unsafe-eval' blob: data: ; font-src 'self' * data:; img-src 'self' * blob: data: ; script-src 'self' * 'unsafe-inline' 'unsafe-eval'; style-src 'self' * 'unsafe-inline'
-> content-type: application/json
-> date: Fri, 11 Oct 2024 01:51:46 GMT
-> referrer-policy: origin
-> server: nginx
-> x-content-type-options: nosniff
-> x-download-options: noopen
-> x-frame-options: SAMEORIGIN
-> x-permitted-cross-domain-policies: master-only
-> x-xss-protection: 1; mode=block
BODY Content-Type: application/json
BODY START
{"DeviceInfo":{"bootVersion":"","deviceID":""}}
BODY END
{"DeviceInfo":{"bootVersion":"","deviceID":""}}
In RFC 7616, it also seems to state that some parameters are strings with quotes. For example:
-
The quoted string contains the name in plaintext or the hash code in hexadecimal notation.
-
The cnonce value is an opaque quoted ASCII-only string value provided by the client ...
They also both use inverted commas in the RFC 7616 examples.
Maybe there are some parameters that allow quotes to be optional? But the looseness of the checksum depends on the server and not the client, so I think client-side strictness is necessary, or at least the ability to make our own choices?
Core
ByteReadChannel.{readShort/readInt/readLong} could lead to CPU-bound indefinite loop since 3.0.0
After upgrading one of our existing projects to ktor 3.0 we've experienced some issues with the server hogging (near) 100% of cpu after a couple of minutes. I did some investigations what might be the cause of this and found the following (potential) cause for it:
- We're using
call.receiveChannel()
to obtain aByteReadChannel
(instance at runtime is probably of typeByteChannel
from what I have found) - We then manually parse the body from a combination of
ByteReadChannel::readInt
/ByteReadChannel::readLong
/ByteReadChannel::readFully
calls
Now since ktor 3.0 the readShort
/readInt
/readLong
extension functions contain a loop like this:
while (availableForRead < ${numberOfBytes} && awaitContent()) {
}
which depends on awaitContent(min: Int = 1)
to actually suspend
, but if one were to ever run into the situation that there are for example one byte were to be availableForRead
and awaitContent()
(with the default parameter min = 1
) would return immediately without suspending because it checks for min <= availableForRead
and returns true
immediately, so we would find ourselves in an endless, cpu-bound loop. Given this scenario happens to enough threads it will become stuck indefinitely because the coroutine providing the data for the ByteReadChannel
is never yielded to.
about:blank URL should be parsed correctly as about:blank
Ktor http Url parser recognizes about:blank
to be about://localhost/blank
which is incorrect, instead it should recognize about:blank
. Tested on 2.3.4
ContentType.fromFilePath for newer file formats HEIC, AVIF, HEIF
Currently the list of rawMimes
in commonMain/io/ktor/http/Mimes.kt
does not include many newer image file extensions with their mimetype. This should be updated.
Gradle Plugin
Gradle plugin uses deprecated features which will be removed by Gradle 9.0
Hello. Since Gradle v8.2, Gradle complains on deprecated features in ktor plugin.
./gradlew --warning-mode all
on a fresh project from start.ktor.io gives you the following output:
The Project.getConvention() method has been deprecated. This is scheduled to be removed in Gradle 9.0. Consult the upgrading guide for further information: https://docs.gradle.org/8.2/userguide/upgrading_version_8.html#deprecated_access_to_conventions
The org.gradle.api.plugins.Convention type has been deprecated. This is scheduled to be removed in Gradle 9.0. Consult the upgrading guide for further information: https://docs.gradle.org/8.2/userguide/upgrading_version_8.html#deprecated_access_to_conventions
The org.gradle.api.plugins.JavaPluginConvention type has been deprecated. This is scheduled to be removed in Gradle 9.0. Consult the upgrading guide for further information: https://docs.gradle.org/8.2/userguide/upgrading_version_8.html#java_convention_deprecation
Update Gradle Plugin dependencies
- Update Shadow plugin
- Merge version catalogs into one file
- Bump minimal required JVM from 8 to 11 (as it is required for jib plugin)
- Bump default JRE in Docker to 21 (LTS version)
- Set Kotlin API and language level to 1.8 for compatibility with Gradle 8.0+
The minimal supported Gradle version now is 8.3 as it is required for the Shadow plugin
Server
receiveMultipart fails with "IOException: Failed to parse multipart" when content-type is capitalized
Bug description
While uploading files with Content-Type that is not all-lowercase, I'm receiving
java.io.IOException: Failed to parse multipart: Content-Type should be multipart/* but it is Multipart/form-data; boundary=------------------------VcLit6ZPt7boeOIbGtsNSp
This happens also in Ktor 2.x and is most probably caused by the case-sensitive check on https://github.com/ktorio/ktor/blob/a3ae5d52a1ebaec4eae5e491e6904a4c595e8b36/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/Multipart.kt#L158
Why is it a bug
RFC 2045 says:
The type, subtype, and parameter names are not case sensitive. For example, TEXT, Text, and TeXt are all equivalent top-level media types.
Steps to reproduce:
I have this very basic server receiving file uploads:
fun main() {
embeddedServer(Netty, port = 8080) {
routing {
contentType(ContentType.MultiPart.FormData) {
post("/upload") {
try {
println("Received ${call.request.contentType()}")
var i = 0
val multipartData = call.receiveMultipart()
multipartData.forEachPart {
if (it is PartData.FileItem) i += it.provider().readRemaining().readByteArray().size
it.dispose()
}
call.respondText("Received $i bytes")
} catch (e: Exception) {
e.printStackTrace()
call.respond(HttpStatusCode.InternalServerError, e.toString())
}
}
}
}
}.start(wait = true)
}
Uploading files using curl:
(multipart/...
works, while Multipart/...
does not)
curl -F "file=@/Users/jakub.g/.zshrc" -H 'Content-Type: Multipart/form-data' localhost:8080/upload
INFO log message with all server interceptors on server startup
When starting my ktor server, I am seeing this stacktrace:
[2024-08-16 11:11:08,166] INFO i.k.s.Application - Application started: class io.ktor.server.application.Application(0x1e9804b9) [
class io.ktor.server.application.hooks.CallSetup$install$1
class io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1
class io.ktor.server.application.PluginBuilder$onDefaultPhaseWithMessage$1$1
class io.ktor.server.engine.BaseApplicationEngineKt$installDefaultInterceptors$2
class io.ktor.server.routing.RoutingRoot$Plugin$install$1
class io.ktor.server.engine.BaseApplicationEngineKt$installDefaultInterceptors$1
]
Other
Server Compression Truncates Response Body
install(Compression) {
excludeContentType(
ContentType.Application.OctetStream,
ContentType.Application.Zip,
ContentType.Application.GZip,
// copied from default Ktor exclusions
ContentType.Video.Any,
ContentType.Image.JPEG,
ContentType.Image.PNG,
ContentType.Audio.Any,
ContentType.MultiPart.Any,
ContentType.Text.EventStream
)
minimumSize(200L)
}
Reproduced randomly with Ktor update to 3.1.0-eap-*
The content length is around 80k. The client is Chrome browser
`Url.segments` throws on URLs with root path
public val segments: List<String> by lazy {
if (pathSegments.isEmpty()) return@lazy emptyList()
val start = if (pathSegments.first().isEmpty()) 1 else 0
val end = if (pathSegments.last().isEmpty()) pathSegments.lastIndex else pathSegments.lastIndex + 1
pathSegments.subList(start, end)
}
If pathSegments
is listOf("")
, start
will be 1 and end
will be 0.
https://github.com/ktorio/ktor/blob/3d71a28fd3045bb5c40829f8abc07460272295ed/ktor-http/common/src/io/ktor/http/Url.kt#L138
https://github.com/ktorio/ktor/blob/3d71a28fd3045bb5c40829f8abc07460272295ed/ktor-http/common/src/io/ktor/http/URLParser.kt#L9
Support missing native targets in ktor-serialization-kotlinx-xml
Support of androidNative*
and watchosDeviceArm64
targets were added in xmlutil 0.90.2. Wo we can enable these targets for ktor-serialization-kotlinx-xml
module.
3.0.0
released 10th October 2024
Client
Ktor JVM client file upload is slower than corresponding http clients.
Ktor JVM client file upload is several times slower (2-3) than corresponding http clients. So Ktor + Apache is slower than plain Apache, the same is true for Java http client and OkHttp.
I've checked this with ktor client 1.6.8 and 2.0.0-eap-356.
I've checked two cases:
- Local server and local client on MBP.
- Aws s3 as a server and aws ec2 t3a.large instance as a client.
To run server extract ktor-test-server.zip
archive and run ./gradlew run
in the directory with content from archive.
To run client extract ktor-upload-file-engines-test.zip
archive, create a file with size 2 GB (you can use truncate -s 2g test-file
on mac and linux), and run ./gradlew run --args="<client-name> <file-path> <server url> <repeat number>"
Where
<client-name>
is one of [KApache, KCIO, KJava, KOkHttp, Apache, Java, OkHttp]. Prefix K
means Ktor. So to test difference between Ktor + Apache and Apache you should you KApache
for first run and Apache
for second run.
<file-path>
is a path to test file, you can use test-file
if you've created it via truncate
.
<server url>
is a server url, by default the server from ktor-test-server.zip
is listening at http://0.0.0.0:17987
.
<repeat number>
is a number of repeats. By default the value is 1, I advise you to use at least 5.
Sudden CPU consumption on `io.ktor.util.Deflater#deflated`
Recently we had a precedent in production. Suddenly CPU consumption jumps and fully occupies two cores.
CIO client: Support for Windows (mingw_x64) target
The artifact for the mingw_x64 target is missing.
KotlinReflectionInternalError (createClientPlugin) when running release APK on Android
To reproduce, execute the installRelease
Gradle task using the attached sample project and then run the application in the Android emulator.
As a result, the KotlinReflectionInternalError
is thrown:
kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Function 'createClientPlugin' (JVM signature: createClientPlugin(Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lio/ktor/client/plugins/api/ClientPlugin;) not resolved in file class io.ktor.client.plugins.api.CreatePluginUtilsKt: no members found
at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.findFunctionDescriptor(KDeclarationContainerImpl.kt:149)
at kotlin.reflect.jvm.internal.KFunctionImpl.descriptor_delegate$lambda$0(KFunctionImpl.kt:62)
at kotlin.reflect.jvm.internal.KFunctionImpl.accessor$KFunctionImpl$lambda0(KFunctionImpl.kt:0)
at kotlin.reflect.jvm.internal.KFunctionImpl$$Lambda$0.invoke(Unknown Source:0)
at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32)
at kotlin.reflect.jvm.internal.KFunctionImpl.getDescriptor(KFunctionImpl.kt:61)
at kotlin.reflect.jvm.internal.KFunctionImpl.getDescriptor(KFunctionImpl.kt:42)
at kotlin.reflect.jvm.internal.KCallableImpl._typeParameters$lambda$9(KCallableImpl.kt:85)
at kotlin.reflect.jvm.internal.KCallableImpl.accessor$KCallableImpl$lambda3(KCallableImpl.kt:0)
at kotlin.reflect.jvm.internal.KCallableImpl$$Lambda$3.invoke(Unknown Source:0)
at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70)
at kotlin.reflect.jvm.internal.KCallableImpl.getTypeParameters(KCallableImpl.kt:89)
at kotlin.jvm.internal.CallableReference.getTypeParameters(CallableReference.java:156)
at kotlin.reflect.jvm.internal.ReflectionFactoryImpl.typeParameter(ReflectionFactoryImpl.java:135)
at kotlin.jvm.internal.Reflection.typeParameter(Reflection.java:175)
at io.ktor.client.plugins.api.CreatePluginUtilsKt$createClientPlugin$1.<init>(CreatePluginUtils.kt:17)
at io.ktor.client.plugins.api.CreatePluginUtilsKt.createClientPlugin(CreatePluginUtils.kt:52)
at io.ktor.client.plugins.contentnegotiation.ContentNegotiationKt.<clinit>(ContentNegotiation.kt:135)
at io.github.jan.supabase.network.KtorSupabaseHttpClient.applyDefaultConfiguration(KtorSupabaseHttpClient.kt:113)
at io.github.jan.supabase.network.KtorSupabaseHttpClient.httpClient$lambda$1(KtorSupabaseHttpClient.kt:51)
at io.github.jan.supabase.network.KtorSupabaseHttpClient.$r8$lambda$-qS_I_iUg2V_tN7DvbQnK7mHZAM(KtorSupabaseHttpClient.kt:0)
at io.github.jan.supabase.network.KtorSupabaseHttpClient$$ExternalSyntheticLambda1.invoke(R8$$SyntheticClass:0)
at io.ktor.client.HttpClientKt.HttpClient(HttpClient.kt:42)
at io.ktor.client.HttpClientJvmKt.HttpClient(HttpClientJvm.kt:23)
at io.github.jan.supabase.network.KtorSupabaseHttpClient.<init>(KtorSupabaseHttpClient.kt:51)
at io.github.jan.supabase.SupabaseClientImpl.<init>(SupabaseClient.kt:116)
at io.github.jan.supabase.SupabaseClientBuilder.build(SupabaseClientBuilder.kt:103)
at io.github.jan.einkaufszettel.root.di.SupabaseModuleKt.supabaseModule$lambda$11$lambda$5(supabaseModule.kt:148)
at io.github.jan.einkaufszettel.root.di.SupabaseModuleKt.$r8$lambda$YVP6NcF9VyoySuHvU_xdwrDXSDE(supabaseModule.kt:0)
at io.github.jan.einkaufszettel.root.di.SupabaseModuleKt$$ExternalSyntheticLambda2.invoke(R8$$SyntheticClass:0)
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:50)
at org.koin.core.instance.SingleInstanceFactory.create(SingleInstanceFactory.kt:46)
at org.koin.core.instance.SingleInstanceFactory.get$lambda$0(SingleInstanceFactory.kt:55)
at org.koin.core.instance.SingleInstanceFactory.$r8$lambda$pg0mSggu54fTAuEFNfxX5avu4oQ(SingleInstanceFactory.kt:0)
at org.koin.core.instance.SingleInstanceFactory$$ExternalSyntheticLambda0.invoke(R8$$SyntheticClass:0)
at org.koin.mp.KoinPlatformTools.synchronized(KoinPlatformTools.kt:35)
at org.koin.core.instance.SingleInstanceFactory.get(SingleInstanceFactory.kt:53)
AndroidRuntime io....jan.einkaufszettel.androidApp E at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:109)
at org.koin.core.scope.Scope.resolveValue(Scope.kt:247)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:233)
at org.koin.core.scope.Scope.get(Scope.kt:212)
at io.github.jan.einkaufszettel.root.di.SupabaseModuleKt.supabaseModule$lambda$11$lambda$10(supabaseModule.kt:135)
at io.github.jan.einkaufszettel.root.di.SupabaseModuleKt.$r8$lambda$KRAZxYCWjnoF4hT6CIEg2EqcIz0(supabaseModule.kt:0)
at io.github.jan.einkaufszettel.root.di.SupabaseModuleKt$$ExternalSyntheticLambda7.invoke(R8$$SyntheticClass:0)
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:50)
at org.koin.core.instance.SingleInstanceFactory.create(SingleInstanceFactory.kt:46)
at org.koin.core.instance.SingleInstanceFactory.get$lambda$0(SingleInstanceFactory.kt:55)
at org.koin.core.instance.SingleInstanceFactory.$r8$lambda$pg0mSggu54fTAuEFNfxX5avu4oQ(SingleInstanceFactory.kt:0)
at org.koin.core.instance.SingleInstanceFactory$$ExternalSyntheticLambda0.invoke(R8$$SyntheticClass:0)
at org.koin.mp.KoinPlatformTools.synchronized(KoinPlatformTools.kt:35)
at org.koin.core.instance.SingleInstanceFactory.get(SingleInstanceFactory.kt:53)
at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:109)
at org.koin.core.scope.Scope.resolveValue(Scope.kt:247)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:233)
at org.koin.core.scope.Scope.get(Scope.kt:212)
at io.github.jan.einkaufszettel.ComposableSingletons$AppKt$lambda-2$1.invoke(App.kt:135)
at io.github.jan.einkaufszettel.ComposableSingletons$AppKt$lambda-2$1.invoke(App.kt:37)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.material3.SurfaceKt$Surface$1.invoke(Surface.kt:134)
at androidx.compose.material3.SurfaceKt$Surface$1.invoke(Surface.kt:115)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:380)
at androidx.compose.material3.SurfaceKt.Surface-T9BRK9s(Surface.kt:112)
at io.github.jan.einkaufszettel.root.ui.theme.ThemeKt$AppTheme$1.invoke(Theme.kt:31)
at io.github.jan.einkaufszettel.root.ui.theme.ThemeKt$AppTheme$1.invoke(Theme.kt:30)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:401)
at androidx.compose.material3.TextKt.ProvideTextStyle(Text.kt:352)
at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:72)
at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:71)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:380)
at androidx.compose.material3.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:64)
at io.github.jan.einkaufszettel.root.ui.theme.ThemeKt.AppTheme(Theme.kt:28)
at io.github.jan.einkaufszettel.AppKt.App(App.kt:37)
at io.github.jan.einkaufszettel.ComposableSingletons$App_androidKt$lambda-1$1.invoke(App.android.kt:27)
at io.github.jan.einkaufszettel.ComposableSingletons$App_androidKt$lambda-1$1.invoke(App.android.kt:26)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
AndroidRuntime io....jan.einkaufszettel.androidApp E at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:441)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:259)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:258)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:380)
at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:216)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:132)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:131)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:380)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:121)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:155)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:154)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:401)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:154)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:133)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:97)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3595)
at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3522)
at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:743)
at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:1122)
at androidx.compose.runtime.CompositionImpl.composeInitial(Composition.kt:649)
at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:635)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:133)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:124)
at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1631)
at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:124)
at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:180)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.jvm.kt:320)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.jvm.kt:198)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:131)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:124)
at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1712)
at android.view.View.dispatchAttachedToWindow(View.java:21980)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3490)
AndroidRuntime io....jan.einkaufszettel.androidApp E at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3014)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2465)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9305)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1339)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
at android.view.Choreographer.doCallbacks(Choreographer.java:952)
at android.view.Choreographer.doFrame(Choreographer.java:882)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8177)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Misleading `readBytes` method name
When I have the compression plugin installed, this snippet receives compressed ByteArray:
client.preparePost {
url(url)
.....
}.execute { response ->
val body = response.readBytes()
}
Change invocation to bodyAsText(Charsets.UTF_8)
reads correct.
The method name readBytes
is highly misleading
Weak security algorithm (MD5) in FileCacheStorage
Hello, after the security testing of our Android mobile app, we found out that the Ktor client uses a weak hash algorithm (MD5)
That’s why I have two questions - can this vulnerability affect the user and if it is true, are there any plans to replace it with a more secure algorithm? Thanks in advance for the help. Feel free to ask me for any support you need from my side
Websockets/Auth: ProtocolException when requesting protected WebSockets endpoint
I am trying to use the WebSocket implementation on Android and iOS. My Backend requires that I send an Authentication token and if the authentication token is no longer valid, a refresh token call shall be made. I have implemented the exact same mechanism already for normal REST API Calls using a custom HttpClientPlugin
.
Initially I thought that the normal Plugin pipeline is also used for establishing the connection. However on Android when I receive a 401 from my Server the Android app (using the okio http engine) just crashes:
FATAL EXCEPTION: main
Process: app.becoach.android.coachee.dev, PID: 17555
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:558)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Caused by: java.net.ProtocolException: Expected HTTP 101 response but was '401 '
at okhttp3.internal.ws.RealWebSocket.checkUpgradeSuccess$okhttp(RealWebSocket.kt:224)
at okhttp3.internal.ws.RealWebSocket$connect$1.onResponse(RealWebSocket.kt:170)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:920)
And on iOS (using the darwin http enginge) it also crashes with SIGABRT:
failed with exception: io.ktor.client.engine.darwin.DarwinHttpRequestException: Exception in http request: Error Domain=NSURLErrorDomain Code=-1011 "There was a bad response from the server."
Can I somehow tell KTOR in case I get a 401 during the establishment of the connection that it should go through the normal pipeline. In my case this would issue a new token and retry the old request again which would succeed this time.
Or is there any other mechanism how to properly handle authentication when using WebSockets?
HttpRequestRetry: exponential delay doesn't work for delays <= 1 second
Simplified, the exponential delay is calculated as
base ^ retryCount
in seconds(!), with a default base of 2.0
, resulting in 2, 4, 8, 16 ... seconds.
The lowest number still resulting in an exponential backoff is something > 1.0
. For 1.0
, the delay would be constant, and for numbers lower than 1.0
, the delay would even be declining with the number of delays (logarithmic backoff?).
So for all practical uses, the minimum initial delay is 1 or 2 seconds. You cannot set a delay that starts at e.g. 200ms, and then rises to 400, 800, 1600.
A fix could be, to allow the currently fixed factor of 1000 to be parameterized (e.g. as "baseDelayMs"), similar to this:
public fun exponentialDelay(
base: Double = 2.0,
maxDelayMs: Long = 60000,
baseDelayMs: Long = 1000,
randomizationMs: Long = 1000,
respectRetryAfterHeader: Boolean = true
) {
check(base > 0)
check(maxDelayMs > 0)
check(randomizationMs >= 0)
delayMillis(respectRetryAfterHeader) { retry ->
val delay = minOf((base.pow(retry) * baseDelayMs).toLong() , maxDelayMs)
delay + randomMs(randomizationMs)
}
}
moving the "toLong()" after the multiplication would also yield in more accuracy for non-integer bases -- not sure if required.
JS browser: "Error: HttpClientCall expected" on HTTP request when targeting es2015
To reproduce, run the jsBrowserDevelopmentRun
Gradle task of the attached project.
IllegalStateException: Error: HttpClientCall expected, but found EmptyContent(class EmptyContent).
ConnectionUtilsNative leaks descriptors on error
In the event of an error, platform.posix.close
is not being called on the socket descriptor before re-throwing that exception, thus leaking the descriptor.
If connect
call in above line fails via check()
, it swallows the exception and does not call close
on the open descriptor.
Core
The `pathSegments` returns empty strings for trailing slashes
In ktor 2.0.2, the Url#pathSegments
function now adds an empty string to the beginning of the list.
For example this code:
println(Url("https://google.com/cats").pathSegments)
Now prints [, cats]
. If this is intended and not a bug, this is a very confusing behavior change. In 2.0.0
this was returning [cats]
as expected.
Missing constants for AcceptEncoding
It's missing a constant for defining all the possible values of Accept-Encoding (deflate, gzip, ...)
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
For now we are forced to hardcode values when we build a request with header:
header(HttpHeaders.AcceptEncoding, "deflate")
MalformedInputException confusingly is a Throwable but not an Exception
It took me a while to find that issue.
When I made a try-catch with
catch(e : Exception)
I realized the MalformedInputException in Ktor is not an Exception (as the name suggests) but a Throwable.
public expect open class MalformedInputException(message: String) : Throwable
So perhaps this is a bit misleading. Would be nice to make this an exception instead.
Incomplete write using io.ktor.util.cio.FileChannelsKt#writeChannel
Hi!
I'm observing an incomplete file written after flushing and closing the write channel on the version 1.4.1 as well as latest master.
Attaching a sample test to reproduce the problem.
Needs junit 4 + "com.github.cbismuth:junit-repeat-rule:1.1.2" to run the test multiple times as well as "com.willowtreeapps.assertk:assertk:0.19".
The problem seems to be that there is no way to access the underlying WriterJob. At the same time when you close the channel without exception (as it should be in the normal case) it doesn't join the job.
As a temporary workaround I'm creating the job manually and then join the job when necessary, but this behaviour is probably unexpected since it leads to eventual writes.
Thanks
Anton
`response.content.copyAndClose(targetFile.writeChannel())` sometimes loses some bytes
See the attached sample project for the repro details
withTimeout doesn't cancel socket connection on native
Is there a way to configure a socket connect timeout on iOS? Connects on my 15.7.1 iPad device always timeout around 75 seconds. I've tried the suggestion from KTOR-4989, but it appears to have no affect on iOS. This works on the JVM
Here is a sample that will timeout at 75 seconds on iOS:
val selectorManager = SelectorManager(Dispatchers.Default)
println("starting connect")
try {
val socket = withTimeout(1000) {
aSocket(selectorManager).tcp().connect("240.0.0.0", 1235) {
socketTimeout = 1000
}
}
println("connect")
} catch (e: Exception) {
e.printStackTrace()
}
Docs
TcpSocketBuilder.bind became suspend which breaks calling it in init block
I could not find any information on this in the changelog, nor in the migration guide.
We are using the example code to create a socket server, and now I see that bind is suspending, and thus the following examples does not work any longer :
init {
val selectorManager = SelectorManager(Dispatchers.IO)
val serverSocket = aSocket(selectorManager).tcp().bind("127.0.0.1", 9002)
}
https://ktor.io/docs/server-sockets.html#server_create_socket
Tutorial-websockets-client README is confusing
Try to run Tutorial-websockets-client using README as a guide:
Run ./gradlew :tutorial-server-websockets:run
Run ./gradlew :tutorial-websockets-client:run -q --console=plain
Result:
Exception in thread "main" io.ktor.client.plugins.websocket.WebSocketException:
Handshake exception, expected status code 101 but was 404
tutorial-server-websockets sample doesn't have the /chat
endpoint
Inconsistencies in "Create a WebSocket application" tutorial
Document backward compatibility for new session encryption
The session encryption method has changed as per KTOR-6318
For existing setup, users need to add the backwardCompatibleRead
property when the class SessionTransportTransformerEncrypt
is created.
For more info, see https://github.com/ktorio/ktor/pull/3814/files
Generator
DuplicatePluginException after executing the test with a project generated with Yaml or HOCON and StatusPages plugin
Reproducible in many configurations, no correlation observed. Last known configuration follows.
Expected Behavior
./gradlew build
succeeds
Actual Behavior
./gradlew build
fails with the exception trace at the bottom
Configuration:
- Created from https://start.ktor.io, or from IntelliJ Build #IU-232.9921.47
- Properties:
- 2.3.4
- Netty
- YAML
- Add sample
- Plugins:
- Sessions (implies Routing)
- AutoHeadResponse
- Static Content
- Status Pages
- Compression
- Conditional Headers
- CORS
- Default Headers
- OpenAPI
- Swagger
- Call Logging
- CallId
- Micrometer Metrics
- kotlinx.serialization (implies ContentNegotiation)
- Thymeleaf
Application.kt
fun main(args: Array<String>) {
io.ktor.server.netty.EngineMain.main(args)
}
fun Application.module() {
configureTemplating()
configureSerialization()
configureMonitoring()
configureHTTP()
configureSecurity()
configureRouting()
}
Additional Information
Diff below removes the problem - either of these plugins present, or both of them, cause the build to fail.
diff --git a/src/main/kotlin/com/homeclimatecontrol/plugins/Routing.kt b/src/main/kotlin/com/homeclimatecontrol/plugins/Routing.kt
index 2a48db6..9394014 100644
--- a/src/main/kotlin/com/homeclimatecontrol/plugins/Routing.kt
+++ b/src/main/kotlin/com/homeclimatecontrol/plugins/Routing.kt
@@ -9,12 +9,12 @@ import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
- install(StatusPages) {
- exception<Throwable> { call, cause ->
- call.respondText(text = "500: $cause" , status = HttpStatusCode.InternalServerError)
- }
- }
- install(AutoHeadResponse)
+// install(StatusPages) {
+// exception<Throwable> { call, cause ->
+// call.respondText(text = "500: $cause" , status = HttpStatusCode.InternalServerError)
+// }
+// }
+// install(AutoHeadResponse)
routing {
get("/") {
call.respondText("Hello World!")
Exception trace
io.ktor.server.application.DuplicatePluginException: Please make sure that you use unique name for the plugin and don't install it twice. Conflicting application plugin is already installed with the same key as `StatusPages`
at app//io.ktor.server.application.ApplicationPluginKt.install(ApplicationPlugin.kt:114)
at app//com.homeclimatecontrol.plugins.RoutingKt.configureRouting(Routing.kt:12)
at app//com.homeclimatecontrol.ApplicationTest$testRoot$1$1.invoke(ApplicationTest.kt:14)
at app//com.homeclimatecontrol.ApplicationTest$testRoot$1$1.invoke(ApplicationTest.kt:13)
at app//io.ktor.server.engine.internal.CallableUtilsKt.executeModuleFunction(CallableUtils.kt:51)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:332)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:331)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:356)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.launchModuleByName(ApplicationEngineEnvironmentReloading.kt:331)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$launchModuleByName(ApplicationEngineEnvironmentReloading.kt:32)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:319)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:310)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:338)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:310)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:150)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:277)
at app//io.ktor.server.testing.TestApplicationEngine.start(TestApplicationEngine.kt:144)
at app//io.ktor.server.engine.ApplicationEngine$DefaultImpls.start$default(ApplicationEngine.kt:78)
at app//io.ktor.server.testing.client.TestHttpClientEngine.execute(TestHttpClientEngine.kt:47)
at app//io.ktor.server.testing.client.DelegatingTestClientEngine.execute(DelegatingTestClientEngine.kt:56)
at app//io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:99)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:46)
at io.ktor.client.engine.HttpClientEngine$DefaultImpls.executeWithinCallContext(HttpClientEngine.kt:100)
at io.ktor.client.engine.HttpClientEngine$install$1.invokeSuspend(HttpClientEngine.kt:70)
at io.ktor.client.plugins.HttpSend$DefaultSender.execute(HttpSend.kt:138)
at io.ktor.client.plugins.HttpRedirect$Plugin$install$1.invokeSuspend(HttpRedirect.kt:64)
at io.ktor.client.plugins.HttpCallValidator$Companion$install$3.invokeSuspend(HttpCallValidator.kt:151)
at io.ktor.client.plugins.HttpSend$Plugin$install$1.invokeSuspend(HttpSend.kt:104)
at io.ktor.client.plugins.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:130)
at io.ktor.client.plugins.HttpRequestLifecycle$Plugin$install$1.invokeSuspend(HttpRequestLifecycle.kt:38)
at io.ktor.client.HttpClient.execute$ktor_client_core(HttpClient.kt:191)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:108)
at io.ktor.client.statement.HttpStatement.execute(HttpStatement.kt:47)
at com.homeclimatecontrol.ApplicationTest$testRoot$1.invokeSuspend(ApplicationTest.kt:31)
at io.ktor.server.testing.TestApplicationKt$testApplication$builder$1$1.invokeSuspend(TestApplication.kt:335)
Caused by: io.ktor.server.application.DuplicatePluginException: Please make sure that you use unique name for the plugin and don't install it twice. Conflicting application plugin is already installed with the same key as `StatusPages`
at app//io.ktor.server.application.ApplicationPluginKt.install(ApplicationPlugin.kt:114)
at app//com.homeclimatecontrol.plugins.RoutingKt.configureRouting(Routing.kt:12)
at app//com.homeclimatecontrol.ApplicationTest$testRoot$1$1.invoke(ApplicationTest.kt:14)
at app//com.homeclimatecontrol.ApplicationTest$testRoot$1$1.invoke(ApplicationTest.kt:13)
at app//io.ktor.server.engine.internal.CallableUtilsKt.executeModuleFunction(CallableUtils.kt:51)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:332)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:331)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:356)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.launchModuleByName(ApplicationEngineEnvironmentReloading.kt:331)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$launchModuleByName(ApplicationEngineEnvironmentReloading.kt:32)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:319)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:310)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:338)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:310)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:150)
at app//io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:277)
at app//io.ktor.server.testing.TestApplicationEngine.start(TestApplicationEngine.kt:144)
at app//io.ktor.server.engine.ApplicationEngine$DefaultImpls.start$default(ApplicationEngine.kt:78)
at app//io.ktor.server.testing.client.TestHttpClientEngine.execute(TestHttpClientEngine.kt:47)
at app//io.ktor.server.testing.client.DelegatingTestClientEngine.execute(DelegatingTestClientEngine.kt:56)
at app//io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:99)
at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at app//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at app//kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
at app//kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
at app//kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
at app//kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
at app//kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
at app//kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
Invalid dependencies imported at ApplicationTest.kt for a newly generated project
To reproduce:
-
Generate project using start.ktor.io with added Sessions plugin and sample code
-
Download the project and open it in IDE
Expected:
Generated project is valid
Actual:
To ApplicationTest.kt imported dependencies for added plugins (Sessions), not for test
IntelliJ IDEA Plugin
IDEA Ktor plugin OpenApi spec generation requestBody support
I've not been able to get the Ktor plugin to generate OpenApi requestBody sections.
In the plugin page it mentions OpenAPI request parameters are derived from path and query request parameters, which I have validated but if the route receives an object in the request body that is not reflected in the generated OpenAPI spec.
In the following example the response schema is correctly generated but there is no mention of the requestBody.
fun main() {
embeddedServer(Netty, port = 8080){
install(ContentNegotiation) {
json()
}
routing {
//Add a new pet
post("/pets"){
val pet = call.receive<Pet>()
call.respond(pet)
}
}
}
data class Pet(val name: String, val petType: String)
paths:
/pets:
post:
description: "Add a new pet"
responses:
"200":
description: "OK"
content:
'*/*':
schema:
$ref: "#/components/schemas/Pet"
components:
schemas:
Pet:
type: "object"
properties:
name:
type: "string"
petType:
type: "string"
Does the ktor plugin not support generating requestBody sections?
Server
RejectedExecutionException when a server having an active Websockets connection is stopped
Previously our application was running Ktor version 1.6.8 and we recently upgraded to 2.1.1. Our code was working perfectly fine until upgrading, but now the stop() method of our embeddedServer causes the app to crash.
To give you an idea of the code, we have a start method which creates a new instance of the embedded server:
private var ktorEngine: ApplicationEngine? = null
fun start() {
ktorEngine = embeddedServer(Netty, ktorConfiguration).start()
}
Then a stop method which is what is triggering the crash:
fun stop() {
ktorEngine?.stop()
}
The stack trace is as follows:
kotlinx.coroutines.CompletionHandlerException: Exception in completion handler ChildCompletion@60709f1[job@76cd9d6] for StandaloneCoroutine{Cancelled}@76cd9d6
at kotlinx.coroutines.JobSupport.notifyCompletion(JobSupport.kt:1525)
at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:323)
at kotlinx.coroutines.JobSupport.finalizeFinishingState(JobSupport.kt:240)
at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:906)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:863)
at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:828)
at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:100)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [CoroutineName(ws-writer), StandaloneCoroutine{Cancelled}@76cd9d6, io.ktor.server.netty.EventLoopGroupProxy@174aef3]
Caused by: kotlinx.coroutines.CompletionHandlerException: Exception in completion handler ResumeOnCompletion@e9e4b7b[job@87cd98] for JobImpl{Cancelled}@87cd98
at kotlinx.coroutines.JobSupport.notifyCompletion(JobSupport.kt:1525)
at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:323)
at kotlinx.coroutines.JobSupport.finalizeFinishingState(JobSupport.kt:240)
at kotlinx.coroutines.JobSupport.continueCompleting(JobSupport.kt:935)
at kotlinx.coroutines.JobSupport.access$continueCompleting(JobSupport.kt:27)
at kotlinx.coroutines.JobSupport$ChildCompletion.invoke(JobSupport.kt:1155)
at kotlinx.coroutines.JobSupport.notifyCompletion(JobSupport.kt:1520)
... 14 more
Caused by: java.util.concurrent.RejectedExecutionException: event executor terminated
at io.netty.util.concurrent.SingleThreadEventExecutor.reject(SingleThreadEventExecutor.java:934)
at io.netty.util.concurrent.SingleThreadEventExecutor.offerTask(SingleThreadEventExecutor.java:351)
at io.netty.util.concurrent.SingleThreadEventExecutor.addTask(SingleThreadEventExecutor.java:344)
at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:836)
at io.netty.util.concurrent.SingleThreadEventExecutor.execute0(SingleThreadEventExecutor.java:827)
at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:817)
at io.ktor.server.netty.NettyDispatcher.dispatch(CIO.kt:68)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:159)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
at kotlinx.coroutines.ResumeOnCompletion.invoke(JobSupport.kt:1398)
at kotlinx.coroutines.JobSupport.notifyCompletion(JobSupport.kt:1520)
... 20 more
It appears to be similar to https://youtrack.jetbrains.com/issue/KTOR-2762, though this is happening when stopping a server.
MicrometerMetrics: Prometheus meter registry 1.13.0 generates configuration warning
Log message
io.micrometer.prometheusmetrics.PrometheusMeterRegistry - A MeterFilter is being configured after a Meter has been registered to this registry. All MeterFilters should be configured before any Meters are registered. If that is not possible or you have a use case where it should be allowed, let the Micrometer maintainers know at https://github.com/micrometer-metrics/micrometer/issues/4920.
Stack trace
at io.micrometer.core.instrument.MeterRegistry$Config.logWarningAboutLateFilter(MeterRegistry.java:844)
at io.micrometer.core.instrument.MeterRegistry$Config.meterFilter(MeterRegistry.java:830)
at io.ktor.server.metrics.micrometer.MicrometerMetricsKt$MicrometerMetrics$2.invoke(MicrometerMetrics.kt:138)
at io.ktor.server.metrics.micrometer.MicrometerMetricsKt$MicrometerMetrics$2.invoke(MicrometerMetrics.kt:111)
at io.ktor.server.application.CreatePluginUtilsKt.setupPlugin(CreatePluginUtils.kt:301)
at io.ktor.server.application.CreatePluginUtilsKt.createPluginInstance(CreatePluginUtils.kt:270)
at io.ktor.server.application.CreatePluginUtilsKt.access$createPluginInstance(CreatePluginUtils.kt:1)
at io.ktor.server.application.CreatePluginUtilsKt$createApplicationPlugin$2.install(CreatePluginUtils.kt:90)
at io.ktor.server.application.CreatePluginUtilsKt$createApplicationPlugin$2.install(CreatePluginUtils.kt:83)
Code snippet
install(MicrometerMetrics) {
registry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
}
Auth: Add extra logging to diagnose JWT auth failure
Build version:
all
Steps to reproduce:
1. provide incorrect token via jwt auth
Actual result:
Nothing is logged
Expected result:
Challenge function is called when token verification was failed, so we expect that the reason of failed verification will be written.
Also, it should be possible to write in common logs, therefore, the log message should not contain any sensitive/personal data.
CIO Server: Support for Windows (mingw_x64) target
Ktor Server (CIO engine) is not supported on Windows target. We are evaluating the possibility of using Ktor on different platforms, and not having support for Windows is a real bummer.
project :backend:native > io.ktor:ktor-bom:3.0.0-beta-2
> No matching variant of io.ktor:ktor-server-cio:3.0.0-beta-2 was found. The consumer was configured to find a library for use during 'kotlin-metadata', preferably optimized for non-jvm, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'mingw_x64', attribute 'org.jetbrains.kotlin.platform.type' with value 'native' but:
Provide the API that simplifies disabling CORS for testing purposes
The CORS plugin provides the anyHost
function to allow cross-origin requests from any host. It would be helpful to have similar functions for allowing any HTTP methods and headers - smth like anyMethod
/anyContentType
/ any...
.
Netty: UnsupportedOperationException is thrown when responding in CallSetup and CORS plugin is installed
I have built the following plugin:
val HealthChecks: ApplicationPlugin<HealthConfig> = createApplicationPlugin("HealthChecks", ::HealthConfig) {
on(CallSetup) { call ->
if (call.request.path() != "/monitoring/health-check" && call.request.httpMethod != HttpMethod.Get) return@on
var healthCheckStatus: Map<String, Boolean>
runBlocking {
healthCheckStatus = pluginConfig.performChecks()
}
val statusCode = when (healthCheckStatus.all { it.value }) {
true -> HttpStatusCode.OK
false -> HttpStatusCode.InternalServerError
}
call.respondText(Json.encodeToString(healthCheckStatus), ContentType.Application.Json, statusCode)
}
}
It works accordingly and responds as expected, but it produces the following exception when processing the response.
java.lang.UnsupportedOperationException: Headers can no longer be set because response was already completed
at io.ktor.server.netty.http1.NettyHttp1ApplicationResponse$headers$1.engineAppendHeader(NettyHttp1ApplicationResponse.kt:42)
at io.ktor.server.response.ResponseHeaders.append(ResponseHeaders.kt:57)
at io.ktor.server.response.ResponseHeaders.append$default(ResponseHeaders.kt:48)
at io.ktor.server.response.ApplicationResponsePropertiesKt.header(ApplicationResponseProperties.kt:14)
at io.ktor.server.plugins.cors.CORSKt.corsVary(CORS.kt:226)
FYI, I have CORS enabled with anyHosts()
option.
Add `respondFile` overload with `Path` parameters
There are two LocalFileContent methods in io.ktor.server.response.ApplicationResponseFunctionsJvmKt
public fun LocalFileContent(
baseDir: Path,
relativePath: Path,
contentType: ContentType = ContentType.defaultForFile(relativePath)
): LocalFileContent =
LocalFileContent(baseDir.combineSafe(relativePath), contentType)
public fun LocalFileContent(
baseDir: File,
relativePath: String,
contentType: ContentType = ContentType.defaultForFilePath(relativePath)
): LocalFileContent =
LocalFileContent(baseDir.combineSafe(relativePath), contentType)
But the first one is unused. There is a respondFile
which operates on old File
API
public suspend fun ApplicationCall.respondFile(
baseDir: File,
fileName: String,
configure: OutgoingContent.() -> Unit = {}
) {
val message = LocalFileContent(baseDir, fileName).apply(configure)
respond(message)
}
However we don't have a respondFile
which would work on Path
API.
This is necessary to work with a FileSystem
which does not support .toFile()
(for example in-memory system like com.github.marschall.memoryfilesystem)
This is the code which I think is missing:
suspend fun ApplicationCall.respondFile(
dir: Path,
fileName: Path,
configure: OutgoingContent.() -> Unit = {}
) {
respond(LocalFileContent(dir, fileName).apply(configure))
}
Data truncated in receiveParameters and receiveMultipart
On server, when using call.receiveParameters()
or call.receiveMultipart()
, values are truncated to 65536 characters.
Root cause: io.ktor.server.engine.DefaultTransformJvmKt#multiPartData
doesn't specify formFieldLimit
parameter of CIOMultipartDataBase
which is 65536 by default.
CSRF: The allowOrigin method enables the Origin Header validation
To reproduce, make a / GET
request to the server with the following code:
embeddedServer(Netty, port = 3000) {
install(CSRF) {
allowOrigin("http://example.com")
}
routing {
get {
call.respondText { "OK" }
}
}
}.start(wait = true)
By default, all validations are disabled, but the allowOrigin
method enables the Origin header validation. This behavior is confusing because here allowance enables validation.
Routing: Support accessing the request body in RouteSelector
I want to write a custom route selector (for XML SOAP services). A soap service normally uses POST and one url as endpoint for all messages. The soap message contains a header part, which defines the actual body type. To implement different callbacks/routes in Ktor, I want to create a custom route selector which reads the soap "header". But this header is part of the actual http body.
data class Envelope<T>(val header: Header, val body: Body<T>) {
data class Header(val action: String)
data class Body<T>(val body: T? = null)
}
// current implementation
routing {
post("services) {
val soapMessage = call.receive<Envelope<Nothing?>>()
if (soapMessage.header.action == "FooAction") {
val foo = call.receive<Envelope<Foo>>().body
} else if (soapMessage.header.action == "BarAction") {
val bar = call.receive<Envelope<Bar>>().body
}
}
}
// idea
routing {
route("services) {
soap("FooAction") {
val foo = call.receive<Envelope<Foo>>().body
}
soap("BarAction") {
val bar = call.receive<Envelope<Bar>>().body
}
}
}
To support this implementation, RouteSelector.evaluate
must be suspend
. The actual usage of RouteSelector.evaluate
is already suspending.
I am open to create a PR.
Make the internal Route.swaggerUI method public
I manipulate documentation.yaml that Ktor plugin creates. I want to serve the updated OAS using swaggerUI
method. But publicly available endpoint requires parameter as File. The desired method exists since it takes API as String
but it is set as internal
method.
Method that is mentioned here:
https://github.com/ktorio/ktor/blob/9a67d61bea7659989115e99ac7716daf1c83322e/ktor-server/ktor-server-plugins/ktor-server-swagger/jvm/src/io/ktor/server/plugins/swagger/Swagger.kt#L52
internal fun Route.swaggerUI(
path: String,
apiUrl: String,
api: String,
block: SwaggerConfig.() -> Unit = {}
) {...}
Can we change it? Is there a design decision that I'm not aware of?
CORS check fails when the Origin header has a value without trailing slash
I might have encountered an issue with the configuration and handling of CORS (Cross-Origin Resource Sharing) requests in Ktor, which leads to inconsistent behavior when processing requests with different URL formats.
To reproduce the issue, I have created a minimal example in a GitHub repository: Link to Repository
The CORS configuration in the example project is as follows:
install(CORS) {
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
allowMethod(HttpMethod.Patch)
allowHeader(HttpHeaders.Authorization)
allowHeader("MyCustomHeader")anyHost()
}
When I send the following two requests, one fails while the other succeeds (the requests.http can be found here: https://github.com/jcraane/ktor-cors/blob/main/requests.http_ :
- Failing Request:
- Method: GET
- URL:
localhost:8080/messages
- Headers:
- Content-Type: application/json
- Origin: http://localhost:3000
- Successful Request:
- Method: GET
- URL:
localhost:8080/messages
- Headers:
- Content-Type: application/json
- Origin: http://localhost:3000/
I don't know if this is indeed an issue, but I would expect that both origins would be treated the same. The origin with the trailing /, evaluates to OriginCheckResult.SkipCORS when I debug the request in the debugger.
Test Infrastructure
MockEngine: the ability to set dispatcher is removed
2.3.5 removed the option of setting
/**
* Dispatcher to use with [MockEngine].
*/
public var dispatcher: CoroutineDispatcher = Dispatchers.Default
inside MockEngineConfig. We rely on this for testing and controlling timings inside https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html.
Example usecase: We are testing a polling application that continuously polls a REST endpoint (with delay) for notifications using the Ktor client and sends them to a Kafka topic. In the test we are using Kafka MockProducer with autoComplete: false
, which means we have to use mockProducer.completeNext()
to control timings on the Kafka side. Without the test dispatcher we cannot control the timing on the polling side.
We are also using the test dispatcher to verify the number of ktor client retries for custom retry logic.
Would it be possible to reintroduce it?
Other
Add support for OpenTelemetry plugin for 3.x.x versions
After Ktor release 3.0.0, we also need to provide support for the OpenTelemetry plugin
ReadableByteChannel to ByteReadChannel JVM interop is missing
In earlier releases of Ktor, we could use ReadableByteChannel instances for async IO, however this functionality is mising in 3.0.
This was discovered while migrating Space to Ktor 3.0
Environment, Engine, Application Design
There are no clear responsibilities of Environment, Engine, Application instances and there are several problems in the accessibility of them:
you can't get engine
from Environment or Application, so it's impossible to know port and host if you're running not embeddedServer
. There are also naming issues: we're using server
and application
words in the package names, while they are meaning the same.
As the result we would like to have the design document of what is what and why do we need this. The design should also cover connectors and configuration access.
Ktor clients and servers should use Dispatchers.IO.limitedParallelism(...) wherever possible
Using pure Dispatchers.IO
is prone to thread starvation under heavy load. Since multiple independent use sites can share that dispatcher, there is no safe way to determine a safe maximum thread count, even if manually configured beyond the default limit of 64 threads.
Fixed thread pool dispatchers waste memory and may introduce unnecessary context switches. The latter is also true for single thread dispatchers.
There is Dispatchers.IO elasticity for limited parallelism, which
-
shares threads between all views of
Dispatchers.IO
andDispatchers.Default
, -
reduces context switching,
The withContext() invocation with Dispatchers.Default, Dispatchers.IO, or their views attempts not to switch threads when possible.
-
creates daemon threads (not blocking application exit),
-
but still avoids thread starvation by guaranteeing each view the requested parallelism. If necessary, threads are created beyond the 64-thread limit.
So each use site of Dispatchers.IO
(and possibly other dispatchers as well) should use Dispatchers.IO.limitedParallelism(requiredParallelism)
, with requiredParallelism
set according to the individual use site's requirements.
See also:
Relates to:
Auth: Drop marker interface requirements
We have these two empty interfaces that create a small barrier for developers in decoupling their applications from Ktor when implementing their authentication:
/**
* A marker interface indicating that a class represents credentials for authentication.
*/
public interface Credential
/**
* A marker interface indicating that a class represents an authenticated principal.
*/
public interface Principal
It would be handy to remove this type boundary in the auth functions, since they don't serve any purpose now.
Ktor 3: please publish metadata
I've received a PR:
https://github.com/TWiStErRob/net.twisterrob.cinema/pull/860
but the release doesn't seem to exist anywhere else:
- https://ktor.io/docs/eap/migrating-3.html (no stable docs)
- https://github.com/ktorio/ktor/releases (no release notes)
- YouTrack (no affected version)
Project generator: migrations for 3.0.0
We need to introduce migrations for structural changes between versions Ktor 2 to 3 under the /migrations endpoint in the project generator. This will allow the IDE to automatically perform the migrations.
Common backwards compatibility problems for 3.0
- Coroutine scope from call context is commonly used, and we can introduce it to the RoutingContext by way of Application call property delegation
- Any custom interceptors have the call field moved to context, we can provide a simple extension here for better semantics and easier transition
Introduce Flow API for multi-part
Our current forEachPart
function for Multi-part is very inflexible and would benefit greatly from introducing a simple flow.
Remove reflection utils used only on JVM target from common source set
I've noticed that TypeInfo.reifiedType
makes sense only for the JVM target. We could convert it into an extension-property and deprecate the code not needed in the common source set anymore.
Support androidNative targets
After https://github.com/ktorio/ktor/pull/4187 (support for mingw in server modules) and https://github.com/ktorio/ktor/pull/4191 (support for watchosDeviceArm64), all native
related modules support all available targets except androidNative
ones.
Those targets are Tier 3, according to official docs. Would be nice to add them just for completeness and to simplify depending on Ktor in KMP projects(libraries) which supports all K/N targets.
Make Cookie class Serializable
Hello, I am trying to implement a persistant cookie storage for a Ktor client in KMM.
The documentation is referring me to the implementation of AcceptAllCookiesStorage: <https://github.com/ktorio/ktor/blob/main/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/cookies/AcceptAllCookiesStorage.kt>
I see how I can implement my feature, but AcceptAllCookiesStorage is storing Cookie instances in array in memory, but if i want to store them in in where else in need that class to be Serializable
Please add kotlinx.serialization.Serializable annotation to Cookie and GMTDate classes
Thank you
ByteWriteChannel.flush is not Waiting Until Flushing the Internal Buffer to the Destination
Closing socket and selector leaks descriptor on native
- Open TCP/UDP socket
- Open a read channel
- Close socket and SelectorManager
- Descriptor is not closed due to a bug
@Test
fun testDescriptorClose() = testSuspend {
val selector = SelectorManager()
val socket = aSocket(selector)
.udp()
.bind(InetSocketAddress("127.0.0.1", 8000))
val descriptor = (socket as DatagramSocketNative).selectable.descriptor
socket.openReadChannel()
socket.close()
selector.close()
selector.coroutineContext[Job]?.join()
val isDescriptorValid = fcntl(descriptor, F_GETFL) != -1 || errno != EBADF
check(!isDescriptorValid) { "Descriptor was not closed" }
}
Maybe this is related to KTOR-4047 but I cannot access the project to check it.
Add support for mingw to ktor-network in order to make server and client work on windows
Seeing as most targets are already done and making ktor truly multiplatform, this target is a very missed feature to make sure we can develop and release products for Windows using ktor. Would it be possible to add this for ktor 2.0.0?