Changelog 1.5 version
1.5.4
released 30th April 2021
Client
Ktor fails to deliver response with error: failed with exception: kotlinx.coroutines.JobCancellationException: Parent job is Completed;
Kotlin version 1.5.4
Issue: API Call failed with reason: failed with exception: kotlinx.coroutines.JobCancellationException: Parent job is Completed;
Hi there, this piece of code is causing an exception:
ktor/ktor-client/ktor-client-core/common/src/io/ktor/client/features/observer/ResponseObserver.kt
@Suppress("UNCHECKED_CAST")
(response.coroutineContext[Job] as CompletableJob).complete()
proceedWith(context.response)
Even though I am not a coroutine expert I suspect that when HttpClientCall -> receive method is called and more specifically the part below, the job is already completed and this is causing the exception to be thrown.
This is a quite major issue because it results in randomly losing API call responses.
val result = currentClient.responsePipeline.execute(this, subject).response
ref: https://github.com/ktorio/ktor/blob/764cad998700430f54f3e6c77edb53159d75d7c3/ktor-client/ktor-client-core/common/src/io/ktor/client/call/HttpClientCall.kt#L74
Is there a workaround or a fix?
java.nio.charset.IllegalCharsetNameException: %s
Some users of my app are getting the following crash:
Fatal Exception: java.nio.charset.IllegalCharsetNameException: %s
at java.nio.charset.Charset.checkName(Charset.java:317)
at java.nio.charset.Charset.lookup2(Charset.java:519)
at java.nio.charset.Charset.lookup(Charset.java:497)
at java.nio.charset.Charset.forName(Charset.java:563)
at io.ktor.http.ContentTypesKt.charset(ContentTypesKt.java:271)
at io.ktor.http.HttpMessagePropertiesKt.charset(HttpMessagePropertiesKt.java:79)
at io.ktor.client.statement.HttpStatementKt.readText(HttpStatementKt.java:169)
at io.ktor.client.statement.HttpStatementKt.readText$default(HttpStatementKt.java:168)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidationKt.java:36)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(BaseContinuationImpl.java:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.java:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.java:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.java:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.java:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.java:665)
Looks like that for some reason the charset seems to be %s
. I am not sure why that is the case. However, I do not think that this should crash the app. I think it would be better if Ktor could just fall back to UTF8 when the charset is invalid. Would that be possible in Ktor?
I am using a manually built version of Ktor based on this commit: https://github.com/Thomas-Vos/ktor/commit/6dceed335d2de689dd30e3cb1d62d571c8061041
java.lang.IllegalStateException: Mutex is not locked
It is possible that CallLogging
feature may crash with "java.lang.IllegalStateException: Mutex is not locked" because of cancellation during beginLogging -> mutex.lock
ClassCastException: kotlin.coroutines.intrinsics.CoroutineSingletons cannot be cast to ...
Hi there. what is a good way to approach debugging this?
I am using suspend fun as well as Flows and awaits, but I am not sure where to start with this.
on Slack, Roman suggested this might be ktor related.
Exception in thread "main" java.lang.ClassCastException: kotlin.coroutines.intrinsics.CoroutineSingletons cannot be cast to io.ktor.client.statement.HttpResponse
at com.github.wakingrufus.kalibrate.example.MainKt$main$2$4$1$1$1.invoke(Main.kt:68)
at com.github.wakingrufus.kalibrate.example.MainKt$main$2$4$1$1$1.invoke(Main.kt)
at com.github.wakingrufus.kalibrate.scenario.Step.invoke(Step.kt:15)
at com.github.wakingrufus.kalibrate.scenario.StepContainer$invoke$1.invokeSuspend(Simulation.kt:93)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:86)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:61)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.github.wakingrufus.kalibrate.scenario.StepContainer.invoke(Simulation.kt:91)
at com.github.wakingrufus.kalibrate.scenario.Simulation.runSetup(Simulation.kt:75)
at com.github.wakingrufus.kalibrate.scenario.Simulation$singletonWorkPattern$1$1$1.invokeSuspend(Simulation.kt:25)
at com.github.wakingrufus.kalibrate.scenario.Simulation$singletonWorkPattern$1$1$1.invoke(Simulation.kt)
at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61)
at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:212)
at kotlinx.coroutines.flow.FlowKt__MergeKt$flattenConcat$$inlined$unsafeFlow$1.collect(SafeCollector.common.kt:114)
at kotlinx.coroutines.flow.internal.ChannelFlowMerge$collectTo$$inlined$collect$1$lambda$1.invokeSuspend(Merge.kt:69)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:86)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:61)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.github.wakingrufus.kalibrate.KalibrateDslBuilder.invoke(KalibrateDsl.kt:92)
at com.github.wakingrufus.kalibrate.KalibrateDslKt$kalibrate$1.invoke(KalibrateDsl.kt:125)
at com.github.wakingrufus.kalibrate.KalibrateDslKt$kalibrate$1.invoke(KalibrateDsl.kt)
at com.xenomachina.argparser.SystemExitExceptionKt.mainBody(SystemExitException.kt:74)
at com.xenomachina.argparser.SystemExitExceptionKt.mainBody$default(SystemExitException.kt:72)
at com.github.wakingrufus.kalibrate.KalibrateDslKt.kalibrate(KalibrateDsl.kt:124)
at com.github.wakingrufus.kalibrate.example.MainKt.main(Main.kt:16)
source:
https://github.com/wakingrufus/kalibrate
to reproduce:
cd kalibrate-example/
./example.sh --scenario deploy
When the main thread executes runBlocking, using the iOS engine will cause a deadlock
When executing Unit Test, runBlocking must be executed to call suspend.
But IosClientEngine.kt
specifies the callback thread as the main thread when creating NSURLSession. Once the main thread is locked, this callback cannot return.
Should change
val session = NSURLSession.sessionWithConfiguration(
configuration,
responseReader.freeze(),
delegateQueue = NSOperationQueue.mainQueue()
)
to
val session = NSURLSession.sessionWithConfiguration(
configuration,
responseReader.freeze(),
delegateQueue = NSOperationQueue.currentQueue()
)
Apache HTTP Client does not send Content-Length header if body is empty content
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1333
Ktor Version and Engine Used (client or server and name)
io.ktor:ktor-client-core:1.2.4
io.ktor:ktor-client-core-jvm:1.2.4
io.ktor:ktor-client-apache:1.2.4
Describe the bug
Apache HTTP Client does not send Content-Length header if body is empty content.
CIO send the header correctly.
To Reproduce
Steps to reproduce the behavior:
- Write the following
val apacheClient = HttpClient(Apache)
apacheClient.put<HttpResponseData>("http://example.com/")
apacheClient.post<HttpResponseData>("http://example.com/")
apacheClient.patch<HttpResponseData>("http://example.com/")
- Run on jvm
- The sent header does not contain Content-Length
{
"accept-charset": "UTF-8",
"accept": "*/*",
"user-agent": "Ktor client",
"host": "example.com",
"connection": "Keep-Alive"
}
Some endpoints that do not require body return 411 Length Required.
Expected behavior
Content-Length: 0
Should be sent.
Apache seems not to send Content-Length
if entity is null.
Following code sent the header correctly.
apacheClient.put<HttpResponseData>("http://example.com/"){
body = ""
}
The relevant part of the code in ktor:
https://github.com/ktorio/ktor/blob/f3858b2bd95dcb35aa7755451403e7cab1dd678c/ktor-client/ktor-client-apache/jvm/src/io/ktor/client/engine/apache/ApacheRequestProducer.kt#L122-L155
and org.apache.http.client.methods.RequestBuilder 's build() method.
Digest authentition: using the realm of the 401 response to an initial incomplete request for an immediate second complete request including realm.
There are cases where the client of a digest authentication has no knowledge of the realm value. For this scenario, a case in which the realm contained in the 401 response is immediately used should be added.
How can I get the download progress bar through httpClient.responsePipeline.intercept
Core
ApplicationEngineEnvironmentBuilder.module { … } is executed twice on Exception
When initializing the application with applicationEngineEnvironment(…)
any exception on module { … }
will lead to the block being executed twice. This may lead to unexpected results.
For example:
val env = applicationEngineEnvironment {
config = HoconApplicationConfig(ConfigFactory.load())
module {
install(DefaultHeaders) {
header(HttpHeaders.Server, "My Server")
}
// simulate that something goes wrong
throw Exception("I am hidden.")
}
connector {
host = config.property("deployment.host").getString()
port = config.property("deployment.port").getString().toInt()
}
}
embeddedServer(Netty, env).start(true)
Here the real exception "I am hidden" is shadowed by
io.ktor.application.DuplicateApplicationFeatureException: Conflicting application feature is already installed with the same key as `Default Headers`
since the feature is installed twice.
The originates from this code in ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication
:
try {
launchModuleByName(name, currentClassLoader, newInstance)
} catch (_: Throwable) {
module(newInstance)
}
I would expect this to only catch exceptions specific to launchModuleByName
or handle errors differently.
ByteBufferChannel.readRemaining suspend when should not
Reproducer:
suspend fun main() {
val channel = ByteChannel(true)
println(channel.availableForRead) //prints 0
channel.writePacket {
writeText("hello")
}
println(channel.availableForRead) //prints 5
val packet = channel.readRemaining(channel.availableForRead.toLong()) //suspend...
// val packet = channel.readPacket(channel.availableForRead)
println(packet.readText())
}
Channel has 5 bytes available for read.
Contract of availableForRead
says, that:
/**
* Returns number of bytes that can be read without suspension. Read operations do no suspend and return
* immediately when this number is at least the number of bytes requested for read.
*/
public actual val availableForRead: Int
But in readRemaining
it suspends on trying to read...
Using readPacket
it works ok.
Using ByteChannelSequential on JVM don't suspend on both calls. On K/N the same - both don't suspends.
Looks like it regression after KTOR-1268. Caused by this change: https://github.com/ktorio/ktor/pull/2214/files#diff-408be3eaf4a8ec196449c0483c1525a24450590d9473ebcc3a5bc3798326e3e7L2234-R2222
Now channel is trying to read one byte, even if it's not needed.
Reproduced on:
kotlin: 1.4.32
ktor: 1.5.3
ktor-html-builder: exceptions in DSL are swallowed when StatusPages feature is installed
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/541
Simple demo:
Expected behavior: I would expect the default to involve a 500 and the exception being logged somewhere, hopefully customizable via StatusPages. Rendering part of the DSL's execution with a 200 is highly misleading, to say the least.
K/N: ByteChannelSequential.readPacket freeze
Minimal reproducer:
on K/N:
fun main() = runBlocking {
val channel = ByteChannel(true)
channel.writePacket {
writeText("h")
}
channel.close()
val packet = withTimeoutOrNull(1000) {
// val packet = channel.readRemaining(5L)
channel.readPacket(5)
}
println(packet?.readText())
}
Channel closed, but readPacket freezes forever - even not fail by timeout.
Same behavior is if using ByteChannelSequentialJVM(IoBuffer.Empty, true)
on K/JVM.
If using ByteBufferChannel on JVM - behavior is ok, exception is thrown:
Exception in thread "main" kotlinx.coroutines.channels.ClosedReceiveChannelException: Unexpected EOF: expected 4 more bytes
at io.ktor.utils.io.ByteBufferChannel.readFullySuspend(ByteBufferChannel.kt:598)
at io.ktor.utils.io.ByteBufferChannel.readFully(ByteBufferChannel.kt:590)
at io.ktor.utils.io.ByteBufferChannel.readPacketSuspend(ByteBufferChannel.kt:806)
at io.ktor.utils.io.ByteBufferChannel.readPacket$suspendImpl(ByteBufferChannel.kt:792)
at io.ktor.utils.io.ByteBufferChannel.readPacket(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteReadChannelKt.readPacket(ByteReadChannel.kt:202)
at AppKt$main$1$packet$1.invokeSuspend(App.kt:57)
If to use readRemaining
- for any channel, behavior is the same, it reads packet with one letter and prints it.
Original issue: TCP socket freeze on K/N
Minimal reproducer:
Server (K/JVM):
@OptIn(InternalAPI::class)
suspend fun main() {
val connection = aSocket(SelectorManager()).tcp().bind(port = 8000).accept().connection()
while (true) {
delay(1000)
connection.output.writePacket(buildPacket {
writeText("1".repeat(128))
})
connection.output.flush()
}
}
Client (K/Native, K/JVM - same code):
@OptIn(InternalAPI::class)
fun main() = runBlocking {
val connection = aSocket(SelectorManager()).tcp().connect("0.0.0.0", port = 8000).connection()
while (true) {
val packet = withTimeoutOrNull(5000) {
println("await")
val packet = connection.input.readRemaining(128)
// val packet = connection.input.readPacket(128)
require(packet.remaining == 128L) { "Length should be 128 but was ${packet.remaining}" }
packet
}
println("received: ${packet?.readText()}")
}
}
Here, if to start server, then, start client, on client side there will be output:
await
received: 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
await
received: 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
After several seconds, stop the server.
Client on K/Native fails with:
Uncaught Kotlin exception: kotlin.IllegalArgumentException: Length should be 128 but was 0
It's expected, channel closed, and no bytes to read
Client on K/JVM fails with same exception:
Exception in thread "main" java.lang.IllegalArgumentException: Length should be 128 but was 0
Also expected.
But If to replace readRemaining
with readPacket
and do the same (start server, start client, stop server after several seconds):
Client on K/Native just freezes - prints only await
and don't fail on 2 seconds timeout.
Not expected at all!
Client on K/JVM fail with:
Exception in thread "main" kotlinx.coroutines.channels.ClosedReceiveChannelException: Unexpected EOF: expected 128 more bytes
at io.ktor.utils.io.ByteBufferChannel.readFullySuspend(ByteBufferChannel.kt:598)
at io.ktor.utils.io.ByteBufferChannel$readFullySuspend$1.invokeSuspend(ByteBufferChannel.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:86)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:61)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at AppKt.main(App.kt:24)
at AppKt.main(App.kt)
But this is expected.
BTW, if to reduce timeout to 2 seconds, K/JVM will fail by timeout for readRemaining
case, but K/N will fail with expected exception.
Reproduced on:
kotlin: 1.4.32
ktor: 1.5.3
os: macos
K/N target: macos, both release and debug
Docs
Timeout documentation is inaccurate
This issue was imported from GitHub issue: https://github.com/ktorio/ktorio.github.io/issues/268
https://ktor.io/clients/http-client/features/timeout.html
The Client Timeout Feature states that "By default all these timeouts are infinite"; this doesn't seem to be the case with either the Apache or CIO engines where unless I explicitly set a timeout I get:
Apache
io.ktor.network.sockets.SocketTimeoutException: Socket timeout has been expired [url=http://localhost:1080/api/import/tickets/v1/file, socket_timeout=unknown] ms
at io.ktor.client.features.HttpTimeoutKt.SocketTimeoutException(HttpTimeout.kt:170)
at io.ktor.client.engine.apache.ApacheHttpRequestKt$sendRequest$$inlined$suspendCancellableCoroutine$lambda$2.failed(ApacheHttpRequest.kt:44)
at org.apache.http.concurrent.BasicFuture.failed(BasicFuture.java:137)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.executionFailed(DefaultClientExchangeHandlerImpl.java:101)
at org.apache.http.impl.nio.client.AbstractClientExchangeHandler.failed(AbstractClientExchangeHandler.java:426)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.timeout(HttpAsyncRequestExecutor.java:387)
at org.apache.http.impl.nio.client.InternalIODispatch.onTimeout(InternalIODispatch.java:92)
at org.apache.http.impl.nio.client.InternalIODispatch.onTimeout(InternalIODispatch.java:39)
at org.apache.http.impl.nio.reactor.AbstractIODispatch.timeout(AbstractIODispatch.java:175)
at org.apache.http.impl.nio.reactor.BaseIOReactor.sessionTimedOut(BaseIOReactor.java:261)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.timeoutCheck(AbstractIOReactor.java:502)
at org.apache.http.impl.nio.reactor.BaseIOReactor.validate(BaseIOReactor.java:211)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:280)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.net.SocketTimeoutException: 10,000 milliseconds timeout on connection http-outgoing-0 [ACTIVE]
... 11 common frames omitted
CIO (which doesn't seem to accept specifying timeouts at all)
kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 15000 ms
at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:149)
at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:119)
at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:493)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:272)
at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:68)
at java.base/java.lang.Thread.run(Thread.java:834)
Review documentation for the onUpload/onDownload client callbacks
Add links to complete examples (github/zip) on every quickstart sub-page
This issue was imported from GitHub issue: https://github.com/ktorio/ktorio.github.io/issues/96
Pages like https://ktor.io/quickstart/quickstart/gradle.html lack ready-to-download (and use) example projects
Update documentation for FatJar generation in ktor.io
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1004
Ktor Version
1.1.3 with Tomcat and Gradle
Operating System
Operating system: MacOS High Sierra
Feedback
By following the steps in documentation to generate a FatJar, whenever I try to execute my Ktor application by using the command java -jar pathToFatJar
, I receive de following error:
Exception in thread "main" java.lang.UnsupportedOperationException: Packages and file facades are not yet supported in Kotlin reflection. Meanwhile please use Java reflection to inspect this class: class com.tidelevel.ApplicationKt
at kotlin.reflect.jvm.internal.KClassImpl.reportUnresolvedClass(KClassImpl.kt:301)
at kotlin.reflect.jvm.internal.KClassImpl.access$reportUnresolvedClass(KClassImpl.kt:43)
at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:53)
at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:44)
at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:92)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
at kotlin.reflect.jvm.internal.KClassImpl$Data.getDescriptor(KClassImpl.kt)
at kotlin.reflect.jvm.internal.KClassImpl$Data$objectInstance$2.invoke(KClassImpl.kt:106)
at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(ReflectProperties.java:62)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
at kotlin.reflect.jvm.internal.KClassImpl$Data.getObjectInstance(KClassImpl.kt)
at kotlin.reflect.jvm.internal.KClassImpl.getObjectInstance(KClassImpl.kt:239)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createModuleContainer(ApplicationEngineEnvironmentReloading.kt:328)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.executeModuleFunction(ApplicationEngineEnvironmentReloading.kt:317)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:275)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:127)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:247)
at io.ktor.server.tomcat.TomcatApplicationEngine.start(TomcatApplicationEngine.kt:108)
at io.ktor.server.tomcat.EngineMain.main(EngineMain.kt:16)
This error just happen when trying to execute the FatJar in command line, everything works fine when running in Intellij. I have seen similar issues at:
- https://github.com/ktorio/ktor/issues/386
- https://stackoverflow.com/questions/54282790/ktor-running-fat-jar-throws-java-lang-unsupportedoperationexceptionpackages-an
By changing the mainClassName
property to correspond to my own Application class, everything seems to work fine! I am not sure if the documentation needs to be updated in this page or if I did something wrong.
Hope I receive an answer from you!
Wrong Tabs Name in Code Blocks
It should be Groovy
Kotlin
Maven
instead of Groovy
Kotlin
Kotlin
https://ktor.io/docs/testing.html#example-with-dependencies
Duplicate server `Features` Section on the Documentation Website
Documentation for GraalVM
Documentation on Cancelling Requests in Client.
Should cover multiplatform scenarios also
Requests page has incorrect code example for post query
https://ktor.io/clients/http-client/quick-start/requests.html -- This is another page that describes post requests (among with other requests).
First of all, it does not teach what is contentType
parameter of the TextContent
constructor (please, add explanation)
Second of all, the only example about using JsonFeature
tells:
If you install the JsonFeature, and set the content type to application/json you can use arbitrary instances as the body, and they will be serialized as JSON:
but in the code snippet provided for this example there is no contentType
set, and it has incorrect code (please, fix):
// !!!!!!! @Serializable is missing here !!!!!!
data class HelloWorld(val hello: String)
val client = HttpClient(Apache) {
install(JsonFeature) {
serializer = GsonSerializer {
// Configurable .GsonBuilder
serializeNulls()
disableHtmlEscaping()
}
}
}
client.post<Unit> {
url("http://127.0.0.1:8080/")
body = HelloWorld(hello = "world")
// !!!!!!! contentType(ContentType.Application.Json) is missing here !!!!!!
}
Fat JAR documentation lacks information
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/386
Followed instruction here https://ktor.io/servers/deploy.html for fat jar deployment
When I run java -jar myjar.jar , i get main class could'nt be found in io.ktor.server.netty.DevelopmentEngine.
Project runs fine in IDEA
Any idea why?
Generator
Wizard: Remove kotlinx-html Space Repository from Gradle Template
Including Kotlinx HTML DSL as the templating language results in the following line in the configuration:
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") }
Since kotlinx-html-jvm:0.7.3 is already published on mavenCentral, there is no need for this line anymore going forward, and it can be removed. (Relates to KTOR-2203, which can be closed, I suppose.)
No templates/thymeleaf directory in new project
IntelliJ IDEA 2021.1 with Ktor plugin 1.5.3
- New Project - Ktor
- Add Feature - Thymeleaf
- Open Templating.kt file
Actual result: templates/thymeleaf/
is mentioned in code but there is no such directory and index
file in project
fun Application.configureTemplating() {
install(Thymeleaf) {
setTemplateResolver(ClassLoaderTemplateResolver().apply {
prefix = "templates/thymeleaf/"
suffix = ".html"
characterEncoding = "utf-8"
})
}
routing {
get("/html-thymeleaf") {
call.respond(ThymeleafContent("index", mapOf("user" to ThymeleafUser(1, "user1"))))
}
}
}
"IllegalArgumentException: Neither port nor sslPort specified" when running via Gradle Application plugin
To reproduce create a new project via the IDEA plugin without any feature and run generated application using Gradle:
./gradlew run
As a result the app crashes with the following error:
> Task :run FAILED
2021-04-08 16:20:57.086 [main] TRACE Application - No configuration provided: neither application.conf nor system properties nor command line options (-config or -P:ktor...=) provided
Exception in thread "main" java.lang.IllegalArgumentException: Neither port nor sslPort specified. Use command line options -port/-sslPort or configure connectors in application.conf
at io.ktor.server.engine.CommandLineKt$commandLineEnvironment$environment$1.invoke(CommandLine.kt:142)
at io.ktor.server.engine.CommandLineKt$commandLineEnvironment$environment$1.invoke(CommandLine.kt)
at io.ktor.server.engine.ApplicationEngineEnvironmentBuilder.build(ApplicationEngineEnvironment.kt:110)
at io.ktor.server.engine.ApplicationEngineEnvironmentKt.applicationEngineEnvironment(ApplicationEngineEnvironment.kt:46)
at io.ktor.server.engine.CommandLineKt.commandLineEnvironment(CommandLine.kt:61)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:21)
Wizard: Different serialization engines should be made mutually exclusive
Adding "Gson" and "Kotlinx.Serialization" dependencies in the same wizard project generates the following code:
install(ContentNegotiation) {
gson {
}
}
install(ContentNegotiation) {
json()
}
// . . .
routing {
get("/json/gson") {
call.respond(mapOf("hello" to "world"))
}
}
routing {
get("/json/kotlinx-serialization") {
call.respond(mapOf("hello" to "world"))
}
}
This (expectedly) crashes at first run:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
Caused by: io.ktor.application.DuplicateApplicationFeatureException: Conflicting application feature is already installed with the same key as `ContentNegotiation`
at io.ktor.application.ApplicationFeatureKt.install(ApplicationFeature.kt:82)
at com.example.ApplicationKt.module(Application.kt:27)
at com.example.ApplicationKt.module$default(Application.kt:21)
Selecting custom package name in Ktor wizard still results in example.com import in ApplicationTest.kt
Infrastructure
EAP Naming: main-number conflicts with dependencies
If you want to use the latest eap release of Ktor and use a library, which uses Ktor as api
dependency, eg a Ktor plugin, Gradle don't use the main-buildNumber
release over a published version.
Reason: 1.5.3
is preferred over main-133
. The eap builds should use the version property with the prefix instead, eg 1.6.0-main-133
. This tells Gradle to use the main
eap version for all dependencies, because 1.6.0
> 1.5.4
.
See the attached project
Run the test with/without removed line implementation(ratelimit)
in build.gradle.kts
.
Add watchosX64 as an architecture
The new simulator requires watchosX64 (added in Kotlin 1.5.0). Please add it to the supported architectures
Migrate to Dokka 1.4.0
Curl Add Path to new default Homebrew installation
If you want to build KTOR on a new fresh Mac, you need curl. The primary option to install curl is using Homebrew, which change the default installation path from /usr/local
to /opt/homebrew
from 2.5.10. The installation path will only change for new brew installation, to prevent crashing stable installation. On Apple M1 chip, it will always use the new installation path by default.
Fix:
Add "/opt/homebrew/opt/curl/include/curl",
to the build script
IntelliJ IDEA Plugin
Wrong parameter passed to embeddedServer on generated wizard with CIO option
When selecting the CIO option in the Project Wizard, it uses "Netty" as parameter to the embeddedServer function as opposed to CIO.
Generated project with specific security and session features selected fails to compile / run
Created selecting a bunch of features. Security.kt imports Auth0 which is not referenced.
In addition
- makeJwtVerifier is not implemented
- MySession is not implemented
Ktor plugin memory leak in New Project wizard
IJ 212 Nightly
- Open New Project wizard
- Select Ktor
- Close New Project wizard
2021-04-30 10:44:27,515 [ 181985] ERROR - tellij.openapi.util.ObjectTree - Memory leak detected: 'io.ktor.initializr.intellij.features.KtorFeaturesStep' of class io.ktor.initializr.intellij.features.KtorFeaturesStep is registered in Disposer but wasn't disposed.
Register it with a proper parentDisposable or ensure that it's always disposed by direct Disposer.dispose call.
See https://jetbrains.org/intellij/sdk/docs/basics/disposers.html for more details.
The corresponding Disposer.register() stacktrace is shown as the cause:
java.lang.RuntimeException: Memory leak detected: 'io.ktor.initializr.intellij.features.KtorFeaturesStep' of class io.ktor.initializr.intellij.features.KtorFeaturesStep is registered in Disposer but wasn't disposed.
Register it with a proper parentDisposable or ensure that it's always disposed by direct Disposer.dispose call.
See https://jetbrains.org/intellij/sdk/docs/basics/disposers.html for more details.
The corresponding Disposer.register() stacktrace is shown as the cause:
at com.intellij.openapi.util.ObjectTree.assertIsEmpty(ObjectTree.java:227)
at com.intellij.openapi.util.Disposer.assertIsEmpty(Disposer.java:167)
at com.intellij.openapi.util.Disposer.assertIsEmpty(Disposer.java:162)
at com.intellij.openapi.application.impl.ApplicationImpl.disposeContainer(ApplicationImpl.java:246)
at com.intellij.openapi.application.impl.ApplicationImpl.disposeSelf(ApplicationImpl.java:263)
at com.intellij.openapi.application.impl.ApplicationImpl.doExit(ApplicationImpl.java:650)
at com.intellij.openapi.application.impl.ApplicationImpl.exit(ApplicationImpl.java:603)
at com.intellij.openapi.application.impl.ApplicationImpl.exit(ApplicationImpl.java:592)
at com.intellij.openapi.application.ex.ApplicationEx.exit(ApplicationEx.java:73)
at com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame$3.windowClosing(WelcomeFrame.java:117)
at java.desktop/java.awt.AWTEventMulticaster.windowClosing(AWTEventMulticaster.java:357)
at java.desktop/java.awt.AWTEventMulticaster.windowClosing(AWTEventMulticaster.java:357)
at java.desktop/java.awt.Window.processWindowEvent(Window.java:2090)
at java.desktop/javax.swing.JFrame.processWindowEvent(JFrame.java:298)
at java.desktop/java.awt.Window.processEvent(Window.java:2049)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5027)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2784)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4859)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:778)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:751)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:749)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:748)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:896)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:768)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:434)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:825)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$9(IdeEventQueue.java:433)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:842)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:487)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Caused by: java.lang.Throwable
at com.intellij.openapi.util.ObjectNode.<init>(ObjectNode.java:31)
at com.intellij.openapi.util.ObjectTree.createNodeFor(ObjectTree.java:99)
at com.intellij.openapi.util.ObjectTree.register(ObjectTree.java:60)
at com.intellij.openapi.util.Disposer.register(Disposer.java:73)
at com.intellij.util.Alarm.<init>(Alarm.java:130)
at com.intellij.openapi.ui.LoadingDecorator.<init>(LoadingDecorator.java:54)
at com.intellij.openapi.ui.LoadingDecorator.<init>(LoadingDecorator.java:47)
at com.intellij.openapi.ui.LoadingDecorator.<init>(LoadingDecorator.java:43)
at com.intellij.ui.components.JBLoadingPanel.lambda$new$0(JBLoadingPanel.java:29)
at com.intellij.ui.components.JBLoadingPanel.<init>(JBLoadingPanel.java:38)
at com.intellij.ui.components.JBLoadingPanel.<init>(JBLoadingPanel.java:29)
at io.ktor.initializr.intellij.features.KtorFeaturesStep.<init>(KtorFeaturesStep.kt:33)
at io.ktor.initializr.intellij.KtorModuleBuilder.createWizardSteps(KtorModuleBuilder.kt:63)
at com.intellij.ide.util.newProjectWizard.StepSequence.addStepsForBuilder(StepSequence.java:52)
at com.intellij.ide.projectWizard.ProjectTypeStep.<init>(ProjectTypeStep.java:234)
at com.intellij.ide.projectWizard.NewProjectWizard.init(NewProjectWizard.java:57)
at com.intellij.ide.projectWizard.NewProjectWizard.<init>(NewProjectWizard.java:46)
at com.intellij.ide.actions.NewProjectAction.actionPerformed(NewProjectAction.java:20)
at com.intellij.openapi.actionSystem.ex.ActionUtil.performAction(ActionUtil.java:247)
at com.intellij.openapi.actionSystem.ex.ActionUtil.performActionDumbAware(ActionUtil.java:236)
at com.intellij.openapi.actionSystem.ex.ActionUtil.performActionDumbAwareWithCallbacks(ActionUtil.java:229)
at com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenActionsUtil.performAnActionForComponent(WelcomeScreenActionsUtil.java:96)
at com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenActionsUtil$ToolbarTextButtonWrapper$1.actionPerformed(WelcomeScreenActionsUtil.java:56)
at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1967)
at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2308)
at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:270)
at java.desktop/java.awt.Component.processMouseEvent(Component.java:6652)
at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3345)
at java.desktop/java.awt.Component.processEvent(Component.java:6417)
at java.desktop/java.awt.Container.processEvent(Container.java:2263)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5027)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4859)
at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4918)
at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4547)
at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4488)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2307)
at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2784)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4859)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:778)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:751)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:749)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:748)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:896)
at com.intellij.ide.IdeEventQueue.dispatchMouseEvent(IdeEventQueue.java:828)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:765)
... 11 more
Unable to add Ktor module to KMM project
To reproduce:
- Create KMM project
- Add Ktor module
Expected: Ktor module is added
Actual: Ktor module is not added with an error message "Error adding module to project: Module already exists"
On the attached video root module is called "karta" and new Ktor module is "zzz", but the error eventually says that module "karta" already exists.
Tried to restart IDE and and invalidate caches.
IU-211.6693.111, JRE 11.0.10+9-b1341.35x64 JetBrains s.r.o., OS Mac OS X(x86_64) v11.2.3, screens 5120.0x2880.0, 5120.0x2880.0, 2880.0x5120.0; Retina
Empty get/post routes with non-empty context are not visible in Endpoints VIew
Example:
route("path1/path2") {
get {}
post {}
}
path1/path2
will not be visible in Endpoints View while it must be there twice: for POST and for GET methods.
Server
MultiPartData.readAllParts throws IOException when the epilogue is omitted
Stack trace:
java.io.IOException: Broken delimiter occurred
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invokeSuspend(Delimited.kt:58)
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invoke(Delimited.kt)
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invoke(Delimited.kt)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend$suspendImpl(ByteBufferChannel.kt:1822)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend(ByteBufferChannel.kt)
at io.ktor.utils.io.DelimitedKt.skipDelimiterSuspend(Delimited.kt:56)
at io.ktor.utils.io.DelimitedKt.skipDelimiter(Delimited.kt:51)
at io.ktor.http.cio.MultipartKt$parseMultipart$1.invokeSuspend(Multipart.kt:378)
Reproducible with the following command using the attached file:
nc localhost 8080 < req.txt
The route "/" ends up as "///" in Route.toString()
This test passes -- note the ///
@Test
internal fun routeSlashes() {
withTestApplication({
routing {
get("/") {
call.respond("OK: ${(call as RoutingApplicationCall).route}")
}
}
}) {
with(handleRequest(HttpMethod.Get, "/")) {
assertEquals("OK: ///(method:GET)", response.content)
}
}
}
If the route is declared with get("")
, Route.toString()
results in /(method:GET)
, which is what I would expect in both cases.
Nesting routes doesn't work with GET requests
While working with Ktor i found out a very weird issue. In the code below whenever I make a request with GET method to /api/user/sth
app responds with status 404. I tried it with every other method and every single one worked except get.
route("/api/") {
route("/user/*") {
handle {
println(call.request.uri)
}
}
}
Other
catch exception generated by respondWriteChannelContent
exception handler installed via statuspage doesn't catch exceptions throws by respondWriteChannelContent.
For example with the ContentNegotiation plugin when I have a serialization error I just have a log: io failed
generated here and catched here.
I'd like to be able to intercept or catch the exception so I can have more useful error message.
Improve diagnostics for exceptions inherited from IOException
Certain exceptions, like ConnectException
is caught by IOException
filter in logFailure
, and produce unhelpful "io failed" message. This should be improved to provide actual exception type and message.
developmentMode is on by default in tests
Hi,
First of all, thank you for creating and maintaining ktor, we enjoy using it a lot! The issue we notice that after v1.5.2, developmentMode value is set as true by default in tests (more precisely it is on when assertions are enabled) -> https://github.com/ktorio/ktor/blob/de742d1adfbbb994bedda198e6739c724e62a974/ktor-utils/jvm/src/io/ktor/util/PlatformUtils.kt#L15-L16
This issue causes our test cases to fail since, in developmentMode, the application is started in a different classloader which leads to some problems with our test setup. We had to override the `io.ktor.development` value to solve it for now.
We believe that this behavior is unintended and a bug. Could you please take a look and provide a better developmentMode resolving method?
Deprecate TestApplicationCall.requestHandled
This property has strange and hard to document behaviour. Users should not rely on it, but check response status/header/content
Use kotlin.reflect.jvm.javaType instead of the type token pattern in io.ktor.util.reflect.typeInfo
Please consider using kotlin.reflect.javaType
instead of the type token-like approach in https://github.com/ktorio/ktor/blob/554b8223c5da0fbd0f3ecdd273e74459b8f009e7/ktor-utils/jvm/src/io/ktor/util/reflect/TypeInfoJvm.kt#L17.
The main benefit of using javaType
is that it doesn't produce an anonymous class for each call site.
Also, we'd like to get some feedback for javaType
before graduating it to stable in a future Kotlin release (we're planning to stabilize typeOf
in 1.6 (KT-45396), javaType
will likely follow in a subsequent release). That said, currently it doesn't support some corner cases which might be critical for Ktor, which I don't know. Those are listed in the kdoc or the link above.
Another aspect of it is that sometimes there are Kotlin compiler bugs that prevent correct reification of type parameters in anonymous objects (e.g. KT-46797), and typeOf
+ javaType
is supposed to be much more stable in this regard. The compiler bugs still need to be fixed, but if this code uses javaType
, at least Ktor users won't suffer from those bugs, like in KT-46797.
Review Auth providers
sendWithoutRequest
is considered harmful- no option to update credentials after installation
Subclass IosHttpRequestException from IoException or HttpRequestException
If you have a common module containing the api for your app, you can't annotate the functions with @Throws(IosHttpRequestException::class)
, because IosHttpRequestException
is defined in the iOS client only and is a subclass from Exception
. Adding @Throws(Exception::class)
is not a good choice, because a possible wrong request builder is catched too (eg invalide urlString).
class API(val client: HttpClient) {
@Throws(IosHttpRequestException::class) // not possible in commonMain
suspend fun users() = client.get<List<User>>("/users")
}
solution
class API(val client: HttpClient) {
@Throws(IOException::class)
suspend fun users() = client.get<List<User>>("/users")
}
Fix testRoutingWithTracing on windows
ConcurrentMap remove does not work on elements with the same hashCodes
Upload progress observer sometimes hangs
Url: mailto protocol URL drops query parameters
The mailto:
URI scheme is standardized in RFC 6068. Such URIs may contain percent-encoded query parameters corresponding to email headers as defined in RFC 5322, such as cc
, bcc
, subject
, and body
.
When encoding a mailto:
Url using toString
, query parameters are ignored, producing only the username and host parts of the URI.
This is important for interoperability with mail clients, which commonly support the additional query parameters by inserting them into a new compose window.
Execution failed for task ':compileKotlinIosArm64'.
Hello I am working on a project in Ktor. All dependencies of ktor and kotlin are updated and the iOS version does not compile. I do not understand or find a reason why this error could be. Please help
PartialContent for video on playstation 4 problem
Good day. I am writing a site for watching videos over a local network. Faced a problem, the video does not play on PlayStation 4. On the server, I get the following error:
TRACE Application - 416 Couldn't satisfy range request bytes=25062288-: it should comply with the restriction [0; 25062288): GET - /static/videos/sasha1.mp4.
Screenshots from the reverse proxy attached in the correct sequence.
Please help to figure out the problem.
PS everything works on the computer, it does not work only on playstation 4.
Random NPEs in CIOEngine
I have random NPEs in selector.coroutineContext[Job]!!.join()
when running my unit tests.
The NPEs aren't logged anywhere. I've only noticed them because I've set a breakpoint for NullPointerException
.
I haven't been able to make it reproducible yet and I cannot share the project.
selector
:
Fix version publishing for Kotlinx.html
Fix `releaseVersion` property
1.5.3
released 5th April 2021
Client
ResponseObserver does not respect MDC context
When logging response from. Ktor-client Logging
feature with the help of ResponseObserver
, the log does not contains the MDC context.
This mean when tracking all logs bound to an MDC context, response content log remains untracked.
This test illustrates the mentioned scenario :
in package [jvmTest]io.ktor.client.features.logging
, in file LoggingTest.kt
@Test
fun loggingWithMDCContextTest(): Unit = runBlocking {
val messages: MutableList<String> = mutableListOf()
val testLogger: Logger = object : Logger by LoggerFactory.getLogger("ktor.test") {
override fun trace(message: String?) = add("TRACE: $message")
override fun debug(message: String?) = add("DEBUG: $message")
override fun info(message: String?) = add("INFO: $message")
private fun add(message: String?) {
if (message != null) {
val mdcText = MDC.getCopyOfContextMap()?.let { mdc ->
if (mdc.isNotEmpty()) {
mdc.entries.sortedBy { it.key }
.joinToString(prefix = " [", postfix = "]") { "${it.key}=${it.value}" }
} else {
""
}
} ?: ""
messages.add(message + mdcText)
}
}
}
val localTestLogger: io.ktor.client.features.logging.Logger = object : io.ktor.client.features.logging.Logger {
override fun log(message: String) {
testLogger.info(message)
}
}
val client: HttpClient = HttpClient {
install(Logging) {
level = LogLevel.ALL
logger = localTestLogger
// responseCoroutineContext = MDCContext() This is to provide CoroutineContext from feature config
}
}
MDC.put("trace_id", "48b49e15-f7c4-4991-9e33-23850a218718")
val response: String = withContext(MDCContext()) {
client.get("https://jsonplaceholder.typicode.com/todos/1")
}
MDC.clear()
assertNotNull(messages)
assertNotNull(response)
}
Theory : First I suspected that this is because of EmptyCoroutineContext
being used in launch
, but when I gave MDCContext()
from kotlinx-coroutines-slf4j
to it, it still didn't work. Now I suspect, it is because function is provided as a predicate. However, I am not too sure about it. Can you help please ?
Change I tested :
scope.launch(feature.coroutineContext) {
try {
feature.responseHandler(sideCall.response)
} catch (_: Throwable) {
}
val content = sideCall.response.content
if (!content.isClosedForRead) {
content.discard()
}
}
where ResponseObserver = public class ResponseObserver(private val responseHandler: ResponseHandler, private val coroutineContext: CoroutineContext = EmptyCoroutineContext)
Elements of messages :
INFO: REQUEST: https://jsonplaceholder.typicode.com/todos/1 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: METHOD: HttpMethod(value=GET) [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: COMMON HEADERS [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> Accept: */* [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> Accept-Charset: UTF-8 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: CONTENT HEADERS [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> Content-Length: 0 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: BODY Content-Type: null [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: BODY START [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: BODY END [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: RESPONSE: 200 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: METHOD: HttpMethod(value=GET) [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: FROM: https://jsonplaceholder.typicode.com/todos/1 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: COMMON HEADERS [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> access-control-allow-credentials: true [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> age: 5692 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> cache-control: max-age=43200 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> cf-cache-status: HIT [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> cf-ray: 63a05ebc7fdf10f3-CPH [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> cf-request-id: 09381989cb000010f38b157000000001 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> content-type: application/json; charset=utf-8 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> date: Sat, 03 Apr 2021 06:51:43 GMT [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> etag: W/"53-hfEnumeNh6YirfjyjaujcOPPT+s" [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> expires: -1 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> nel: {"max_age":604800,"report_to":"cf-nel"} [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> pragma: no-cache [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> report-to: {"group":"cf-nel","endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report?s=39vBUwnOjgmuV0yQJPiunD1hXHE28ku4%2FDq55GSjP%2B66ufTXnkgV1a%2F2H7DHSc28QRc1OiJA6k%2BKKXceS0z%2BaAFzxTU%2FuDpZCeBrgceQ7gvyxUQ45qbyYIa4sg2x"}],"max_age":604800} [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> server: cloudflare [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> set-cookie: __cfduid=dbe3ed123d14f8db7af7d7697a9f4049f1617432703; expires=Mon, 03-May-21 06:51:43 GMT; path=/; domain=.typicode.com; HttpOnly; SameSite=Lax [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> vary: Origin, Accept-Encoding [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> via: 1.1 vegur [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-content-type-options: nosniff [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-powered-by: Express [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-ratelimit-limit: 1000 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-ratelimit-remaining: 999 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-ratelimit-reset: 1616387857 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: BODY Content-Type: application/json; charset=utf-8
INFO: BODY START
INFO: {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
INFO: BODY END
Memory leak in ktor-client-curl
Receiving byte array data by GET request with using ktor-client-curl on native (macosX64) seems to be leaking memory, here's the snippet used to reproduce:
repeat(1000) {
runBlocking {
client.get("http://some-test-image.jpg")
val responseBody: ByteArray = httpResponse.receive()
}
}
Running that code shows constantly increasing memory usage of the process - using quite big images seems to be quickest way to observe it. This seems to be reproducible with 1.5.3, 1.6.0, 1.6.7 as well as with 2.0.0-beta-1 (with introduced API changes into snippet). I actually cherry-picked these versions amongst others and each seems to be affected.
Is there anything I'm assuming wrong here? I would expect heap size to stabilise around some usage instead.
KDoc: HttpRequestBuilder.header actually appends header value, does not set it
/**
* Sets a single header of [key] with a specific [value] if the value is not null.
*/
public fun HttpRequestBuilder.header(key: String, value: Any?): Unit =
value?.let { headers.append(key, it.toString()) } ?: Unit
The KDoc states that the header is set. This word usually signifies that the resulting value will be equal to the parameter, replacing existing value. But since the implementation appends the resulting value might not be what the caller expected.
The KDoc should be more explicit about what the function does. I can imagine even deprecating the current function name in favor of something more descriptive, such as appendHeader
instead of just header
webSocket session is closed due to ping timeout when setting ping value in DefaultWebSocketSession Client.
ext.kotlin_version = "1.3.72"
ext.ktor_version = "1.5.3"
I'd like to check whether server alive or not at client side.
So, I checked this using pingIntervalMillis, timeoutMillis in Client DefaultWebSocketSession.
But it is not work as my intended.
The session is closed due to ping time out as soon as it start. (There must not happen Ping timeout because Server is alive)
Also, it is happened at only setting client side ping value.
If setting server side ping value, the session is not closed.
If setting both side, the result is same with client.
Here is code.
class MainActivity : AppCompatActivity() {
private fun startServer() {
embeddedServer(Netty, port = 8080) {
install(io.ktor.websocket.WebSockets) {
// pingPeriod = Duration.ofSeconds(10)
// timeout = Duration.ofSeconds(5)
}
routing {
webSocket("/language") {
try {
incoming.consumeEach { frame ->
when (frame) {
is Frame.Text -> println("######################### ${frame.readText()}")
}
}
} finally {
println("######################### ${closeReason.await()}")
}
}
}
}.start()
}
private val client = HttpClient(CIO) {
install(io.ktor.client.features.websocket.WebSockets)
}
private fun connectLocalhost() {
CoroutineScope(Dispatchers.IO).launch {
try {
client.ws(host = "localhost", port = 8080, path = "/language") {
pingIntervalMillis = Duration.ofSeconds(10).toMillis()
timeoutMillis = Duration.ofSeconds(5).toMillis()
send(Frame.Text("My name is Hello World"))
try {
incoming.consumeEach { frame ->
when (frame) {
is Frame.Text -> println(frame.readText())
}
}
} finally {
println("------------------------- ${closeReason.await()}")
}
}
} catch (e:Throwable) {
println("------------------------- ${e.message}")
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startServer()
connectLocalhost()
}
}
Here is log
I/Adreno: PFP: 0x005ff112, ME: 0x005ff066
I/ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasWideColorDisplay retrieved: 0
I/ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 0
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OpenGLRenderer: Swap behavior 2
I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy@7cfdec5 time:17937450
I/System.out: ######################### My name is Hello World
I/System.out: ######################### CloseReason(reason=INTERNAL_ERROR, message=Ping timeout)
I/System.out: ------------------------- CloseReason(reason=INTERNAL_ERROR, message=Ping timeout)
MockEngine doest work for Android
After updating to 1.5.2 Android target doest work. But it is ok for iOS
at 1.4.3 is ok for both platforms iOS and Android.
MockEngine {
// this code is not called on request as result mock doesn't work
}
Postpone (and don't cache) name resolution in cio client
*What steps will reproduce the issue?
I think we have a regression of this issue: https://github.com/ktorio/ktor/issues/1358
As we can see the current retry logic continues to use the cached network address: https://github.com/ktorio/ktor/blob/main/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/Endpoint.kt#L183
- Use the CIO engine for Ktor Client
- Connect to an endpoint.
- Turn off Wifi or cause network issue somehow
- Try to connect again and get java.nio.channels.UnresolvedAddressException
5 Turn on Wifi or stop causing network issue - Try to connect again and still get java.nio.channels.UnresolvedAddressException.
What is the expected result?
Once network issues are resolved, the client should be able to connect and not keep getting java.nio.channels.UnresolvedAddressException
Other info*
Stack trace log included.
java.lang.IllegalStateException: No instance for key AttributeKey: ExpectSuccessAttribyteKey
After updating from ktor 1.4.3 to 1.5.2 and running my integration tests using ktor I'm running into:
java.lang.IllegalStateException: No instance for key AttributeKey: ExpectSuccessAttribyteKey
at io.ktor.util.Attributes$DefaultImpls.get(Attributes.kt:29)
at io.ktor.util.AttributesJvmBase.get(AttributesJvm.kt:15)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidation.kt:28)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.features.HttpCallValidator.validateResponse(HttpCallValidator.kt:55)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invokeSuspend(HttpCallValidator.kt:134)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invoke(HttpCallValidator.kt)
at io.ktor.client.features.HttpSend$Feature$install$1.invokeSuspend(HttpSend.kt:98)
at ???(Coroutine boundary.?(?)
at app.becoach.roboter.CoacheeRoboter$login$1.invokeSuspend(CoacheeRoboter.kt:26)
Caused by: java.lang.IllegalStateException: No instance for key AttributeKey: ExpectSuccessAttribyteKey
at io.ktor.util.Attributes$DefaultImpls.get(Attributes.kt:29)
at io.ktor.util.AttributesJvmBase.get(AttributesJvm.kt:15)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidation.kt:28)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.features.HttpCallValidator.validateResponse(HttpCallValidator.kt:55)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invokeSuspend(HttpCallValidator.kt:134)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invoke(HttpCallValidator.kt)
at io.ktor.client.features.HttpSend$Feature$install$1.invokeSuspend(HttpSend.kt:98)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Do you happen to have any idea what's causing this? My unit tests are getting 4xx error codes from the backend. I've got a custom Feature installed where upon unauthorized error codes, I'm re-issuing a new token.
My feature looks like this:
internal class AuthFeature(
private val credentials: String,
private val tokenStorage: TokenStorage,
private val logger: Logger
) {
class Config {
lateinit var credentials: String
lateinit var tokenStorage: TokenStorage
lateinit var logger: Logger
}
companion object Feature : HttpClientFeature<Config, AuthFeature> {
override val key: AttributeKey<AuthFeature> = AttributeKey("AuthFeature")
/**
* The Mutex is required in order to only refresh once when multiple requests
* are being triggered simultaneously e.g. at app startup.
*/
private val mutex = Mutex()
override fun prepare(block: Config.() -> Unit): AuthFeature {
val config = Config().apply(block)
return AuthFeature(
credentials = config.credentials,
tokenStorage = config.tokenStorage,
logger = config.logger,
)
}
@KtorExperimentalAPI
override fun install(feature: AuthFeature, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.State) {
val accessToken = feature.tokenStorage.getAccessToken()
val isRefreshingToken = context.url.buildString().endsWith("oauth/token")
val header = if (accessToken != null && !isRefreshingToken) "Bearer $accessToken" else feature.credentials
context.headers.append(HttpHeaders.Authorization, header)
}
requireNotNull(scope.feature(HttpSend)).intercept { call, _ ->
if (call.response.status == HttpStatusCode.Unauthorized) {
val mutexId = uuid4().toString()
val tag = "${key.name}-$mutexId"
feature.logger.i(tag, "Got ${HttpStatusCode.Unauthorized.value}")
val beforeMutexAccessToken = feature.tokenStorage.getAccessToken()
val request = call.request
mutex.withLock {
val inMutexAccessToken = feature.tokenStorage.getAccessToken()
val needsToRefresh = beforeMutexAccessToken == inMutexAccessToken
if (needsToRefresh) {
feature.tokenStorage.putAccessToken(null) // Invalidate access token.
}
val refreshToken = feature.tokenStorage.getRefreshToken()
if (refreshToken != null) {
if (needsToRefresh) {
feature.logger.i(tag, "Issuing new access token")
val refreshResponse = scope.refreshToken(refreshToken)
feature.logger.i(tag, "Updating access & refresh token")
feature.tokenStorage.putAccessToken(refreshResponse.accessToken)
feature.tokenStorage.putRefreshToken(refreshResponse.refreshToken)
} else {
feature.logger.i(tag, "Other request already refreshed token")
}
// Build previous request once again.
val requestBuilder = HttpRequestBuilder()
requestBuilder.takeFrom(request)
// Force the new token by explicitly setting it.
requestBuilder.headers[HttpHeaders.Authorization] = "Bearer ${feature.tokenStorage.getAccessToken()}"
feature.logger.i(tag, "Retrying previous request with new access token")
execute(requestBuilder).also {
feature.logger.i(tag, "Finished previous request")
}
} else {
feature.logger.i(tag, "No refresh token and hence leaving as is")
call
}
}
} else {
call
}
}
}
}
}
Fix flaky CIOSustainabilityTest.testBlockingConcurrency[jvm]
java.net.SocketException: Connection reset by peer
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:715)
at io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:38)
at io.ktor.network.sockets.SocketImpl$connect$1.invokeSuspend(SocketImpl.kt)
at (Coroutine boundary. ( )
at kotlinx.coroutines.CompletableDeferredImpl.await(CompletableDeferred.kt:86)
Ktor 1.5.2: Can't resolve 'node-fetch' on libs produced by jsBrowserProductionLibraryDistribution
I'm using Ktor client with an MPP project.
After upgrading to 1.5.2, I started having this error on a yarn
project that is using my js module as a javascript dependency:
./node_modules/mp-game-clib/mp-game-clib.js
Module not found: Can't resolve 'node-fetch' in '/Users/brunomed/git/pok-p2p-game-demo/react-app/node_modules/mp-game-clib'
That's the relevant Gradle config for the library project:
js(IR) {
useCommonJs()
browser {
binaries.library()
webpackTask {
enabled = false
}
testTask {
useKarma {
useChromeHeadless()
webpackConfig.cssSupport.enabled = true
}
}
}
}
...
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":client-server"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-websockets:$ktorVersion")
implementation("com.benasher44:uuid:0.2.3")
// https://youtrack.jetbrains.com/issue/KTOR-541
runtimeOnly(npm("text-encoding", "0.7.0"))
runtimeOnly(npm("abort-controller", "3.0.0"))
runtimeOnly(npm("node-fetch", "2.6.1")) <== Had to add this to fix the problem for now, I didn't need it with Ktor 1.5.1
}
}
...
}
Not sure if I'm doing something wrong here, but adding those npm
dependencies is the only way I could find to produce correct package.json
on the jsBrowserProductionLibraryDistribution
goal (previous comment here: https://youtrack.jetbrains.com/issue/KTOR-541#focus=Comments-27-4731438.0-0)
Outdated doc string for FormPart
Documentation for FormPart
says
@param value content, could be [String], [Number] or [Input]
https://github.com/ktorio/ktor/blob/e03bafda3b3d72fcac166e46cf55e5d2d9383660/ktor-client/ktor-client-core/common/src/io/ktor/client/request/forms/formDsl.kt#L17
But later in this file Input
is deprecated
@Suppress("UNUSED_PARAMETER")
@Deprecated(
"Input is not reusable. Please use [InputProvider] instead.",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("appendInput(key, headers) { /* create fresh input here */ }")
)
public fun append(key: String, value: Input, headers: Headers = Headers.Empty) {
error("Input is not reusable. Please use [InputProvider] instead.")
}
And direct usage will cause exception
public fun formData(vararg values: FormPart<*>): List<PartData> {
...
is Input -> error("Can't use [Input] as part of form: $value. Consider using [InputProvider] instead.")
...
}
Core
Remove internal require and switch to Stdlib
Use the stdlib require
function instead of io.ktor.utils.io.core.internal.require
, with has the same signature.
ByteBufferChannel.readRemaining suspend when should not
Reproducer:
suspend fun main() {
val channel = ByteChannel(true)
println(channel.availableForRead) //prints 0
channel.writePacket {
writeText("hello")
}
println(channel.availableForRead) //prints 5
val packet = channel.readRemaining(channel.availableForRead.toLong()) //suspend...
// val packet = channel.readPacket(channel.availableForRead)
println(packet.readText())
}
Channel has 5 bytes available for read.
Contract of availableForRead
says, that:
/**
* Returns number of bytes that can be read without suspension. Read operations do no suspend and return
* immediately when this number is at least the number of bytes requested for read.
*/
public actual val availableForRead: Int
But in readRemaining
it suspends on trying to read...
Using readPacket
it works ok.
Using ByteChannelSequential on JVM don't suspend on both calls. On K/N the same - both don't suspends.
Looks like it regression after KTOR-1268. Caused by this change: https://github.com/ktorio/ktor/pull/2214/files#diff-408be3eaf4a8ec196449c0483c1525a24450590d9473ebcc3a5bc3798326e3e7L2234-R2222
Now channel is trying to read one byte, even if it's not needed.
Reproduced on:
kotlin: 1.4.32
ktor: 1.5.3
K/N: ByteChannelSequential.readPacket freeze
Minimal reproducer:
on K/N:
fun main() = runBlocking {
val channel = ByteChannel(true)
channel.writePacket {
writeText("h")
}
channel.close()
val packet = withTimeoutOrNull(1000) {
// val packet = channel.readRemaining(5L)
channel.readPacket(5)
}
println(packet?.readText())
}
Channel closed, but readPacket freezes forever - even not fail by timeout.
Same behavior is if using ByteChannelSequentialJVM(IoBuffer.Empty, true)
on K/JVM.
If using ByteBufferChannel on JVM - behavior is ok, exception is thrown:
Exception in thread "main" kotlinx.coroutines.channels.ClosedReceiveChannelException: Unexpected EOF: expected 4 more bytes
at io.ktor.utils.io.ByteBufferChannel.readFullySuspend(ByteBufferChannel.kt:598)
at io.ktor.utils.io.ByteBufferChannel.readFully(ByteBufferChannel.kt:590)
at io.ktor.utils.io.ByteBufferChannel.readPacketSuspend(ByteBufferChannel.kt:806)
at io.ktor.utils.io.ByteBufferChannel.readPacket$suspendImpl(ByteBufferChannel.kt:792)
at io.ktor.utils.io.ByteBufferChannel.readPacket(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteReadChannelKt.readPacket(ByteReadChannel.kt:202)
at AppKt$main$1$packet$1.invokeSuspend(App.kt:57)
If to use readRemaining
- for any channel, behavior is the same, it reads packet with one letter and prints it.
Original issue: TCP socket freeze on K/N
Minimal reproducer:
Server (K/JVM):
@OptIn(InternalAPI::class)
suspend fun main() {
val connection = aSocket(SelectorManager()).tcp().bind(port = 8000).accept().connection()
while (true) {
delay(1000)
connection.output.writePacket(buildPacket {
writeText("1".repeat(128))
})
connection.output.flush()
}
}
Client (K/Native, K/JVM - same code):
@OptIn(InternalAPI::class)
fun main() = runBlocking {
val connection = aSocket(SelectorManager()).tcp().connect("0.0.0.0", port = 8000).connection()
while (true) {
val packet = withTimeoutOrNull(5000) {
println("await")
val packet = connection.input.readRemaining(128)
// val packet = connection.input.readPacket(128)
require(packet.remaining == 128L) { "Length should be 128 but was ${packet.remaining}" }
packet
}
println("received: ${packet?.readText()}")
}
}
Here, if to start server, then, start client, on client side there will be output:
await
received: 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
await
received: 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
After several seconds, stop the server.
Client on K/Native fails with:
Uncaught Kotlin exception: kotlin.IllegalArgumentException: Length should be 128 but was 0
It's expected, channel closed, and no bytes to read
Client on K/JVM fails with same exception:
Exception in thread "main" java.lang.IllegalArgumentException: Length should be 128 but was 0
Also expected.
But If to replace readRemaining
with readPacket
and do the same (start server, start client, stop server after several seconds):
Client on K/Native just freezes - prints only await
and don't fail on 2 seconds timeout.
Not expected at all!
Client on K/JVM fail with:
Exception in thread "main" kotlinx.coroutines.channels.ClosedReceiveChannelException: Unexpected EOF: expected 128 more bytes
at io.ktor.utils.io.ByteBufferChannel.readFullySuspend(ByteBufferChannel.kt:598)
at io.ktor.utils.io.ByteBufferChannel$readFullySuspend$1.invokeSuspend(ByteBufferChannel.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:86)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:61)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at AppKt.main(App.kt:24)
at AppKt.main(App.kt)
But this is expected.
BTW, if to reduce timeout to 2 seconds, K/JVM will fail by timeout for readRemaining
case, but K/N will fail with expected exception.
Reproduced on:
kotlin: 1.4.32
ktor: 1.5.3
os: macos
K/N target: macos, both release and debug
How to track leaked buffers in ktor-io?
Prior ktor 1.5.3 it was possible to replace pool of created ByteReadPacket
by using something like this:
inline fun buildPacket(pool: ObjectPool<ChunkBuffer>, block: BytePacketBuilder.() -> Unit): ByteReadPacket {
val builder = BytePacketBuilder(0, pool)
try {
block(builder)
return builder.build()
} catch (t: Throwable) {
builder.release()
throw t
}
}
and then, use such pool
that will intercept calls to default ChunkBuffer.Pool
that is used for underlying storage:
object InUseTrackingPool : ObjectPool<ChunkBuffer> {
override val capacity: Int get() = ChunkBuffer.Pool.capacity
private val inUse = atomic(0)
override fun borrow(): ChunkBuffer {
inUse.incrementAndGet()
return ChunkBuffer.Pool.borrow()
}
override fun recycle(instance: ChunkBuffer) {
inUse.decrementAndGet()
ChunkBuffer.Pool.recycle(instance)
}
override fun dispose() {
ChunkBuffer.Pool.dispose()
}
fun resetInUse() {
inUse.lazySet(0)
}
fun assertNoInUse() {
val v = inUse.value
assertEquals(0, v, "Buffers in use")
}
}
But after fix for KTOR-960, now ChunkBuffer
s are linked to parentPool
in which they were created, and so, borrow
interception works, but recycle
will be never called, as ChunkBuffer.Pool.recycle
will be called directly.
Even now it's possible to do something like this but using internal
APIs like here: https://github.com/rsocket/rsocket-kotlin/blob/06ce789c6aeaf499f1dd43cdcf1c78e1b3825881/rsocket-test/src/commonMain/kotlin/io/rsocket/kotlin/test/InUseTrackingPool.kt - very very hacky.
The question is: Is it possible to have ability to track leaked buffers in another way? Or can you provide some easier (non-internal) way to create own Buffer Pools, or some other API
I/O readRemaining sometimes looses exception
The function channel.readRemaining
sometimes returns a packet (possibly the empty one) when the channel is closed abnormally.
Upgrade kotlinx.serialization to 1.1.0
Upgrade to coroutines 1.4.3
Expose TrailingSlashRouteSelector
Hey,
I think there is an unnecessary restriction on the RouteSelector
that limits TrailingSlashRouteSelector
as an internal object. I know that RouteSelection is an internal API, but it is very useful for route analysis, for instance I am starting to build out an OpenAPI spec generator, and would like to construct a route from the final route declaration, so something like this
@OptIn(InternalAPI::class)
fun Route.calculatePath(tail: String = ""): String = when (selector) {
is RootRouteSelector -> tail
is PathSegmentParameterRouteSelector -> parent?.calculatePath("/$selector$tail") ?: "/{$selector}$tail"
is PathSegmentConstantRouteSelector -> parent?.calculatePath("/$selector$tail") ?: "/$selector$tail"
else -> error("unknown selector type $selector")
}
However, I am running into a limitation placed seemingly only on TrailingSlashRouteSelector, which is declared internal object
. Are there any concerns around making this publicly available?
If so, what would be a proposed alternative for building out route patterns at runtime?
Docs
Documentation for GraalVM
Requests topic miss the import statement for call.receiveText
A user gets the unresolved reference "call.receiveText"
error when trying to use examples from https://ktor.io/docs/requests.html
Validate tutorial on Creating HTTP APIs with Ktor
There are reports that the tutorial located at https://play.kotlinlang.org/hands-on/Creating HTTP APIs with Ktor/02_application-init does not work well with the latest version of the Ktor plugin. We recently ported this tutorial over to our docs. Please make sure it is all correct.
Wrong redirect from old documentation page
Right now the page https://ktor.io/clients/http-client/features/auth.html is redirected to https://ktor.io/docs/http-client-features.html but should be redirected to https://ktor.io/docs/features-auth.html instead.
404 page on https://ktor.io/clients/http-client.html#websockets
The first link from the comment https://github.com/ktorio/ktor/issues/507#issuecomment-410173950 gives a 404 page.
Doc Code Snippets contain out of data repositories
Most contain bintray and jcenter repositories. They need to be removed.
Wrong indentation in `Serving Static Content` guide
Some snippets have 4 spaces and some 2
https://ktor.io/docs/serving-static-content.html#serving-individual-files
Generator
Wizard: Remove kotlinx-html Space Repository from Gradle Template
Including Kotlinx HTML DSL as the templating language results in the following line in the configuration:
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") }
Since kotlinx-html-jvm:0.7.3 is already published on mavenCentral, there is no need for this line anymore going forward, and it can be removed. (Relates to KTOR-2203, which can be closed, I suppose.)
Could not find artifact org.jetbrains.kotlinx:kotlinx-html-jvm:pom:0.7.2
To reproduce create a project with all features selected and the the following properties:
- Build system: Maven
- Ktor version 1.4.0
- Engine: Tomcat
- Configuration: HOCON file
After project generation, sync fails with the following error:
Could not find artifact org.jetbrains.kotlinx:kotlinx-html-jvm:pom:0.7.2 in kotlinx-html (https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven)
Also, build fails with an unresolved reference error:
/home/stexe/projects/ktor_plugin/ktor-sample7/src/main/kotlin/com/example/plugins/Sockets.kt:11:24
Kotlin: Unresolved reference: tls
No templates/thymeleaf directory in new project
IntelliJ IDEA 2021.1 with Ktor plugin 1.5.3
- New Project - Ktor
- Add Feature - Thymeleaf
- Open Templating.kt file
Actual result: templates/thymeleaf/
is mentioned in code but there is no such directory and index
file in project
fun Application.configureTemplating() {
install(Thymeleaf) {
setTemplateResolver(ClassLoaderTemplateResolver().apply {
prefix = "templates/thymeleaf/"
suffix = ".html"
characterEncoding = "utf-8"
})
}
routing {
get("/html-thymeleaf") {
call.respond(ThymeleafContent("index", mapOf("user" to ThymeleafUser(1, "user1"))))
}
}
}
"IllegalArgumentException: Neither port nor sslPort specified" when running via Gradle Application plugin
To reproduce create a new project via the IDEA plugin without any feature and run generated application using Gradle:
./gradlew run
As a result the app crashes with the following error:
> Task :run FAILED
2021-04-08 16:20:57.086 [main] TRACE Application - No configuration provided: neither application.conf nor system properties nor command line options (-config or -P:ktor...=) provided
Exception in thread "main" java.lang.IllegalArgumentException: Neither port nor sslPort specified. Use command line options -port/-sslPort or configure connectors in application.conf
at io.ktor.server.engine.CommandLineKt$commandLineEnvironment$environment$1.invoke(CommandLine.kt:142)
at io.ktor.server.engine.CommandLineKt$commandLineEnvironment$environment$1.invoke(CommandLine.kt)
at io.ktor.server.engine.ApplicationEngineEnvironmentBuilder.build(ApplicationEngineEnvironment.kt:110)
at io.ktor.server.engine.ApplicationEngineEnvironmentKt.applicationEngineEnvironment(ApplicationEngineEnvironment.kt:46)
at io.ktor.server.engine.CommandLineKt.commandLineEnvironment(CommandLine.kt:61)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:21)
Rename build system options in generator
Should be: Gradle Kotlin, Gradle Groovy, Maven
Provide default route in generated application
The wizard generates a new app that compiles and runs, but when clicking on the default route as indicated by the output, it doesn't respond. I think we should provide a default route not only as means of folks knowing that it works, but also as a starting point.
Wizard generates obsolete repositories
Wizard is still referencing Bintray and jcenter which aren't required.
repositories {
mavenLocal()
jcenter()
maven { url = uri("https://kotlin.bintray.com/ktor") }
}
A project generated with Maven using a new plugin contains deprecated API in 'pom.xml'
To reproduce the issue, create a new Ktor project with the Maven build system. The pom.xml
file will contain the following code for the exec-maven-plugin
plugin:
<configuration>
<mainClass>io.ktor.server.netty.DevelopmentEngine</mainClass>
</configuration>
But DevelopmentEngine
is deprecated.
Infrastructure
Build: Remove different darwin/iOS handling on ideaActive
I want to build Ktor locally for iOS.
If I open Ktor using IntelliJ and click on Gradle
publishToMavenLocal
, the iOS sources won't be created, because the special handling in darwin.gradle
if (project.ext.ideaActive) {
fromPreset(project.ext.ideaPreset, 'darwin')
} else {
fromPreset(presets.iosArm64, 'iosArm64')
fromPreset(presets.iosArm32, 'iosArm32')
fromPreset(presets.iosX64, 'iosX64')
...
To build the iOS sources, you need to exit IntelliJ and run ./gradlew publishToMavenLocal
in your terminal. Otherwise you will get a No matching variant of io.ktor:ktor-client-ios:1.6.0-SNAPSHOT was found.
error in the consuming project.
Depending on the IDE to create different artifacts of the same Gradle task is very confusing and takes some time to understand...
Autoreload not working with 1.5.x when using embeddedServer NOT in debug mode
The 1.5.x update broke the auto-reload feature.
I made an example repo here, when switching from 1.4.x to 1.5.x the auto-reload break (not working anymore).
You can reproduce by running ./gradlew run
and ./gradlew build -t
and switch versions.
IntelliJ IDEA Plugin
IDE plugin: SOE through KtorUrlReferenceContributor$Companion.getParentUrlPaths
User feedback:
After installing Intellij IDEA 2021.1 (on Linux Mint 20.1 with all updates installed) I was having a lot of problems working in a simple Ktor/Gradle project, newly created. I could build in gradle and run the application but in the IDE the editor was showing errors where there were none. Go to definition and other code navigation features weren't working. I got an error saying "Kotlin not enabled" but the plugin was installed and enabled. Eventually the IDE would lock up completely and I'd have to kill the process manually.
I tried uninstalling and reinstalling, removing all folders in places like .local where files were stored before reinstalling. I tried installing using JB toolbox and then removing that as well and installing from a .tar.gz file.
Finally I just removed it all and installed 2020.3, and it's working, but doesn't have some of the new features like Code With Me that I wanted to try.
IDEA 2021.1.1 Preview Build #IU-211.7142.13
Kotlin (211-1.4.32-release-IJ7142.3)
Ktor version: 1.5.3
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - IntelliJ IDEA 2021.1.1 Preview Build #IU-211.7142.13
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - JDK: 11.0.10; VM: Dynamic Code Evolution 64-Bit Server VM; Vendor: JetBrains s.r.o.
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - OS: Linux
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - Plugin to blame: Ktor version: 1.5.3
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - Last Action: About
2021-04-22 09:09:59,895 [ 34465] ERROR - aemon.impl.PassExecutorService - null
java.lang.StackOverflowError
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processGlobalRequests(PsiSearchHelperImpl.java:837)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processGlobalRequestsOptimized(PsiSearchHelperImpl.java:830)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processRequests(PsiSearchHelperImpl.java:755)
at com.intellij.psi.search.SearchRequestQuery.processResults(SearchRequestQuery.java:24)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.delegateProcessResults(AbstractQuery.java:108)
at com.intellij.util.MergeQuery.processResults(MergeQuery.java:22)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.delegateProcessResults(AbstractQuery.java:108)
at com.intellij.util.UniqueResultsQuery.processResults(UniqueResultsQuery.java:39)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.forEach(AbstractQuery.java:83)
at com.intellij.util.AbstractQuery.findAll(AbstractQuery.java:28)
at com.intellij.util.Query.iterator(Query.java:129)
at com.intellij.util.AbstractQuery.iterator(AbstractQuery.java:39)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths(KtorUrlReferenceContributor.kt:370)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths$default(KtorUrlReferenceContributor.kt:120)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths(KtorUrlReferenceContributor.kt:191)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths$default(KtorUrlReferenceContributor.kt:120)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.calculateUrlPathContext(KtorUrlReferenceContributor.kt:85)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1$injector$1.invoke(KtorUrlReferenceContributor.kt:232)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1$injector$1.invoke(KtorUrlReferenceContributor.kt:22)
at com.intellij.microservices.url.references.UrlPathReferenceInjector$buildReferences$1.rootContext(UrlPathReferenceInjector.kt:128)
at com.intellij.microservices.url.references.UrlPathReferenceInjector$buildReferences$1.forPsiElement(UrlPathReferenceInjector.kt:136)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1.invoke(KtorUrlReferenceContributor.kt:238)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1.invoke(KtorUrlReferenceContributor.kt:22)
at com.intellij.psi.UastReferenceRegistrar$uastReferenceProvider$1.getReferencesByElement(UastReferenceRegistrar.kt:54)
at com.intellij.psi.UastReferenceProviderAdapter$getReferencesByElement$1.invoke(UastReferenceProviderAdapter.kt:13)
at com.intellij.psi.UastReferenceProviderAdapter$getReferencesByElement$1.invoke(UastReferenceProviderAdapter.kt:8)
at com.intellij.openapi.progress.impl.CancellationCheck.withCancellationCheck(CancellationCheck.kt:59)
at com.intellij.openapi.progress.impl.CancellationCheck$Companion.runWithCancellationCheck(CancellationCheck.kt:105)
at com.intellij.psi.UastReferenceProviderAdapter.getReferencesByElement(UastReferenceProviderAdapter.kt:13)
at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl.getReferences(ReferenceProvidersRegistryImpl.java:201)
at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl.mapNotEmptyReferencesFromProviders(ReferenceProvidersRegistryImpl.java:164)
at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl.doGetReferencesFromProviders(ReferenceProvidersRegistryImpl.java:143)
at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry.getReferencesFromProviders(ReferenceProvidersRegistry.java:43)
at com.intellij.psi.PsiReferenceServiceImpl.doGetReferences(PsiReferenceServiceImpl.java:34)
at com.intellij.psi.PsiReferenceServiceImpl.getReferences(PsiReferenceServiceImpl.java:26)
at com.intellij.psi.search.SingleTargetRequestResultProcessor.processTextOccurrence(SingleTargetRequestResultProcessor.java:32)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$5.lambda$execute$0(PsiSearchHelperImpl.java:942)
at com.intellij.psi.impl.search.LowLevelSearchUtil.processTreeUp(LowLevelSearchUtil.java:88)
at com.intellij.psi.impl.search.LowLevelSearchUtil.lambda$processElementsAtOffsets$0(LowLevelSearchUtil.java:179)
at com.intellij.psi.impl.search.LowLevelSearchUtil.processOffsets(LowLevelSearchUtil.java:205)
at com.intellij.psi.impl.search.LowLevelSearchUtil.processElementsAtOffsets(LowLevelSearchUtil.java:178)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$5.execute(PsiSearchHelperImpl.java:938)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$2.processInReadAction(PsiSearchHelperImpl.java:272)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$2.processInReadAction(PsiSearchHelperImpl.java:263)
at com.intellij.openapi.application.ReadActionProcessor.lambda$process$0(ReadActionProcessor.java:25)
at com.intellij.openapi.application.impl.ApplicationImpl.runReadAction(ApplicationImpl.java:844)
at com.intellij.openapi.application.ReadAction.compute(ReadAction.java:61)
at com.intellij.openapi.application.ReadActionProcessor.process(ReadActionProcessor.java:25)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processCandidates$17(PsiSearchHelperImpl.java:899)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processVirtualFile$10(PsiSearchHelperImpl.java:535)
at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1091)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processVirtualFile(PsiSearchHelperImpl.java:515)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processPsiFileRoots$7(PsiSearchHelperImpl.java:392)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processFilesConcurrentlyDespiteWriteActions$8(PsiSearchHelperImpl.java:465)
at com.intellij.openapi.application.impl.ReadMostlyRWLock.executeByImpatientReader(ReadMostlyRWLock.java:167)
at com.intellij.openapi.application.impl.ApplicationImpl.executeByImpatientReader(ApplicationImpl.java:178)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processFilesConcurrentlyDespiteWriteActions$9(PsiSearchHelperImpl.java:464)
at com.intellij.concurrency.JobLauncherImpl.lambda$processImmediatelyIfTooFew$2(JobLauncherImpl.java:142)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:688)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:634)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:64)
at com.intellij.concurrency.JobLauncherImpl.lambda$processImmediatelyIfTooFew$3(JobLauncherImpl.java:138)
at com.intellij.openapi.application.impl.ApplicationImpl.runReadAction(ApplicationImpl.java:814)
at com.intellij.concurrency.JobLauncherImpl.processImmediatelyIfTooFew(JobLauncherImpl.java:149)
at com.intellij.concurrency.JobLauncherImpl.invokeConcurrentlyUnderProgress(JobLauncherImpl.java:45)
at com.intellij.concurrency.JobLauncher.invokeConcurrentlyUnderProgress(JobLauncher.java:49)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processFilesConcurrentlyDespiteWriteActions(PsiSearchHelperImpl.java:481)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processPsiFileRoots(PsiSearchHelperImpl.java:389)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processCandidates(PsiSearchHelperImpl.java:894)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processGlobalRequests(PsiSearchHelperImpl.java:864)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processGlobalRequestsOptimized(PsiSearchHelperImpl.java:830)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processRequests(PsiSearchHelperImpl.java:755)
at com.intellij.psi.search.SearchRequestQuery.processResults(SearchRequestQuery.java:24)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.delegateProcessResults(AbstractQuery.java:108)
at com.intellij.util.MergeQuery.processResults(MergeQuery.java:22)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.delegateProcessResults(AbstractQuery.java:108)
at com.intellij.util.UniqueResultsQuery.processResults(UniqueResultsQuery.java:39)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.forEach(AbstractQuery.java:83)
at com.intellij.util.AbstractQuery.findAll(AbstractQuery.java:28)
at com.intellij.util.Query.iterator(Query.java:129)
at com.intellij.util.AbstractQuery.iterator(AbstractQuery.java:39)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths(KtorUrlReferenceContributor.kt:370)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths$default(KtorUrlReferenceContributor.kt:120)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths(KtorUrlReferenceContributor.kt:191)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths$default(KtorUrlReferenceContributor.kt:120)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.calculateUrlPathContext(KtorUrlReferenceContributor.kt:85)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1$injector$1.invoke(KtorUrlReferenceContributor.kt:232)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1$injector$1.invoke(KtorUrlReferenceContributor.kt:22)
at com.intellij.microservices.url.references.UrlPathReferenceInjector$buildReferences$1.rootContext(UrlPathReferenceInjector.kt:128)
at com.intellij.microservices.url.references.UrlPathReferenceInjector$buildReferences$1.forPsiElement(UrlPathReferenceInjector.kt:136)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1.invoke(KtorUrlReferenceContributor.kt:238)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1.invoke(KtorUrlReferenceContributor.kt:22)
at com.intellij.psi.UastReferenceRegistrar$uastReferenceProvider$1.getReferencesByElement(UastReferenceRegistrar.kt:54)
[...]
Prioritize text found in feature titles over descriptions
For example, when I'm trying to find the HTML DSL
feature, the wizard shows template engines first.
Wizard: Creating a project without sample code creates `Application.configureRouting` without `routing`
The code generated:
fun Application.configureRouting() {
}
Even without sample code, it's probably preferable to at least generate the following stub:
fun Application.configureRouting() {
routing {
}
}
Wrong parameter passed to embeddedServer on generated wizard with CIO option
When selecting the CIO option in the Project Wizard, it uses "Netty" as parameter to the embeddedServer function as opposed to CIO.
Link to GitHub for Features is incorrect
If we're linking to the specific code for a feature on GitHub (which should probably be indicated in the metadata for the feature and only linked if present), it should link to the specific feature. Currently all the ones we ship out of the box point to generic https://github.com/ktorio/ktor
Also, please note the text should say
"Source on GitHub" instead of "See features's GitHub"
Wizard: Creating Maven project with Kotlinx.Serialization results in Could not find artifact org.jetbrains.kotlin:kotlin-maven-serialization:pom
Could not find artifact org.jetbrains.kotlin:kotlin-maven-serialization:pom:${kotlin.version} in central (https://repo.maven.apache.org/maven2)
I assume this is the offending block:
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-serialization</artifactId>
<version>${kotlin.version}</version>
</dependency>
Wizard: WebSocket feature adds redundant qualifier name
Remove README From generated projects
With a barebones Ktor project as generated by the Wizard, the stubbed-out code snippets in the README.md cause IntelliJ to believe we're committing very faulty software when importing our project into Git (and, likely, other VCS). IJ shows 50+ errors (not warnings).
This looks very scary, and makes it feel like something is broken with the setup even when there isn't. Please either annotate the README with some kind of flag to let IntelliJ know not to check all the code fences, or otherwise exclude the README file from code analysis.
Tested on:
IntelliJ IDEA 2021.1 Beta (Ultimate Edition)
Build #IU-211.6305.21, built on March 4, 2021
IntelliJ IDEA EAP User
Expiration date: April 4, 2021
Runtime version: 11.0.10+9-b1341.18 x86_64
VM: Dynamic Code Evolution 64-Bit Server VM by JetBrains s.r.o.
macOS 10.14.6
GC: G1 Young Generation, G1 Old Generation
Memory: 4096M
Cores: 8
Registry: ide.images.show.chessboard=true
Non-Bundled Plugins: com.jetbrains.darkPurpleTheme (1.2), org.nik.presentation-assistant (1.0.9), wu.seal.tool.jsontokotlin (3.6.1), org.asciidoctor.intellij.asciidoc (0.32.26), stardust (1.5.2105), com.jetbrains.intellij.api.watcher (6.46.0), intellij.ktor (1.5.1-eap-1)
Kotlin: 211-1.4.21-release-IJ6305.1
Wizard: "Static Content" feature generates invalid index.html
The feature generates the following code in resources/static/index.html
:
<html>
<head>
</head>
<body>
<h1>Hello Ktor!<h1>
</body>
</html>
As IntelliJ IDEA notes correctly, the h1
tag is not being closed properly. Should be </h1>
.
Wizard: Gradle Groovy project with Kotlinx Serialization uses inconsistent quotes
plugins {
id 'application'
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
id "org.jetbrains.kotlin.plugin.serialization" version "1.4.10" // <== these should probably also be single quotes – no need to switch.
}
Wizard: Unused import directive in build.gradle.kts
import org.jetbrains.kotlin.gradle.dsl.Coroutines
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
Unable to run new Ktor project
Environment details:
IntelliJ IDEA 2021.1 (Ultimate Edition)
Build #IU-211.6693.111, built on April 6, 2021
Runtime version: 11.0.10+9-b1341.35 aarch64
VM: Dynamic Code Evolution 64-Bit Server VM by JetBrains s.r.o.
macOS 11.2.3
GC: G1 Young Generation, G1 Old Generation
Memory: 2048M
Cores: 8
Non-Bundled Plugins: mobi.hsz.idea.gitignore (4.1.0), com.github.beansoft.reatnative.idea.free (2020.2), intellij.ktor (1.5.3), Dart (211.7179), io.flutter (55.1.5), com.jetbrains.php (211.6693.120), org.jetbrains.plugins.go (211.6693.111)
Kotlin: 211-1.4.32-release-IJ6693.72
KTOR - 1.5.3
Created new project from KTor wizard.
Added
- Authentication JWT - this is adding automatically "Routing" & "Authentication"
- Authentication OAuth - this is adding automatically "Locations"
- Status Pages
- Compression
- CORS
- CallLogging
- Metrics
- Kotlinx.serialization - this is adding automatically "ContentNegotiation"
- RawSockets
Result :
At compilation stage :
Exception in thread "main" io.ktor.application.DuplicateApplicationFeatureException: Conflicting application feature is already installed with the same key as "Locations"
Removing :
install(Locations) {
}
from Routing.kt
creates error :
Exception in thread "main" io.ktor.application.MissingApplicationFeatureException: Application feature Locations is not installed
Wizard: Misleading comment in Static Feature
The sample code says:
// Static feature. Try to access `/static/ktor_logo.svg`
...but no ktor_logo.svg is included.
Ktor plugin memory leak in New Project wizard
IJ 212 Nightly
- Open New Project wizard
- Select Ktor
- Close New Project wizard
2021-04-30 10:44:27,515 [ 181985] ERROR - tellij.openapi.util.ObjectTree - Memory leak detected: 'io.ktor.initializr.intellij.features.KtorFeaturesStep' of class io.ktor.initializr.intellij.features.KtorFeaturesStep is registered in Disposer but wasn't disposed.
Register it with a proper parentDisposable or ensure that it's always disposed by direct Disposer.dispose call.
See https://jetbrains.org/intellij/sdk/docs/basics/disposers.html for more details.
The corresponding Disposer.register() stacktrace is shown as the cause:
java.lang.RuntimeException: Memory leak detected: 'io.ktor.initializr.intellij.features.KtorFeaturesStep' of class io.ktor.initializr.intellij.features.KtorFeaturesStep is registered in Disposer but wasn't disposed.
Register it with a proper parentDisposable or ensure that it's always disposed by direct Disposer.dispose call.
See https://jetbrains.org/intellij/sdk/docs/basics/disposers.html for more details.
The corresponding Disposer.register() stacktrace is shown as the cause:
at com.intellij.openapi.util.ObjectTree.assertIsEmpty(ObjectTree.java:227)
at com.intellij.openapi.util.Disposer.assertIsEmpty(Disposer.java:167)
at com.intellij.openapi.util.Disposer.assertIsEmpty(Disposer.java:162)
at com.intellij.openapi.application.impl.ApplicationImpl.disposeContainer(ApplicationImpl.java:246)
at com.intellij.openapi.application.impl.ApplicationImpl.disposeSelf(ApplicationImpl.java:263)
at com.intellij.openapi.application.impl.ApplicationImpl.doExit(ApplicationImpl.java:650)
at com.intellij.openapi.application.impl.ApplicationImpl.exit(ApplicationImpl.java:603)
at com.intellij.openapi.application.impl.ApplicationImpl.exit(ApplicationImpl.java:592)
at com.intellij.openapi.application.ex.ApplicationEx.exit(ApplicationEx.java:73)
at com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame$3.windowClosing(WelcomeFrame.java:117)
at java.desktop/java.awt.AWTEventMulticaster.windowClosing(AWTEventMulticaster.java:357)
at java.desktop/java.awt.AWTEventMulticaster.windowClosing(AWTEventMulticaster.java:357)
at java.desktop/java.awt.Window.processWindowEvent(Window.java:2090)
at java.desktop/javax.swing.JFrame.processWindowEvent(JFrame.java:298)
at java.desktop/java.awt.Window.processEvent(Window.java:2049)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5027)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2784)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4859)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:778)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:751)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:749)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:748)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:896)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:768)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:434)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:825)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$9(IdeEventQueue.java:433)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:842)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:487)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Caused by: java.lang.Throwable
at com.intellij.openapi.util.ObjectNode.<init>(ObjectNode.java:31)
at com.intellij.openapi.util.ObjectTree.createNodeFor(ObjectTree.java:99)
at com.intellij.openapi.util.ObjectTree.register(ObjectTree.java:60)
at com.intellij.openapi.util.Disposer.register(Disposer.java:73)
at com.intellij.util.Alarm.<init>(Alarm.java:130)
at com.intellij.openapi.ui.LoadingDecorator.<init>(LoadingDecorator.java:54)
at com.intellij.openapi.ui.LoadingDecorator.<init>(LoadingDecorator.java:47)
at com.intellij.openapi.ui.LoadingDecorator.<init>(LoadingDecorator.java:43)
at com.intellij.ui.components.JBLoadingPanel.lambda$new$0(JBLoadingPanel.java:29)
at com.intellij.ui.components.JBLoadingPanel.<init>(JBLoadingPanel.java:38)
at com.intellij.ui.components.JBLoadingPanel.<init>(JBLoadingPanel.java:29)
at io.ktor.initializr.intellij.features.KtorFeaturesStep.<init>(KtorFeaturesStep.kt:33)
at io.ktor.initializr.intellij.KtorModuleBuilder.createWizardSteps(KtorModuleBuilder.kt:63)
at com.intellij.ide.util.newProjectWizard.StepSequence.addStepsForBuilder(StepSequence.java:52)
at com.intellij.ide.projectWizard.ProjectTypeStep.<init>(ProjectTypeStep.java:234)
at com.intellij.ide.projectWizard.NewProjectWizard.init(NewProjectWizard.java:57)
at com.intellij.ide.projectWizard.NewProjectWizard.<init>(NewProjectWizard.java:46)
at com.intellij.ide.actions.NewProjectAction.actionPerformed(NewProjectAction.java:20)
at com.intellij.openapi.actionSystem.ex.ActionUtil.performAction(ActionUtil.java:247)
at com.intellij.openapi.actionSystem.ex.ActionUtil.performActionDumbAware(ActionUtil.java:236)
at com.intellij.openapi.actionSystem.ex.ActionUtil.performActionDumbAwareWithCallbacks(ActionUtil.java:229)
at com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenActionsUtil.performAnActionForComponent(WelcomeScreenActionsUtil.java:96)
at com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenActionsUtil$ToolbarTextButtonWrapper$1.actionPerformed(WelcomeScreenActionsUtil.java:56)
at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1967)
at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2308)
at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:270)
at java.desktop/java.awt.Component.processMouseEvent(Component.java:6652)
at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3345)
at java.desktop/java.awt.Component.processEvent(Component.java:6417)
at java.desktop/java.awt.Container.processEvent(Container.java:2263)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5027)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4859)
at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4918)
at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4547)
at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4488)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2307)
at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2784)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4859)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:778)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:751)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:749)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:748)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:896)
at com.intellij.ide.IdeEventQueue.dispatchMouseEvent(IdeEventQueue.java:828)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:765)
... 11 more
Empty get/post routes with non-empty context are not visible in Endpoints VIew
Example:
route("path1/path2") {
get {}
post {}
}
path1/path2
will not be visible in Endpoints View while it must be there twice: for POST and for GET methods.
Wizard: Basic run configuration is not always created
If I create a project with ContentNegotiation, Gson, and Routing, I don't get a run configuration automatically configured. I need to press the green gutter play icon, or run the run
Gradle task.
Adding a bunch more features seems to sometimes include a default "ApplicationKt" run configuration.
Wizard: Selecting MAVEN adds multiple Bintray references to pom.xml
For some reason it also add numerous entries
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
Reference-style documentation links are not rendered in a plugin
- Create a new Ktor project.
- Go to the Features page and find the Micrometer Metrics feature.
- Find the
[monitoring the JVM][micrometer_jvm_metrics]
text => should be a link
Documentation links don't work in a plugin
- Create a new Ktor project.
- Go to the Features page and find the Micrometer Metrics feature.
- Click any link => doesn't work
New Ktor plugin saves scroll position in feature description
When I scroll feature documentation to some point and then click to another feature the scroll position remains the same. This requires me to scroll to the top to start reading another feature's documentation.
Application structure browsing, routing navigator and edit for Ktor plugin
As a Ktor backend developer I want to have an IDE interface with endpoints list, generated from my routing configuration:
Possible features of endpoints map:
- go to the related code;
- generate a new route wizard;
- open in browser;
- generate dummy request with applicable params (according to data class) for IDEA http-client;
- generate test from request/response;
- show pipelines and interceptors chain;
- show execution timings
Server
DropwizardMetrics does not append baseName to the 'per endpoint'-metrics breaking-change
When setting basename
install(DropwizardMetrics) {
registry = metricRegistry
baseName = "my.prefix"
}
metrics for endpoints does not get prefixed with the baseName I set.
When GET-ing /some/path/
I get metrics /some/path/(method:GET)
but I expect my.prefix./some/path/(method:GET)
I am using ktor 1.5.2
SessionTrackerById - doesn't remove invalid session id
In the load function the session id key is put into call attributes.
In line 88 it is put into attributes again with the comment "remove".
This might be wrong, it should remove the session id, if I understand the mechanics correctly.
Simple API for writing features
Main goals
- Make API easy to understand for users:
a) Get rid of concept of pipelines and phases
b) Make API compact (reduce number of options available, i.e. consider smaller number of "phases")
c) Rework naming to make it intuitive
d) Think about defaults and yet support common case - API must be compatible with old existing features
- All usages of old features rewritten with new API must remain unchanged yet working. This means all
import {...}
blocks should still work for features rewritten to the new API as well as with the old features. - Choosing "phase" must become intuitive and easy
- Old API functionality should remain available for experienced users.
- API should fit into Kotlin best patterns and concepts (DSL is a good option here)
Onboarding (first steps)
Old API
Let's take a look at some small feature that was defined with the old Features API. This example was taken from our official documentation:
class CustomFeature(configuration: Configuration) {
val prop = configuration.prop // Copies a snapshot of the mutable config into an immutable property.
class Configuration {
var prop = "value" // Mutable property.
}
// Implements ApplicationFeature as a companion object.
companion object Feature : ApplicationFeature<ApplicationCallPipeline, CustomFeature.Configuration, CustomFeature> {
// Creates a unique key for the feature.
override val key = AttributeKey<CustomFeature>("CustomFeature")
// Code to execute when installing the feature.
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): CustomFeature {
// It is responsibility of the install code to call the `configure` method with the mutable configuration.
val configuration = CustomFeature.Configuration().apply(configure)
// Create the feature, providing the mutable configuration so the feature reads it keeping an immutable copy of the properties.
val feature = CustomFeature(configuration)
// Intercept a pipeline.
pipeline.intercept(…) {
// Perform things in that interception point.
}
return feature
}
}
}
Old Problems:
- This code does not look welcoming to newcomers at all.
- It is not easy to understand.
- It contains a lot of concepts to deal with that are not hidden by the API:
ApplicationFeature<ApplicationCallPipeline, CustomFeature.Configuration, CustomFeature>
,AttributeKey<CustomFeature>
,ApplicationCallPipeline
,pipeline.intercept
- It is not clear where is the feature itself and where is it's configuration: there is a
CustomFeature
that has some properties, and there is also aConfiguration
class that defines configuration, and there is also aFeature
object. It is not 100% obvious how each of them is used and such model confuses unexperienced readers because there is no clear separation between these concepts.
Proposed solution:
- Instead of creating 2 nested classes and an object, extending some weird class
ApplicationFeature<ApplicationCallPipeline, CustomFeature.Configuration, CustomFeature>
now it is sufficient to just create an instance ofKtorFeature
usingcreateFeature { ... }
DSL method, and to define a configuration class for it separately:
class Configuration {
// Properties and configuration functions go here:
...
}
val CustomFeature = createFeature(name = "CustomFeature", createConfiguration = ::Configuration) {
// What feature does goes here:
...
}
This feature can now be used as it was before, with just calling install and configuring it in your main application code:
install(CustomFeature) {
// set mutable config properties
}
Benefits
- Now, users will see much more clear separation between the data and actions, and they will not have to deal with confusing methods and base classes to extend.
- It is fully compatible with the old code that uses features
Extending Ktor HTTP behaviour
Old API
In the old API users had to deal with a concept of phases and pipelines. Below there is a list of them:
ApplicationCallPipeline
Setup
: Phase for preparing call and it's attributes for processingMonitoring
: Phase for preparing call and it's attributes for processingFeatures
: Phase for features. Most features should intercept this phase.Call
: Phase for processing a call and sending a responseFallback
: Phase for handling unprocessed calls
ApplicationSendPipeline
Before
: The earliest phase that happens before any otherTransform
: Transformation phase that can proceed with any supported data like StringRender
: Phase to render any current pipeline subject into [io.ktor.http.content.OutgoingContent], beyond this phase only [io.ktor.http.content.OutgoingContent] should be produced by any interceptorContentEncoding
: Phase for processing Content-Encoding, like compression and partial contentTransferEncoding
: Phase for handling Transfer-Encoding, like if chunked encoding is being done manually and not by engineAfter
: The latest application phase that happens right before engine will send the responseEngine
: Phase for Engine to send the response out to client.
ApplicationReceivePipeline
Before
: Executes before any transformations are madeTransform
: Executes transformationsAfter
: Executes after all transformations
Note, that in the list above phases are ordered!
Also, each feature had an ability to create it's custom phase in any of the 3 pipelines, and insert this phase anywhere in the order of existing phases. In order to use some phase ("intercept a pipeline in a specific phase") a feature was meant to call pipeline.intercept
method on a specific phase.
Here is an example of usage of the old API taken from the MicrometerMetrics
implementation:
override fun install(pipeline: Application, configure: Configuration.() -> Unit): MicrometerMetrics {
...
val phase = PipelinePhase("MicrometerMetrics")
pipeline.insertPhaseBefore(ApplicationCallPipeline.Monitoring, phase)
pipeline.intercept(phase) {
// some actions go here
}
val postSendPhase = PipelinePhase("MicrometerMetricsPostSend")
pipeline.sendPipeline.insertPhaseAfter(ApplicationSendPipeline.After, postSendPhase)
pipeline.sendPipeline.intercept(ApplicationSendPipeline.After) {
// some actions go here
}
Old problems:
- There too many phases and pipelines (3 pipelines with 15 phases total)
- Not obvious which phase to select,
- No defaults for some simple usage (onboarding)
- With user-defined phases and insertBefore/insertAfter it builds a complex mental model of the global order of phases and requires a deep knowledge about phases each feature uses in order to think about the order of execution.
- Concept of phases and pipelines is not intuitive and does not mentally map to the concept of Routing in Ktor. It is not obvious when each phase is executed and what are the pipelines.
Proposed solution:
First of all, there should be simple defaults that would extend basic functionalities that users see in Ktor.
There will be 3 "default" methods that are mapped by name with what call inside a routing block each of them extend:
onCall
: extends what happens when a server is processing any HTTP method callonReceive
: extends behaviour of the server when user writescall.receive()
in aroute { ... }
blockonRespond
: extends behaviour of the server when user writescall.respond()
(call.respondText()
, etc.) in aroute { ... }
block
In code they should be placed inside createFeature
block:
val F = createFeature(...) {
onCall {
...
}
onReceive {
...
}
onRespond {
...
}
}
Such defaults are useful for newbies because they are meant to provide the most commonly used functionality without a requirement to know a lot about other options.
Also, for some more advanced use cases there will be more advanced builders with more options to extend:
-
extendCallHandling { ... }
-- a block that configures what happens when HTTP method call handlingmonitoring { ... }
-- defines actions to perform before the call was processed by any feature (including Routing). It is useful for monitoring and logging (see CallLogging feature) to be executed before any "real stuff" was performed with the call because other features can change it's content as well as add more resource usage etc. while for logging and monitoring it is important to observe the pure (untouched) data.onCall { ... }
-- same asonCall
outside ofextendCallHandling
. Most used extension. It defines what to do with the call that is being currently processed by a server.fallback
-- defines what to do with the call in case request was not handled for some reason. Usually, it is handy to usefallback { call -> ...}
to set the response with some message or status code, or to throw an exception.
-
extendReceiveHandling
supports following callbacks:beforeReceive
— defines actions to perform before any transformations were made to the received content. It is useful for caching.onReceive
— defines how to transform data received from a client.
-
extendResponseHandling
supports following callbacks:beforeResponse
— allows to use the direct result of call processing (seeonCall
)onResponse
— does transformations of the data. Example: you can write a custom serializer wit h it.afterResponse
— allows to calculate some stats on the data that was sent, or to handle errors. It is used in Metrics, CachingHeaders, StatusPages.
Example:
val F = createFeature(...) {
onCall {
// prepare some data
}
extendResponseHandling {
beforeResponse {
// use this data
}
onResponse {
// transform data to a string (i.e. serialize data)
}
}
}
Benefits:
- Now we provide fast-to-access defaults that newbies can use (now it is obvious what to select from: not sure -- use a default)
- There are much less options to select from, and these options are mapped to what users do when they define handling of routes
- Api doesn't introduce any new complex or uncommon concepts (like phases or pipelines, or interceptors)
Managing order of execution between features
Old API
In the old API there was a concept of phases that one could introduce and then insert anywhere with insertPhaseBefore
/ insertPhaseAfter
, and then intercept these phases. One of the most common things it was used for was creating order dependencies between features. This idea is explained here: https://www.ximedes.com/2020-09-17/role-based-authorization-in-ktor/. This was useful to either prepare some data that can be used later by another feature, or to use some data already produced by some other feature.
Old Problems:
- It required using 3 concepts to do 1 thing: use "phase", "insertPhase" and "intercept" in order to "execute one feature before/after another"
- It required explicitly knowing which phases another feature was using.
Proposed solution:
Each feature now internally remembers which phases in which pipelines it intercepts (of course, without revealing this complex and confusing information to users). And createFeature
block also provides following functions inside:
beforeFeature(other) { ... }
afterFeature(other) { ... }
Example:
val F = createFeature(...) {
...
}
val G = createFeature(...) {
beforeFeature(F) {
onCall {
// prepare some data for feature F
}
}
}
Benefits:
Now the API just does what it says: it is simply executing one feature before/after another without introducing any complex entities and without a requirement to manually manage the internals.
Manually managing features execution:
Old API
In the old API it was possible to manually manage execution of the current pipeline. Inside any interceptor it was possible to call the following functions of PipelineContext<, >
:
public interface PipelineContext<TSubject : Any, TContext : Any> : CoroutineScope {
/**
* Object representing context in which pipeline executes
*/
public val context: TContext
/**
* Subject of this pipeline execution that goes along the pipeline
*/
public val subject: TSubject
/**
* Finishes current pipeline execution
*/
public fun finish()
/**
* Continues execution of the pipeline with the given subject
*/
public suspend fun proceedWith(subject: TSubject): TSubject
/**
* Continues execution of the pipeline with the same subject
*/
public suspend fun proceed(): TSubject
}
Also, there were some extension methods over this class:
application
: Current application for the contextcall
: Current call for the contexttransformDefaultContent
: Default outgoing content transformation
Although it introduced really complex concept of subject
to understand, it can be required for some advanced users and we can't get rid of it completely. One of the most popular patterns that use PipelineContext
is the following (taken from VelocityFeature
):
pipeline.sendPipeline.intercept(ApplicationSendPipeline.Transform) { value ->
if (value is VelocityContent) {
val response = feature.process(value)
proceedWith(response)
}
}
It actually allows to write a custom data serializer for a specific type (VelocityContent
in this example). proceedWith(outgoingContent)
allows to continue the execution of our HTTP pipeline with the data serialized to a resulting outgoingContent
.
Old problems
- Really complex and hard to understand but yet useful
- Some members/extensions are not that popular or not required for the API (
application
is not commonly used,context
makes no sense to newbies) proceedWith
andsuject
are useful but are mostly used for serialization features- serialization use case requires knowing about
OutgoingContent
type that is not mapped to any general knowledge, and in fact there is always a default serializer fromString
,ByteChanel
andByteArray
types to theOutgoingContent
.
Proposed solution:
- For the case of serialization we will introduce following simple API that users may default to:
public fun <T : Any> KtorFeature<T>.serializeDataToString(callback: suspend SendExecution.(Any) -> String?): Unit =
onSend { callback(subject)?.let { proceedWith(it) } }
public fun <T : Any> KtorFeature<T>.serializeDataToBytes(callback: suspend SendExecution.(Any) -> ByteArray?): Unit =
onSend { callback(subject)?.let { proceedWith(it) } }
public fun <T : Any> KtorFeature<T>.serializeDataToChanel(callback: suspend SendExecution.(Any) -> ByteWriteChannel?): Unit =
onSend { callback(subject)?.let { proceedWith(it) } }
This API can be used as following:
private class Formula(val x: String, val execute: (String) -> String)
private val F = createFeature("F", {}) {
serializeDataToString { f ->
// return null for irrelevant data types
if (f !is Formula) return@serializeDataToString null
// Do a transformation for the current data type
f.execute(f.x)
}
}
private fun Application.f() {
routing {
get("/") {
call.respond(Formula("cake") { x -> "x * x + 1".replace("x", x) })
}
}
}
Also, for any more complex use cases we will introduce the following wrapper over PipelineContext<SubjectT, ApplicationCall>
. It is meant to hide unneeded methods and extensions without limiting functionality too much:
public inline class Execution<SubjectT : Any>(private val context: PipelineContext<SubjectT, ApplicationCall>) {
public suspend fun proceed(): SubjectT = context.proceed()
public suspend fun proceedWith(subectT: SubjectT): SubjectT = context.proceedWith(subectT)
public fun finish(): Unit = context.finish()
public val subject: SubjectT get() = context.subject
public val call: ApplicationCall get() = context.call
...
update cc @leonid.stashevsky Also following methods can be added to handle application-level properties from a feature:
// Useful methods
public val environment: ApplicationEnvironment get() = context.application.environment
public val configuration: ApplicationConfig get() = environment.config
public val port: Int get() = configuration.propertyOrNull("ktor.deployment.port")?.getString()?.toInt() ?: 8080
public val host: String get() = configuration.propertyOrNull("ktor.deployment.host")?.getString() ?: "0.0.0.0"
// Following calback can be used to close resources that a feature has allocated (if needed):
public fun onShutdown(callback: suspend () -> Unit) {
GlobalScope.launch(context.coroutineContext) {
callback()
}
}
}
Also there are type aliases for executions of the different stages:
public typealias CallExecution = Execution<Unit>
public typealias ReceiveExecution = Execution<ApplicationReceiveRequest>
public typealias SendExecution = Execution<Any>
Each of the methods has a corresponding Execution
type as a reciever for a callback, for example:
public override fun onCall(callback: suspend CallExecution.(ApplicationCall) -> Unit): Unit { ... }
This means, that inside a feature definition advanced users who know what they do may use proceed
, finish
etc. methods anywhere in the code:
val F = createFeature(...) {
extendRespondHandling {
beforeRespond {
...
if (someCondition)
finish()
...
}
}
}
update cc @leonid.stashevsky Another option for an API
There are following minor issues with the proposed API:
- Naming is quite long for advanced options (
extendCallHandling
as opposed toonCall
), although it is made so intentionally to hide advanced settings from newbies by a longer name. - It sometimes requires writing
this
in Intellij in order to call a completion to learn what's next.
Benefits
- Now it is easy to write serializers
- Now advanced users can still use what they want
- No complex classes are in your face when you write simple code, so nothing will confuse newbies.
Alternative
We can introduce following interfaces (usage will be described later):
public interface OnCall {
public operator fun invoke(callback: suspend CallExecution.(ApplicationCall) -> Unit): Unit
public fun monitoring(callback: suspend CallExecution.(ApplicationCall) -> Unit): Unit
public fun fallback(callback: suspend CallExecution.(ApplicationCall) -> Unit): Unit
}
public interface OnReceive {
public operator fun invoke(callback: suspend ReceiveExecution.(ApplicationCall) -> Unit): Unit
public fun before(callback: suspend ReceiveExecution.(ApplicationCall) -> Unit): Unit
}
public interface OnResponse {
public operator fun invoke(callback: suspend ResponseExecution.(ApplicationCall) -> Unit): Unit
public fun before(callback: suspend ResponseExecution.(ApplicationCall) -> Unit): Unit
public fun after(callback: suspend ResponseExecution.(ApplicationCall) -> Unit): Unit
}
And change FeatureContext
(a supertype of KtorFeature
) to the following:
public interface FeatureContext {
public val onCall: OnCall
public val onReceive: OnReceive
public val onResponse: OnResponse
}
After implementing FeatureContext
it will allow us to write the code in the next way:
val F = makeFeature("F", {}) {
onCall { ... }
onCall.fallback { ... }
onCall.monitoring { ...}
beforeFeature(G) {
onCall { ... }
onReceive.before { ... }
onResponse.after { ... }
}
...
So, in general, following API transformations have been applied:
onCall { ... }
->onCall { ... }
extendCallExecution { fallback { ... } }
->onCall.fallback { ... }
extendCallExecution { monitoring { ... } }
->onCall.monitoring { ... }
onRespond { ... }
->onRespond { ... }
extendRespondExecution { before { ... } }
->onRespond.before { ... }
extendRespondExecution { after { ... } }
->onRespond.after { ... }
onReceive { ... }
->onReceive { ... }
extendReceiveExecution { before { ... } }
->onReceive.before { ... }
Benefits
- Much shorter and readable names
- Advanced functionality is still hidden (now it is hidden by the dot
.
) - Completion perfectly works when one types a dot after an object name to see the options
- Simple things are kept the same because
onCall
,onReceive
andonSend
are executable objects now
Disadvantages
- Not conventional way of writing API, may be uncommon to write dots after kinda-function names
update cc @leonid.stashevsky Compatibility with features written in old API
New API provides an ability for features to interact/depend on each other with beforeFeature
and afterFeature
.
Inside it automatically creates everything that is needed to guarantee the order between features.
But this change requires old features to imlement new interface InterceptionsHolder
in order to make it working with the old features. We can easily do that for builtin features:
/**
* Compatibility class. Interception class for Call phase
* */
public typealias CallInterception = Interception<Unit>
/**
* Compatibility class. Interception class for Receive phase
* */
public typealias ReceiveInterception = Interception<ApplicationReceiveRequest>
/**
* Compatibility class. Interception class for Send phase
* */
public typealias ResponseInterception = Interception<Any>
/**
* Compatibility class. Every feature that needs to be compatible with `beforeFeature(...)` and `afterFeature(...)`
* needs to implement this class. It defines a list of interceptions (see [Interception]) for phases in different pipelines
* that are being intercepted by the current feature.
*
* It is needed in order to get first/last phase in each pipeline for the current feature and create a new phase
* that is strictly earlier/later in this pipeline than any interception of the current feature.
*
* Note: any [KtorFeature] instance automatically fills these fields and there is no need to implement them by hand.
* But if you want to use old feature API based on pipelines and phases and to make it available for `beforeFeature`/
* `afterFeature` methods of the new features API, please consider filling the phases your feature defines by implementing
* this interface [InterceptionsHolder].
* */
public interface InterceptionsHolder {
public val name: String get() = this.javaClass.simpleName
public val fallbackInterceptions: MutableList<CallInterception>
public val callInterceptions: MutableList<CallInterception>
public val monitoringInterceptions: MutableList<CallInterception>
public val beforeReceiveInterceptions: MutableList<ReceiveInterception>
public val onReceiveInterceptions: MutableList<ReceiveInterception>
public val beforeResponseInterceptions: MutableList<ResponseInterception>
public val onResponseInterceptions: MutableList<ResponseInterception>
public val afterResponseInterceptions: MutableList<ResponseInterception>
public fun newPhase(): PipelinePhase = PipelinePhase("${name}Phase${Random.nextInt()}")
}
Question: should we make it internal and hide
pros:
- It reveals old API abstactions and is really complex
- It has a lot of fields to implement and is confusing
- We can implement it in our builtin features and this will cover almost all the cases of the features interaction. It is very rare when someone wants to insert their feature before/after some other third-party feature while it is much more common to insert one relatively to some builtin feature. For the rest of the cases, we can suggest using new API that is easier and does all the work automatically.
cons:
- (Didn't actually find any use-case) probably we'll force old users to rewrite their features with the new API to make them available to the new features.
CORS
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1656
Ktor Version and Engine Used (client or server and name)
1.3.0
Describe the bug
CORS can't pass on some none standard orgin like chrome-extension://mfgdmpfihlmdekaclngibpjhdebndhdj
To Reproduce
open some new page in chrome or other browser,just call any api,CORS throw would error(brower side).
Expected behavior
should be AnyOrigin
pass by CORS, when we config it as host("*")
or anyHost()
Can't override Kotlin module configuration using jackson dsl function
Hi team,
I'm using as Jackson content negotiation feature and can't override Kotlin module configuration when installing using jackson
function.
Here's my code:
install(ContentNegotiation) {
jackson {
registerModule(kotlinModule { nullIsSameAsDefault(true) }) // doesn't work ❌
registerModule(KotlinModule(nullIsSameAsDefault = true)) // also doesn't work ❌
}
}
When I trace to ktor-jackson source code, I found this code:
fun jacksonObjectMapper(): ObjectMapper = jsonMapper { addModule(kotlinModule()) }
Look like Kotlin module already added and there is no way to reconfigure it (?). I can't get any information in issues and documentation page.
My temporary workaround,
install(ContentNegotiation) {
val mapper = jsonMapper { addModule(KotlinModule(nullIsSameAsDefault = true)) } // work great ✔
val jackson = JacksonConverter(mapper)
register(ContentType.Application.Json, jackson)
}
Thank you!
Unexpected exception when using Session feature: "Using blocking primitives on this dispatcher is not allowed"
kotlin: 1.4.20
kotlinx.coroutines: 1.4.2
Sessions feature is configured in a rather standard manner:
install(Sessions) {
cookie<UserIdPrincipal>(
Cookies.SESSION_ID,
storage = directorySessionStorage(sessionStorageRootDir)
) {
cookie.path = "/"
cookie.httpOnly = true
cookie.extensions["SameSite"] = "Lax"
}
}
When it is called during a pipeline execution, an unexpected exception is thrown.
java.lang.IllegalStateException: Using blocking primitives on this dispatcher is not allowed. Consider using async channel instead or use blocking primitives in withContext(Dispatchers.IO) instead.
at io.ktor.utils.io.jvm.javaio.BlockingKt.ensureParkingAllowed(Blocking.kt:302) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.access$ensureParkingAllowed(Blocking.kt:1) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.InputAdapter.<init>(Blocking.kt:30) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream(Blocking.kt:20) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream$default(Blocking.kt:20) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1$1.invokeSuspend(CacheStorage.kt:15) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1$1.invoke(CacheStorage.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.DirectoryStorage.read(DirectoryStorage.kt:46) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1.invokeSuspend(CacheStorage.kt:15) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1.invoke(CacheStorage.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache$container$1.invokeSuspend(Cache.kt:82) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache$container$1.invoke(Cache.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.BaseCache$getOrCompute$2$1.invokeSuspend(Cache.kt:38) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
(Coroutine boundary) ~[?:?]
at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:101) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at io.ktor.sessions.ReferenceCache.getOrCompute$suspendImpl(Cache.kt:86) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage.read(CacheStorage.kt:20) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.SessionTrackerById.load(SessionTrackerById.kt:34) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.SessionsKt.receiveSessionData(Sessions.kt:215) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.Sessions$Feature$install$1.invokeSuspend(Sessions.kt:259) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:110) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:102) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:101) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:142) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.CallLogging$Feature$install$2.invokeSuspend(CallLogging.kt:139) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:118) ~[ktor-server-host-common-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:118) ~[ktor-server-netty-jvm-1.4.3.jar:1.4.3]
Caused by: java.lang.IllegalStateException: Using blocking primitives on this dispatcher is not allowed. Consider using async channel instead or use blocking primitives in withContext(Dispatchers.IO) instead.
at io.ktor.utils.io.jvm.javaio.BlockingKt.ensureParkingAllowed(Blocking.kt:302) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.access$ensureParkingAllowed(Blocking.kt:1) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.InputAdapter.<init>(Blocking.kt:30) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream(Blocking.kt:20) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream$default(Blocking.kt:20) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1$1.invokeSuspend(CacheStorage.kt:15) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1$1.invoke(CacheStorage.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.DirectoryStorage.read(DirectoryStorage.kt:46) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1.invokeSuspend(CacheStorage.kt:15) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1.invoke(CacheStorage.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache$container$1.invokeSuspend(Cache.kt:82) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache$container$1.invoke(Cache.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.BaseCache$getOrCompute$2$1.invokeSuspend(Cache.kt:38) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[kotlin-stdlib-1.4.20.jar:1.4.20-release-308 (1.4.20)]
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:342) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:27) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:91) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt.async(Unknown Source) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:84) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt.async$default(Unknown Source) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at io.ktor.sessions.BaseCache$getOrCompute$2.apply(Cache.kt:37) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.BaseCache$getOrCompute$2.apply(Cache.kt:31) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660) ~[?:1.8.0_251]
at io.ktor.sessions.BaseCache.getOrCompute(Cache.kt:36) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache.getOrCompute$suspendImpl(Cache.kt:86) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache.getOrCompute(Cache.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.BaseTimeoutCache.getOrCompute(Cache.kt:163) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage.read(CacheStorage.kt:20) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.SessionTrackerById.load(SessionTrackerById.kt:34) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.SessionsKt.receiveSessionData(Sessions.kt:215) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.Sessions$Feature$install$1.invokeSuspend(Sessions.kt:63) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.Sessions$Feature$install$1.invoke(Sessions.kt) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:110) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:102) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$interceptCall$2.invoke(StatusPages.kt) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:194) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:101) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:142) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$Feature$install$2.invoke(StatusPages.kt) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.CallLogging$Feature$install$2.invokeSuspend(CallLogging.kt:139) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.CallLogging$Feature$install$2.invoke(CallLogging.kt) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:118) [ktor-server-host-common-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt) [ktor-server-host-common-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:118) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:55) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:111) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt.launch(Unknown Source) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:43) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:34) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [netty-transport-4.1.54.Final.jar:4.1.54.Final]
at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:61) [netty-transport-4.1.54.Final.jar:4.1.54.Final]
at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:370) [netty-transport-4.1.54.Final.jar:4.1.54.Final]
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500) [netty-transport-4.1.54.Final.jar:4.1.54.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at io.ktor.server.netty.EventLoopGroupProxy$Companion$create$factory$1$1.run(NettyApplicationEngine.kt:216) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_251]
NettyApplicationEngine: providing a configureBootstrap in the configuration throws IllegalStateException: group set already
Code snippet:
val server = embeddedServer(Netty, env) {
configureBootstrap = {
group(NioEventLoopGroup(16, Executors.newCachedThreadPool()))
}
}
During NettyApplicationEngine initialization we have:
private val bootstraps = environment.connectors.map { connector ->
ServerBootstrap().apply {
configuration.configureBootstrap(this)
group(connectionEventGroup, workerEventGroup)
The call to group(...) tries to initialize the parent and child EventLoopGroup
s for a second time, which throws the exception above.
Routing: Add PutTyped and PatchTyped Overload
Actually there exists a overloaded post
function in Routing.kt
, which allows you to write:
routing {
post<Foo> { foo ->
}
}
This overload is only available for POST
, but not for PATCH
or PUT
too.
Added these two overloads in PR:
ContentConverter for list, map or other generic types
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/831
Ktor Version
1.0.1
Ktor Engine Used(client or server and name)
Jetty Server
JVM Version, Operating System and Relevant Context
1.8.0 Update 191, macOS 10.14.2
Feedback
I use post<List<ConfigCorepage>>("/api/pageName/update")
to accept the json content from user request, but ktor couldn't recognize the type List<ConfigCorepage>
, it use List
instead.
I debug the code, and find that the code here shouldn't use T::class
.
Jackson use a TypeReference
class for preserving class info, ktor should use the same tricks.
How to config nginx [solved]
Enviroment:
os: MacOs Cantalina 10.15.6
ide:IntelliJ IDEA 2021.1 (Ultimate Edition)
Build #IU-211.6693.111, built on April 6, 2021
Runtime version: 11.0.10+9-b1341.35 x86_64
VM: Dynamic Code Evolution 64-Bit Server VM by JetBrains s.r.o.
macOS 10.15.6
GC: G1 Young Generation, G1 Old Generation
Memory: 1024M
Cores: 4
Non-Bundled Plugins: com.poratu.idea.plugins.tomcat (3.8.3), wu.seal.tool.jsontokotlin (3.6.1), intellij.ktor (1.5.3), Dart (211.6693.108), org.intellij.scala (2021.1.16)
Kotlin: 211-1.4.32-release-IJ6693.72
I created a sample project by Ktor plugin( as described in https://ktor.io/docs/intellij-idea.html )
then run application,these message displayed in the terminal window:
2021-04-11 09:31:59.454 [main] INFO ktor.application - Autoreload is disabled because the development mode is off.
2021-04-11 09:31:59.555 [main] INFO ktor.application - Responding at http://0.0.0.0:8080
but the http://0.0.0.0:8080 didn't display "Hello world.“
solution:
kill nginx process,then restart the application.
Test Infrastructure
ktor request coroutines no longer resolve synchronously in tests on JVM after 1.5.1
In our project we have API clients with Reaktive interface (using Single, Completable) that use ktor, and hence coroutines under the hood.
In tests, we use Reaktive's test helpers so that our tests look like this:
apiClient.doStuff()
.test()
.assertCompleted()
// other checks follow, like checking calls on dependencies' mocks etc.
After trying to bump ktor from 1.4.* to 1.5.3 (we later identified that the 1.5.1 is enough to trigger the issue), these tests started failing but only on JVM. Adding a sleep(1000)
call between .test()
and .assertCompleted()
makes the test green again, another potential workaround is using Reaktive's blockingGet
instead, but ideally we'd like to keep such tests sync.
Under the hood, our tests rely on MockEngine
from ktor. Dispatchers.Unconfined
is used for everything in test setup.
Other
Implementation for Simple API for writing features
developmentMode is on by default in tests
Hi,
First of all, thank you for creating and maintaining ktor, we enjoy using it a lot! The issue we notice that after v1.5.2, developmentMode value is set as true by default in tests (more precisely it is on when assertions are enabled) -> https://github.com/ktorio/ktor/blob/de742d1adfbbb994bedda198e6739c724e62a974/ktor-utils/jvm/src/io/ktor/util/PlatformUtils.kt#L15-L16
This issue causes our test cases to fail since, in developmentMode, the application is started in a different classloader which leads to some problems with our test setup. We had to override the `io.ktor.development` value to solve it for now.
We believe that this behavior is unintended and a bug. Could you please take a look and provide a better developmentMode resolving method?
Implementation for Events Feature For Client Metrics
Fix 2.0.0 branch compilation
In 2.0.0 branch IOBuffer
was removed, but main
branch has new tests that use it.
Subclass IosHttpRequestException from IoException or HttpRequestException
If you have a common module containing the api for your app, you can't annotate the functions with @Throws(IosHttpRequestException::class)
, because IosHttpRequestException
is defined in the iOS client only and is a subclass from Exception
. Adding @Throws(Exception::class)
is not a good choice, because a possible wrong request builder is catched too (eg invalide urlString).
class API(val client: HttpClient) {
@Throws(IosHttpRequestException::class) // not possible in commonMain
suspend fun users() = client.get<List<User>>("/users")
}
solution
class API(val client: HttpClient) {
@Throws(IOException::class)
suspend fun users() = client.get<List<User>>("/users")
}
Fix testRoutingWithTracing on windows
ConcurrentMap remove does not work on elements with the same hashCodes
InsufficientSpaceException trying to build ByteReadPacket
Periodically (not every time) during running tests in https://github.com/rsocket/rsocket-kotlin using ktor-io exception appeared trying to build ByteReadPacket using buildPacket {}
Exception in thread "DefaultDispatcher-worker-9 @coroutine#122840" io.ktor.utils.io.core.InsufficientSpaceException: Not enough free space to write long integer of 8 bytes, available 7 bytes.
at io.ktor.utils.io.core.BufferPrimitivesKt.writeLong(BufferPrimitives.kt:1081)
at io.ktor.utils.io.core.OutputPrimitivesKt.writeLongFallback(OutputPrimitives.kt:50)
at io.ktor.utils.io.core.OutputPrimitivesKt.writeLong(OutputPrimitives.kt:45)
at io.rsocket.kotlin.frame.KeepAliveFrame.writeSelf(KeepAliveFrame.kt:33)
at io.rsocket.kotlin.frame.Frame.toPacket(Frame.kt:36)
at io.rsocket.kotlin.internal.RSocketState$start$3.invokeSuspend(RSocketState.kt:143)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.selects.SelectBuilderImpl.resumeWith(Select.kt:306)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Invalid assertion for existence of the key in the key store
CommandLine.kt
requireNotNull(getKey(sslKeyAlias, sslPrivateKeyPassword.toCharArray()) == null)
is always successful
Incorrect grammar in exception messages
Multiple instance of "has been" in exception messages such as
Request timeout has been expired
Fix flaky JavaEngineTests.testThreadLeak[jvm]
Fix flaky ExceptionsJvmTest.testConnectionClosedDuringRequest[jvm]
Fix flaky JettyStressTest.highLoadStressTest
Cannot manage elasticsearch apm agent to work
Hi,
my ktor service cannot start with -javaagent elasticsearch apm.
i ran with this :
java -javaagent:./resources/elastic-apm-agent-1.21.0.jar \
-Delastic.apm.service_name=service-xxx \
-Delastic.apm.server_urls=http://xxx \
-Delastic.apm.application_packages=com.package.xxx \
-jar ./build/libs/xxx.jar
and got this error :
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/build/libs/xxx.dirty.jar!/co/elastic/apm/agent/shaded/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/resources/elastic-apm-agent-1.21.0.jar!/co/elastic/apm/agent/shaded/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [co.elastic.apm.agent.shaded.apache.logging.slf4j.Log4jLoggerFactory]
2021-04-21 15:35:53,748 [main] INFO co.elastic.apm.agent.util.JmxUtils - Found JVM-specific OperatingSystemMXBean interface: com.sun.management.OperatingSystemMXBean
2021-04-21 15:35:53,819 [main] INFO co.elastic.apm.agent.configuration.StartupInfo - Starting Elastic APM 1.21.0 as service-customer on Java 11.0.10 Runtime version: 11.0.10+9-Ubuntu-0ubuntu1.20.04 VM version: 11.0.10+9-Ubuntu-0ubuntu1.20.04 (Ubuntu) Linux 5.4.0-67-generic
2021-04-21 15:35:53,819 [main] INFO co.elastic.apm.agent.configuration.StartupInfo - VM Arguments: [-javaagent:./resources/elastic-apm-agent-1.21.0.jar, -Delastic.apm.service_name=service-xxx, -Delastic.apm.server_urls=http://xxx, -Delastic.apm.application_packages=com.package.xxx]
2021-04-21 15:35:55,473 [main] INFO co.elastic.apm.agent.impl.ElasticApmTracer - Tracer switched to RUNNING state
2021-04-21 15:35:55,591 [elastic-apm-server-healthcheck] INFO co.elastic.apm.agent.report.ApmServerHealthChecker - Elastic APM server is available: { "build_date": "2021-01-12T21:51:32Z", "build_sha": "42a349a4ec9d2dd16e08b8af125647294e7a7e4b", "version": "7.10.2"}
2021-04-21 15:35:55,683 [elastic-apm-remote-config-poller] INFO co.elastic.apm.agent.configuration.ApmServerConfigurationSource - Received new configuration from APM Server: {}
Exception in thread "main" java.lang.IllegalStateException: ApplicationEngineEnviron…t::class.java.classLoader must not be null
at io.ktor.server.engine.ApplicationEngineEnvironmentBuilder.<init>(ApplicationEngineEnvironment.kt:65)
at io.ktor.server.engine.ApplicationEngineEnvironmentKt.applicationEngineEnvironment(ApplicationEngineEnvironment.kt:44)
at io.ktor.server.engine.CommandLineKt.commandLineEnvironment(CommandLine.kt:57)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:21)
2021-04-21 15:35:55,906 [elastic-apm-init-instrumentation-shutdown-hook] INFO co.elastic.apm.agent.impl.ElasticApmTracer - Tracer switched to STOPPED state
im looking the way to solve from this error, with no luck
what should i do?
thanks
Can not read the request body
I wrote it like this:
//Application.kt
post("/login") {
val user = call.receive<User>()
if(database.sequenceOf(Users).any {Users.user_name eq "abcdefg"}) {
database
.from(Users)
.select(Users.user_name, Users.user_pass)
.where {(Users.user_pass eq "114514") and (Users.user_name eq "abcdefg")}
.forEach { row ->
call.respond(mapOf("type" to Type.SUCCESS, "token" to Auth.sign("abcdefg")))
}
} else {
call.respond(mapOf("message" to "User or password wrong", "type" to Type.WRONG_PASSWORD_OR_NAME))
}
}
//User.kt
data class User(
val user: String,
val pass: String
)
But it had a empty response body, I don't know how to solve that.(I think there aren't any problems in the sql query part)
these were the issue
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Exception in thread "main" java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:589)
at java.base/sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:839)
at io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:37)
at io.ktor.network.sockets.SocketImpl$connect$1.invokeSuspend(SocketImpl.kt)
(Coroutine boundary)
at kotlinx.coroutines.CompletableDeferredImpl.await(CompletableDeferred.kt:87)
at io.ktor.client.engine.cio.CIOEngine.execute(CIOEngine.kt:80)
at io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:86)
(Coroutine creation stacktrace)
at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:122)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
at io.ktor.client.engine.HttpClientEngine$DefaultImpls.executeWithinCallContext(HttpClientEngine.kt:81)
at io.ktor.client.engine.HttpClientEngine$install$1.invokeSuspend(HttpClientEngine.kt:66)
at io.ktor.client.engine.HttpClientEngine$install$1.invoke(HttpClientEngine.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.client.features.HttpSend$DefaultSender.execute(HttpSend.kt:128)
at io.ktor.client.features.HttpSend$Feature$install$1.invokeSuspend(HttpSend.kt:92)
at io.ktor.client.features.HttpSend$Feature$install$1.invoke(HttpSend.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.proceedWith(SuspendFunctionGun.kt:123)
at io.ktor.client.features.websocket.WebSockets$Feature$install$1.invokeSuspend(WebSockets.kt:60)
at io.ktor.client.features.websocket.WebSockets$Feature$install$1.invoke(WebSockets.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.proceedWith(SuspendFunctionGun.kt:123)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:86)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invoke(HttpCallValidator.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invokeSuspend(HttpRequestLifecycle.kt:37)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invoke(HttpRequestLifecycle.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.client.HttpClient.execute(HttpClient.kt:188)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at io.ktor.client.features.websocket.BuildersKt.webSocket(builders.kt:209)
at io.ktor.client.features.websocket.BuildersKt.webSocket(builders.kt:79)
at io.ktor.client.features.websocket.BuildersKt.webSocket$default(builders.kt:77)
at com.jetbrains.handson.chat.client.ChatClientKt$main$1.invokeSuspend(ChatClient.kt:16)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.jetbrains.handson.chat.client.ChatClientKt.main(ChatClient.kt:15)
at com.jetbrains.handson.chat.client.ChatClientKt.main(ChatClient.kt)
at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:99)
at io.ktor.client.features.HttpSend$DefaultSender.execute(HttpSend.kt:129)
at io.ktor.client.features.websocket.WebSockets$Feature$install$1.invokeSuspend(WebSockets.kt:60)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:86)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invokeSuspend(HttpRequestLifecycle.kt:37)
at io.ktor.client.HttpClient.execute(HttpClient.kt:188)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at io.ktor.client.features.websocket.BuildersKt.webSocket(builders.kt:209)
at com.jetbrains.handson.chat.client.ChatClientKt$main$1.invokeSuspend(ChatClient.kt:16)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:86)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invokeSuspend(HttpRequestLifecycle.kt:37)
at io.ktor.client.HttpClient.execute(HttpClient.kt:188)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at io.ktor.client.features.websocket.BuildersKt.webSocket(builders.kt:209)
at com.jetbrains.handson.chat.client.ChatClientKt$main$1.invokeSuspend(ChatClient.kt:16)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invokeSuspend(HttpRequestLifecycle.kt:37)
at io.ktor.client.HttpClient.execute(HttpClient.kt:188)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at io.ktor.client.features.websocket.BuildersKt.webSocket(builders.kt:209)
at com.jetbrains.handson.chat.client.ChatClientKt$main$1.invokeSuspend(ChatClient.kt:16)
at io.ktor.client.HttpClient.execute(HttpClient.kt:188)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at io.ktor.client.features.websocket.BuildersKt.webSocket(builders.kt:209)
at com.jetbrains.handson.chat.client.ChatClientKt$main$1.invokeSuspend(ChatClient.kt:16)
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:589)
Caused by: java.net.ConnectException: Connection refused: no further information
at java.base/sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:839)
at io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:37)
at io.ktor.network.sockets.SocketImpl$connect$1.invokeSuspend(SocketImpl.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
> Task :ChatClientKt.main() FAILED
HttpClient has strange behaviour when made many parellels requests (JsonFeature with GsonSerializer enabled)
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/821
Hi Ktor team!
I have some problems with the client when make many parallels http requests.
I hope logs and code examples will help you.
Ktor Version
1.0.1
Ktor Engine Used
Android Engine
Features enabled
JsonFeature with GsonSerializer
Platform and hardware
Android 7, 4-core ARM CPU
Feedback
fun test() {
for (i in 1..100) {
println("Start launch")
GlobalScope.launch(Dispatchers.Default) {
val data = client.get<MyResponse>(url)
println("Response completed: $i")
}
println("Finish launch")
}
}`
Logs (please see time, it important, in really one request take about 1-3 seconds):
`
2018-12-24 01:07:47.388 I/System.out: Start launch
2018-12-24 01:07:47.403 I/System.out: Finish launch
// skiped logs: started 100 coroutines
2018-12-24 01:07:47.487 I/System.out: Start launch
2018-12-24 01:07:47.488 I/System.out: Finish launch // last coroutine started
// there is a big pause: in really one request take about 1-3 seconds
2018-12-24 01:07:57.671 I/System.out: Response completed: 9 // first response
// there is a big pause: in really request take about 1-3 seconds
2018-12-24 01:08:27.280 I/System.out: Response completed: 94 // second response
2018-12-24 01:08:28.265 I/System.out: Response completed: 1
2018-12-24 01:08:28.275 I/System.out: Response completed: 2
2018-12-24 01:08:28.285 I/System.out: Response completed: 4
2018-12-24 01:08:28.310 I/System.out: Response completed: 8
2018-12-24 01:08:28.396 I/System.out: Response completed: 80
// skiped logs: responses are distributed evenly in time
2018-12-24 01:08:29.268 I/System.out: Response completed: 92
2018-12-24 01:08:29.280 I/System.out: Response completed: 97
2018-12-24 01:08:29.296 I/System.out: Response completed: 99
2018-12-24 01:08:29.371 I/System.out: Response completed: 100 // last response`
I overrided GsonSerializer and added logs into problematic code.
override suspend fun read(type: TypeInfo, response: HttpResponse): Any {
println("Start response.readText on ${Thread.currentThread().name}")
val text = response.readText()
println("End response.readText on ${Thread.currentThread().name}")
return backend.fromJson(text, type.reifiedType) ?: Any()
}
Logs:
`2018-12-24 01:32:38.895 I/System.out: Start response.readText on DefaultDispatcher-worker-3 // first start readText
2018-12-24 01:32:38.895 I/System.out: Start response.readText on DefaultDispatcher-worker-2
2018-12-24 01:32:38.895 I/System.out: Start response.readText on DefaultDispatcher-worker-1
2018-12-24 01:32:38.895 I/System.out: Start response.readText on DefaultDispatcher-worker-4
2018-12-24 01:32:40.160 I/System.out: Start response.readText on DefaultDispatcher-worker-1
// skiped 45 logs Start response.readText on ..
2018-12-24 01:33:11.137 I/System.out: End response.readText on DefaultDispatcher-worker-3 // first end readText
2018-12-24 01:33:11.311 I/System.out: Start response.readText on DefaultDispatcher-worker-3
2018-12-24 01:33:12.202 I/System.out: Start response.readText on DefaultDispatcher-worker-4
2018-12-24 01:33:12.298 I/System.out: Start response.readText on DefaultDispatcher-worker-1
2018-12-24 01:33:12.663 I/System.out: Start response.readText on DefaultDispatcher-worker-2
2018-12-24 01:33:12.668 I/System.out: Start response.readText on DefaultDispatcher-worker-1
2018-12-24 01:33:14.052 I/System.out: Start response.readText on DefaultDispatcher-worker-3
2018-12-24 01:33:14.091 I/System.out: Start response.readText on DefaultDispatcher-worker-4
2018-12-24 01:33:14.238 I/System.out: Start response.readText on DefaultDispatcher-worker-4
2018-12-24 01:33:14.313 I/System.out: Start response.readText on DefaultDispatcher-worker-2
2018-12-24 01:33:15.475 I/System.out: Start response.readText on DefaultDispatcher-worker-2
2018-12-24 01:33:15.486 I/System.out: Start response.readText on DefaultDispatcher-worker-3
2018-12-24 01:33:15.576 I/System.out: Start response.readText on DefaultDispatcher-worker-3
2018-12-24 01:33:15.646 I/System.out: Start response.readText on DefaultDispatcher-worker-4
2018-12-24 01:33:15.746 I/System.out: End response.readText on DefaultDispatcher-worker-3
2018-12-24 01:33:15.754 I/System.out: End response.readText on DefaultDispatcher-worker-2
2018-12-24 01:33:15.766 I/System.out: End response.readText on DefaultDispatcher-worker-1
2018-12-24 01:33:15.877 I/System.out: End response.readText on DefaultDispatcher-worker-4
2018-12-24 01:33:15.914 I/System.out: End response.readText on DefaultDispatcher-worker-2
2018-12-24 01:33:15.930 I/System.out: End response.readText on DefaultDispatcher-worker-1
2018-12-24 01:33:15.938 I/System.out: End response.readText on DefaultDispatcher-worker-3
2018-12-24 01:33:15.995 I/System.out: End response.readText on DefaultDispatcher-worker-4
2018-12-24 01:33:16.051 I/System.out: End response.readText on DefaultDispatcher-worker-1
2018-12-24 01:33:16.101 I/System.out: End response.readText on DefaultDispatcher-worker-2
// skiped many End response.readText on ..
2018-12-24 01:33:16.963 I/System.out: End response.readText on DefaultDispatcher-worker-4
2018-12-24 01:33:16.963 I/System.out: End response.readText on DefaultDispatcher-worker-3
2018-12-24 01:33:16.974 I/System.out: End response.readText on DefaultDispatcher-worker-1
2018-12-24 01:33:16.982 I/System.out: End response.readText on DefaultDispatcher-worker-2
2018-12-24 01:33:17.115 I/System.out: Start response.readText on DefaultDispatcher-worker-2
2018-12-24 01:33:17.129 I/System.out: End response.readText on DefaultDispatcher-worker-3 // last end readText`
I think problem in content.readRemaining
method.
Maybe related https://github.com/ktorio/ktor/issues/768, https://github.com/ktorio/ktor/issues/770
I am ready to provide more details let me know.
Thanks!
Reload does not work with "user-local default: JAR manifest" command line shortener
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1266
Ktor Version and Engine Used (client or server and name)
Ktor 1.2.3 embedded Jetty server.
Describe the bug
If server is started with "user-local default: JAR manifest" command line shortener it seems allUrls
in ApplicationEngineEnvironmentReloading.kt
only has a couple of java jars thus it prints:
2019-08-07 14:11:13,088 INFO --- [main] Application : No ktor.deployment.watch patterns match classpath entries, automatic reload is not active
To Reproduce
Error is shown on startup with the mentioned shorten command line option.
Expected behavior
Reloading works. It works for us if we use the classpath file
shorten command line option. The options are not visible for kotlin run configurations but can be accessed in a Java Application run application (with same options as a Kotlin version.). I think the broken option is the default so it would be much nicer if Ktor worked with that.
Expose LocationInfo in ktor locations
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1657
Subsystem
Server
Is your feature request related to a problem? Please describe.
We wanted our typed locations to be able to define a permission node / access level. We did this via an additional annotation but then we had to write an interceptor that would get the class annotated with @Location
from the request path and check for ours. The problem is that there doesn't seem to be any public access to the mapping done in Locations. Right now we're using a pretty-narly looking reflection solution that's less than ideal.
Describe the solution you'd like
Exposing io.ktor.locations.Locations$LocationInfo
and an immutable copy of io.ktor.locations.Locations.info
should be enough.
Motivation to include to ktor
I'm sure this could be a beneficial feature to a lot of users, even if it's not implemented in the way described above. It'll allow users to add additional properties to their routes and implement them in interceptors (and elsewhere) without having to do any reflection or map what is already mapped within ktor.locations.
InvocationTargetException when running the default ktor project
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1747
Hi all
I've created the Ktor project in Intellj with Ktor plugin, when running the application it shows the following error message:
020-03-23 20:31:20.192 [main] INFO Application - No ktor.deployment.watch patterns specified, automatic reload is not active
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Static.call(CallerImpl.kt:106)
at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:166)
at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:110)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.callFunctionWithInjection(ApplicationEngineEnvironmentReloading.kt:384)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.executeModuleFunction(ApplicationEngineEnvironmentReloading.kt:330)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$executeModuleFunction(ApplicationEngineEnvironmentReloading.kt:33)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1$$special$$inlined$forEach$lambda$1.invoke(ApplicationEngineEnvironmentReloading.kt:275)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1$$special$$inlined$forEach$lambda$1.invoke(ApplicationEngineEnvironmentReloading.kt:33)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:310)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:33)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:274)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:33)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:290)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:272)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:125)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:245)
The java version, that is installed on my computer:
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (Zulu 8.44.0.11-CA-linux64) (build 1.8.0_242-b20)
OpenJDK 64-Bit Server VM (Zulu 8.44.0.11-CA-linux64) (build 25.242-b20, mixed mode)
And here in the Application.kt
:
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(Authentication) {
}
install(ContentNegotiation) {
gson {
}
}
val client = HttpClient() {
install(JsonFeature) {
serializer = GsonSerializer()
}
install(Logging) {
level = LogLevel.HEADERS
}
BrowserUserAgent() // install default browser-like user-agent
// install(UserAgent) { agent = "some user agent" }
}
runBlocking {
// Sample for making a HTTP Client request
/*
val message = client.post<JsonSampleClass> {
url("http://127.0.0.1:8080/path/to/endpoint")
contentType(ContentType.Application.Json)
body = JsonSampleClass(hello = "world")
}
*/
}
routing {
get("/") {
call.respondText("HELLO WORLD!", contentType = ContentType.Text.Plain)
}
get("/json/gson") {
call.respond(mapOf("hello" to "world"))
}
}
}
data class JsonSampleClass(val hello: String)
Thanks
Fix version publishing for Kotlinx.html
Implementation for Allow to configure features for subroutes
Implementation for Review `Locations` feature
HttpClient hang when delay()
I am trying to use both ktor server and client in a single application with coroutine, but I realized it is quite buggy. Here is the minimal example of one of the problems (maybe this is the core problem), the function hangs forever since it can't pass the delay(1000)
line, while it works fine if it is delay(100)
Minimal Example:
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.server.cio.*
import io.ktor.server.engine.*
import kotlinx.coroutines.*
import org.junit.jupiter.api.Test
internal class KtorBugTest {
@Test
fun minimalTest() {
val server = embeddedServer(
CIO,
environment = applicationEngineEnvironment {
connector {
port = 12345
host = "127.0.0.1"
}
}
)
val client = HttpClient(io.ktor.client.engine.cio.CIO)
runBlocking {
launch {
server.start(true)
}
println("Launched Server")
val job = launch {
val response: HttpResponse = client.get("http://127.0.0.1:12345")
}
println("Launched response")
delay(1000) // works if replaced by delay(100)
println("Cancel job") // Can't reach here
job.cancel()
server.stop(1000, 1000)
}
}
}
Fix `releaseVersion` property
Ktor 1.5.3 relies on kotlinx-html-jvm:0.7.3
Ktor 1.5.3 relies on kotlinx-html-jvm:0.7.3, but version 0.7.3 has not been published to any of the public maven repos.
Release 1.5.3
Fix flaky ProxyTest.testHttpProxy[CIO][jvm]
1.5.2
released 26th February 2021
Client
ResponseObserver does not respect MDC context
When logging response from. Ktor-client Logging
feature with the help of ResponseObserver
, the log does not contains the MDC context.
This mean when tracking all logs bound to an MDC context, response content log remains untracked.
This test illustrates the mentioned scenario :
in package [jvmTest]io.ktor.client.features.logging
, in file LoggingTest.kt
@Test
fun loggingWithMDCContextTest(): Unit = runBlocking {
val messages: MutableList<String> = mutableListOf()
val testLogger: Logger = object : Logger by LoggerFactory.getLogger("ktor.test") {
override fun trace(message: String?) = add("TRACE: $message")
override fun debug(message: String?) = add("DEBUG: $message")
override fun info(message: String?) = add("INFO: $message")
private fun add(message: String?) {
if (message != null) {
val mdcText = MDC.getCopyOfContextMap()?.let { mdc ->
if (mdc.isNotEmpty()) {
mdc.entries.sortedBy { it.key }
.joinToString(prefix = " [", postfix = "]") { "${it.key}=${it.value}" }
} else {
""
}
} ?: ""
messages.add(message + mdcText)
}
}
}
val localTestLogger: io.ktor.client.features.logging.Logger = object : io.ktor.client.features.logging.Logger {
override fun log(message: String) {
testLogger.info(message)
}
}
val client: HttpClient = HttpClient {
install(Logging) {
level = LogLevel.ALL
logger = localTestLogger
// responseCoroutineContext = MDCContext() This is to provide CoroutineContext from feature config
}
}
MDC.put("trace_id", "48b49e15-f7c4-4991-9e33-23850a218718")
val response: String = withContext(MDCContext()) {
client.get("https://jsonplaceholder.typicode.com/todos/1")
}
MDC.clear()
assertNotNull(messages)
assertNotNull(response)
}
Theory : First I suspected that this is because of EmptyCoroutineContext
being used in launch
, but when I gave MDCContext()
from kotlinx-coroutines-slf4j
to it, it still didn't work. Now I suspect, it is because function is provided as a predicate. However, I am not too sure about it. Can you help please ?
Change I tested :
scope.launch(feature.coroutineContext) {
try {
feature.responseHandler(sideCall.response)
} catch (_: Throwable) {
}
val content = sideCall.response.content
if (!content.isClosedForRead) {
content.discard()
}
}
where ResponseObserver = public class ResponseObserver(private val responseHandler: ResponseHandler, private val coroutineContext: CoroutineContext = EmptyCoroutineContext)
Elements of messages :
INFO: REQUEST: https://jsonplaceholder.typicode.com/todos/1 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: METHOD: HttpMethod(value=GET) [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: COMMON HEADERS [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> Accept: */* [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> Accept-Charset: UTF-8 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: CONTENT HEADERS [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> Content-Length: 0 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: BODY Content-Type: null [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: BODY START [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: BODY END [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: RESPONSE: 200 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: METHOD: HttpMethod(value=GET) [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: FROM: https://jsonplaceholder.typicode.com/todos/1 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: COMMON HEADERS [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> access-control-allow-credentials: true [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> age: 5692 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> cache-control: max-age=43200 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> cf-cache-status: HIT [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> cf-ray: 63a05ebc7fdf10f3-CPH [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> cf-request-id: 09381989cb000010f38b157000000001 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> content-type: application/json; charset=utf-8 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> date: Sat, 03 Apr 2021 06:51:43 GMT [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> etag: W/"53-hfEnumeNh6YirfjyjaujcOPPT+s" [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> expires: -1 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> nel: {"max_age":604800,"report_to":"cf-nel"} [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> pragma: no-cache [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> report-to: {"group":"cf-nel","endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report?s=39vBUwnOjgmuV0yQJPiunD1hXHE28ku4%2FDq55GSjP%2B66ufTXnkgV1a%2F2H7DHSc28QRc1OiJA6k%2BKKXceS0z%2BaAFzxTU%2FuDpZCeBrgceQ7gvyxUQ45qbyYIa4sg2x"}],"max_age":604800} [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> server: cloudflare [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> set-cookie: __cfduid=dbe3ed123d14f8db7af7d7697a9f4049f1617432703; expires=Mon, 03-May-21 06:51:43 GMT; path=/; domain=.typicode.com; HttpOnly; SameSite=Lax [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> vary: Origin, Accept-Encoding [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> via: 1.1 vegur [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-content-type-options: nosniff [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-powered-by: Express [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-ratelimit-limit: 1000 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-ratelimit-remaining: 999 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: -> x-ratelimit-reset: 1616387857 [trace_id=48b49e15-f7c4-4991-9e33-23850a218718]
INFO: BODY Content-Type: application/json; charset=utf-8
INFO: BODY START
INFO: {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
INFO: BODY END
Can't set a base url that includes path data
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/684
My team is trying to move from using OkHttp to using ktor as our HttpClient. We've found this difficult because there doesn't seem to be a way to set the default base url.
If we have the following requests:
https://www.example.com/api/getA/
https://www.example.com/api/getB/
https://www.example.com/api/getC/
It's common practice to have the ability to set the base url for the http client as https://www.example.com/api/
, and then only specify the suffix of the path (/getA/
, /getB/
, /getC/
) at the actual request site.
I like the way you can create a defaultRequest
when creating the http client with ktor and then intuitively override what's needed at the request site, however this functionality needs to be extended to allow the inclusion of path data in the base url. This functionality is available in OkHttp and also Retrofit. Otherwise it requires significant refactoring in our code-base when switching between environments, as base urls and base paths often change between environments. I imagine this will be a common problem.
If somehow this is already possible, it's not clear that it is, and in which case, this should be documented better.
CIO WebSockets client incorrectly sends Sec-WebSocket-Extensions header even if empty
I have observed that the CIO WebSockets client is sending out a Sec-WebSocket-Extensions
header in the initial handshake even if no extension has been specified. This is causing issues with RFC 6455 compliant servers as (if my understanding is correct) the spec mandates that at least one extension be provided if the header exists (otherwise fail).
https://tools.ietf.org/html/rfc6455#section-9.1
https://tools.ietf.org/html/rfc2616#section-2.1 (ABNF notes)
I am not familiar enough with the Ktor code to submit a PR with a fix, but as a workaround a simple plugin can be used to strip the header from the request before sending it to the server:
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.util.*
/**
* Intercept outgoing HTTP requests to strip the `Sec-WebSocket-Extensions` header if it is empty.
*
* This feature should be installed after the `WebSockets` feature has been installed.
*/
internal class StripBlankSWSEHeader {
private fun stripHeader(context: HttpRequestBuilder) {
if (HttpHeaders.SecWebSocketExtensions in context.headers
&& context.headers[HttpHeaders.SecWebSocketExtensions]?.isBlank() == true) {
context.headers.remove(HttpHeaders.SecWebSocketExtensions)
}
}
class EmptyConfig
internal companion object Feature : HttpClientFeature<EmptyConfig, StripBlankSWSEHeader> {
override val key: AttributeKey<StripBlankSWSEHeader> = AttributeKey("StripEmptyWSExtensionHeader")
override fun prepare(block: EmptyConfig.() -> Unit): StripBlankSWSEHeader {
return StripBlankSWSEHeader()
}
override fun install(feature: StripBlankSWSEHeader, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.Render) {
feature.stripHeader(context)
proceed()
}
}
}
}
In the client configuration the StripBlankSWSEHeader
feature must be registered AFTER the WebSockets
feature:
install(WebSockets)
install(StripBlankSWSEHeader) // must be registered AFTER WebSockets feature
MockEngine doest work for Android
After updating to 1.5.2 Android target doest work. But it is ok for iOS
at 1.4.3 is ok for both platforms iOS and Android.
MockEngine {
// this code is not called on request as result mock doesn't work
}
java.lang.IllegalStateException: No instance for key AttributeKey: ExpectSuccessAttribyteKey
After updating from ktor 1.4.3 to 1.5.2 and running my integration tests using ktor I'm running into:
java.lang.IllegalStateException: No instance for key AttributeKey: ExpectSuccessAttribyteKey
at io.ktor.util.Attributes$DefaultImpls.get(Attributes.kt:29)
at io.ktor.util.AttributesJvmBase.get(AttributesJvm.kt:15)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidation.kt:28)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.features.HttpCallValidator.validateResponse(HttpCallValidator.kt:55)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invokeSuspend(HttpCallValidator.kt:134)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invoke(HttpCallValidator.kt)
at io.ktor.client.features.HttpSend$Feature$install$1.invokeSuspend(HttpSend.kt:98)
at ???(Coroutine boundary.?(?)
at app.becoach.roboter.CoacheeRoboter$login$1.invokeSuspend(CoacheeRoboter.kt:26)
Caused by: java.lang.IllegalStateException: No instance for key AttributeKey: ExpectSuccessAttribyteKey
at io.ktor.util.Attributes$DefaultImpls.get(Attributes.kt:29)
at io.ktor.util.AttributesJvmBase.get(AttributesJvm.kt:15)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidation.kt:28)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.features.HttpCallValidator.validateResponse(HttpCallValidator.kt:55)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invokeSuspend(HttpCallValidator.kt:134)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invoke(HttpCallValidator.kt)
at io.ktor.client.features.HttpSend$Feature$install$1.invokeSuspend(HttpSend.kt:98)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Do you happen to have any idea what's causing this? My unit tests are getting 4xx error codes from the backend. I've got a custom Feature installed where upon unauthorized error codes, I'm re-issuing a new token.
My feature looks like this:
internal class AuthFeature(
private val credentials: String,
private val tokenStorage: TokenStorage,
private val logger: Logger
) {
class Config {
lateinit var credentials: String
lateinit var tokenStorage: TokenStorage
lateinit var logger: Logger
}
companion object Feature : HttpClientFeature<Config, AuthFeature> {
override val key: AttributeKey<AuthFeature> = AttributeKey("AuthFeature")
/**
* The Mutex is required in order to only refresh once when multiple requests
* are being triggered simultaneously e.g. at app startup.
*/
private val mutex = Mutex()
override fun prepare(block: Config.() -> Unit): AuthFeature {
val config = Config().apply(block)
return AuthFeature(
credentials = config.credentials,
tokenStorage = config.tokenStorage,
logger = config.logger,
)
}
@KtorExperimentalAPI
override fun install(feature: AuthFeature, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.State) {
val accessToken = feature.tokenStorage.getAccessToken()
val isRefreshingToken = context.url.buildString().endsWith("oauth/token")
val header = if (accessToken != null && !isRefreshingToken) "Bearer $accessToken" else feature.credentials
context.headers.append(HttpHeaders.Authorization, header)
}
requireNotNull(scope.feature(HttpSend)).intercept { call, _ ->
if (call.response.status == HttpStatusCode.Unauthorized) {
val mutexId = uuid4().toString()
val tag = "${key.name}-$mutexId"
feature.logger.i(tag, "Got ${HttpStatusCode.Unauthorized.value}")
val beforeMutexAccessToken = feature.tokenStorage.getAccessToken()
val request = call.request
mutex.withLock {
val inMutexAccessToken = feature.tokenStorage.getAccessToken()
val needsToRefresh = beforeMutexAccessToken == inMutexAccessToken
if (needsToRefresh) {
feature.tokenStorage.putAccessToken(null) // Invalidate access token.
}
val refreshToken = feature.tokenStorage.getRefreshToken()
if (refreshToken != null) {
if (needsToRefresh) {
feature.logger.i(tag, "Issuing new access token")
val refreshResponse = scope.refreshToken(refreshToken)
feature.logger.i(tag, "Updating access & refresh token")
feature.tokenStorage.putAccessToken(refreshResponse.accessToken)
feature.tokenStorage.putRefreshToken(refreshResponse.refreshToken)
} else {
feature.logger.i(tag, "Other request already refreshed token")
}
// Build previous request once again.
val requestBuilder = HttpRequestBuilder()
requestBuilder.takeFrom(request)
// Force the new token by explicitly setting it.
requestBuilder.headers[HttpHeaders.Authorization] = "Bearer ${feature.tokenStorage.getAccessToken()}"
feature.logger.i(tag, "Retrying previous request with new access token")
execute(requestBuilder).also {
feature.logger.i(tag, "Finished previous request")
}
} else {
feature.logger.i(tag, "No refresh token and hence leaving as is")
call
}
}
} else {
call
}
}
}
}
}
Fix flaky CIOSustainabilityTest.testBlockingConcurrency[jvm]
java.net.SocketException: Connection reset by peer
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:715)
at io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:38)
at io.ktor.network.sockets.SocketImpl$connect$1.invokeSuspend(SocketImpl.kt)
at (Coroutine boundary. ( )
at kotlinx.coroutines.CompletableDeferredImpl.await(CompletableDeferred.kt:86)
Ktor 1.5.2: Can't resolve 'node-fetch' on libs produced by jsBrowserProductionLibraryDistribution
I'm using Ktor client with an MPP project.
After upgrading to 1.5.2, I started having this error on a yarn
project that is using my js module as a javascript dependency:
./node_modules/mp-game-clib/mp-game-clib.js
Module not found: Can't resolve 'node-fetch' in '/Users/brunomed/git/pok-p2p-game-demo/react-app/node_modules/mp-game-clib'
That's the relevant Gradle config for the library project:
js(IR) {
useCommonJs()
browser {
binaries.library()
webpackTask {
enabled = false
}
testTask {
useKarma {
useChromeHeadless()
webpackConfig.cssSupport.enabled = true
}
}
}
}
...
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":client-server"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-websockets:$ktorVersion")
implementation("com.benasher44:uuid:0.2.3")
// https://youtrack.jetbrains.com/issue/KTOR-541
runtimeOnly(npm("text-encoding", "0.7.0"))
runtimeOnly(npm("abort-controller", "3.0.0"))
runtimeOnly(npm("node-fetch", "2.6.1")) <== Had to add this to fix the problem for now, I didn't need it with Ktor 1.5.1
}
}
...
}
Not sure if I'm doing something wrong here, but adding those npm
dependencies is the only way I could find to produce correct package.json
on the jsBrowserProductionLibraryDistribution
goal (previous comment here: https://youtrack.jetbrains.com/issue/KTOR-541#focus=Comments-27-4731438.0-0)
Changing `requestTimeoutMillis` in config of HttpTimeout feature doesn't change the CIO's timeout
Ktor 1.5.0
I am using CIO Engine which has a default timeout of 15000 milliseconds, however, I changed the timeout for a particular request using the HttpRequestBuilder.timeout
extension function, but I still get ConnectTimeoutException after 15000 milliseconds.
Gradle:
dependencies {
....
def ktorVersion = "1.5.0"
implementation "io.ktor:ktor-client-core:$ktorVersion"
implementation "io.ktor:ktor-client-cio:$ktorVersion"
implementation "io.ktor:ktor-client-serialization:$ktorVersion"
implementation "io.ktor:ktor-client-logging-jvm:$ktorVersion"
....
}
HttpClient:
HttpClient(CIO) {
defaultRequest {
host = HOST
header("Content-Type", "application/json")
}
Logging {
logger = object : Logger {
override fun log(message: String) {
Timber.i(message)
}
}
level = LogLevel.ALL
}
install(HttpTimeout)
}
Request:
httpClient.post("${HOST_URL}/users/new") {
timeout {
requestTimeoutMillis = 30000
}
body = requestBody
}
HTTP Client exception is masked by JobCancellationException with Ktor 1.5.0
STR: perform http GET with ktor Apache client to a resource which replies with truncated content
AR:
kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelled}@eb5ab92
at kotlinx.coroutines.JobSupport.getChildJobCancellationCause(JobSupport.kt:711)
at kotlinx.coroutines.JobSupport.createCauseException(JobSupport.kt:717)
at kotlinx.coroutines.JobSupport.cancelMakeCompleting(JobSupport.kt:692)
at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:664)
at kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:634)
at kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1465)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1500)
at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:897)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:860)
at kotlinx.coroutines.JobSupport.cancelMakeCompleting(JobSupport.kt:693)
at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:664)
at kotlinx.coroutines.JobSupport.childCancelled(JobSupport.kt:648)
at kotlinx.coroutines.ChildHandleNode.childCancelled(JobSupport.kt:1466)
at kotlinx.coroutines.JobSupport.cancelParent(JobSupport.kt:358)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:332)
at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:897)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:860)
at kotlinx.coroutines.JobSupport.makeCompleting$kotlinx_coroutines_core(JobSupport.kt:803)
at kotlinx.coroutines.JobImpl.completeExceptionally(JobSupport.kt:1326)
at io.ktor.client.engine.apache.ApacheRequestProducer.failed(ApacheRequestProducer.kt:72)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.executionFailed(DefaultClientExchangeHandlerImpl.java:98)
at org.apache.http.impl.nio.client.AbstractClientExchangeHandler.failed(AbstractClientExchangeHandler.java:426)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.exception(HttpAsyncRequestExecutor.java:163)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:276)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:81)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:39)
at org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:114)
at org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:162)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:337)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:315)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:276)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)
... 1 more
Caused by: org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body (expected: 178; received: 0)
at org.apache.http.impl.nio.codecs.LengthDelimitedDecoder.read(LengthDelimitedDecoder.java:89)
at io.ktor.client.engine.apache.ApacheResponseConsumer$consumeContent$1.invoke(ApacheResponseConsumer.kt:52)
at io.ktor.client.engine.apache.ApacheResponseConsumer$consumeContent$1.invoke(ApacheResponseConsumer.kt:18)
at io.ktor.utils.io.ByteBufferChannel.writeAvailable(ByteBufferChannel.kt:1631)
at io.ktor.utils.io.ByteWriteChannel$DefaultImpls.writeAvailable$default(ByteWriteChannel.kt:85)
at io.ktor.client.engine.apache.ApacheResponseConsumer.consumeContent(ApacheResponseConsumer.kt:51)
at org.apache.http.impl.nio.client.MainClientExec.consumeContent(MainClientExec.java:329)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.consumeContent(DefaultClientExchangeHandlerImpl.java:157)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.inputReady(HttpAsyncRequestExecutor.java:336)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:265)
... 10 more
ER: ConnectionClosedException
is not masked
org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body (expected: 178; received: 0)
at org.apache.http.impl.nio.codecs.LengthDelimitedDecoder.read(LengthDelimitedDecoder.java:89)
at io.ktor.client.engine.apache.ApacheResponseConsumer$consumeContent$1.invoke(ApacheResponseConsumer.kt:52)
at io.ktor.client.engine.apache.ApacheResponseConsumer$consumeContent$1.invoke(ApacheResponseConsumer.kt:18)
at io.ktor.utils.io.ByteBufferChannel.writeAvailable(ByteBufferChannel.kt:1631)
at io.ktor.utils.io.ByteWriteChannel$DefaultImpls.writeAvailable$default(ByteWriteChannel.kt:85)
at io.ktor.client.engine.apache.ApacheResponseConsumer.consumeContent(ApacheResponseConsumer.kt:51)
at org.apache.http.impl.nio.client.MainClientExec.consumeContent(MainClientExec.java:329)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.consumeContent(DefaultClientExchangeHandlerImpl.java:157)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.inputReady(HttpAsyncRequestExecutor.java:336)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:265)
feature interceptor ignored: ktor client
https://github.com/ktorio/ktor/blob/master/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/Auth.kt#L39
—> I implemented the auth feature using 90% of the code from your example.
At this moment:
scope.feature(HttpSend)!!.intercept { origin, context ->
println("foo bar") // never happened
}
CIO
: last working version is 1.4.3
,
Apache
, Jetty
: work with any ktor version,
OkHttp
doesn't work even with 1.4.3
.
No special logic, the example above is enough to reproduce.
The block inside intercept {}
is just not called.
Ktor Http Client: Receive null on a not nullable field when parsing response payload
Assume you want to query data from an API that responds with the following payload if everything works well, while the data
field is generic but should never be null.
data class Response<T: Any>(val message: String, val data: T)
If now something goes wrong and the API returns a JSON such as:
{"message":"error", "data": null}
Then the payload.data
will be null...
val response = httpClient.get<HttpResponse>("some-endpoint")
val payload: Response<ExpectedDataType> = response.receive()
I do not know how this can happen but it does and leads to very unexpected NullPointerExceptions. I would expect that parsing would fail if the object of the specified type can not be constructed. I would highly encourage some feedback on this issue :-)
Core
Add modulepath support for Java >= 9 breaking-change
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1137
Ktor application can't be compiled with JDK >=9. I'm getting a lot of such errors:
error: module kotlinx.io.jvm reads package io.ktor.http from both ktor.server.core and ktor.http.jvm
error: module kotlinx.io.jvm reads package io.ktor.http.content from both ktor.server.core and ktor.http.jvm
error: module kotlinx.io.jvm reads package io.ktor.util from both ktor.server.core and ktor.utils.jvm
error: module kotlinx.coroutines.jdk8 reads package io.ktor.http from both ktor.server.core and ktor.http.jvm
error: module kotlinx.coroutines.jdk8 reads package io.ktor.http.content from both ktor.server.core and ktor.http.jvm
error: module kotlinx.coroutines.jdk8 reads package io.ktor.util from both ktor.server.core and ktor.utils.jvm
error: module kotlinx.coroutines.core reads package io.ktor.http from both ktor.server.core and ktor.http.jvm
error: module kotlinx.coroutines.core reads package io.ktor.http.content from both ktor.server.core and ktor.http.jvm
error: module kotlinx.coroutines.core reads package io.ktor.util from both ktor.server.core and ktor.utils.jvm
...................
Support of Java 8 ends quite soon, so it makes sense to start supporting Jigsaw. IMHO, this should be prioritized, as it will require breaking backward compatibility. The less Ktor is popular, the easier would be to break backward compatibility. And Ktor will definitely become more popular over time.
Ktor Version
1.2.0
JVM Version, Operating System and Relevant Context
JDK 12
Significant performance penalty with socket write() and file read() on JVM
Hi,
It seems that there are performance issues with the current implementation of TCP socket write and also with reading files. Attaching the performance comparison for ktor IO vs regular JVM raw IO:
Files:
--------------------------------------------------------------------
Ktor channel read (128MiB), 20 iterations
Avg: 1239.65ms Min: 1095ms, Max: 1723ms
Raw: 1095, 1104, 1113, 1113, 1113, 1122, 1123, 1126, 1151, 1184, 1249, 1251, 1254, 1254, 1278, 1314, 1351, 1399, 1476, 1723
--------------------------------------------------------------------
Random File Read (128MiB), 20 iterations
Avg: 75.4ms Min: 65ms, Max: 161ms
Raw: 65, 65, 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 70, 70, 75, 79, 83, 96, 161
--------------------------------------------------------------------
Stream Read (128MiB), 20 iterations
Avg: 58.5ms Min: 56ms, Max: 69ms
Raw: 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 58, 58, 59, 59, 59, 59, 59, 65, 69
Sockets:
--------------------------------------------------------------------
JVM socket write (128MiB), 20 iterations
Avg: 87.7ms Min: 79ms, Max: 112ms
Raw: 79, 81, 81, 82, 82, 82, 83, 83, 84, 84, 84, 86, 87, 90, 90, 91, 92, 96, 105, 112
--------------------------------------------------------------------
Ktor socket write (128MiB), 20 iterations
Avg: 675.3ms Min: 572ms, Max: 968ms
Raw: 572, 582, 583, 589, 598, 612, 633, 636, 651, 676, 682, 683, 686, 688, 689, 702, 727, 763, 786, 968
You can reproduce the performance measurements with the following tests:
https://gist.github.com/Malinskiy/2dbe05a38321417fd4f23e1872593806
https://gist.github.com/Malinskiy/0172b369040cabc074ccfea9a787a79c
This is the result of an investigation of the issue raised here https://github.com/Malinskiy/marathon/issues/462
Cheers,
Anton
I/O readRemaining sometimes looses exception
The function channel.readRemaining
sometimes returns a packet (possibly the empty one) when the channel is closed abnormally.
Upgrade kotlinx.serialization to 1.1.0
Upgrade to coroutines 1.4.3
Ktor Client CIO engine Jvm ignores Ciphersuites with key strength more than 128 bits.
The list contains TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 here :
https://github.com/ktorio/ktor/blob/master/ktor-network/ktor-network-tls/common/src/io/ktor/network/tls/CipherSuites.kt#L144-L152
but it is ignored by isSupported method here :
https://github.com/ktorio/ktor/blob/master/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/CipherSuitesJvm.kt#L9-L12
which makes these cipher suites unavailable to be used because of :
https://github.com/ktorio/ktor/blob/master/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSHandshakeType.kt#L70
Session cookie with very long max age duration
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/893
When configuring a very very long session duration it may cause integer overflow. It need to be decided what should be done in this case:
- fail at attempt to set
duration
- bump
max-age
toInt.MAX_VALUE
orLong.MAX_VALUE
(long type requires changes, probably breaking changes)
This is related to #892.
Docs
Behaviour of ApplicationEngine start method not documented properly
The wait
parameter of the start
method states that
wait - if true, this function does not exit until application engine stops and exits
Mentioned here: https://api.ktor.io/1.5.2/io.ktor.server.engine/-application-engine/start.html
Does it mean when wait
is false
, the method will exit before the ApplicationEngine stops. Would this mean it only handles the first request made to it?
Timeout documentation is inaccurate
This issue was imported from GitHub issue: https://github.com/ktorio/ktorio.github.io/issues/268
https://ktor.io/clients/http-client/features/timeout.html
The Client Timeout Feature states that "By default all these timeouts are infinite"; this doesn't seem to be the case with either the Apache or CIO engines where unless I explicitly set a timeout I get:
Apache
io.ktor.network.sockets.SocketTimeoutException: Socket timeout has been expired [url=http://localhost:1080/api/import/tickets/v1/file, socket_timeout=unknown] ms
at io.ktor.client.features.HttpTimeoutKt.SocketTimeoutException(HttpTimeout.kt:170)
at io.ktor.client.engine.apache.ApacheHttpRequestKt$sendRequest$$inlined$suspendCancellableCoroutine$lambda$2.failed(ApacheHttpRequest.kt:44)
at org.apache.http.concurrent.BasicFuture.failed(BasicFuture.java:137)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.executionFailed(DefaultClientExchangeHandlerImpl.java:101)
at org.apache.http.impl.nio.client.AbstractClientExchangeHandler.failed(AbstractClientExchangeHandler.java:426)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.timeout(HttpAsyncRequestExecutor.java:387)
at org.apache.http.impl.nio.client.InternalIODispatch.onTimeout(InternalIODispatch.java:92)
at org.apache.http.impl.nio.client.InternalIODispatch.onTimeout(InternalIODispatch.java:39)
at org.apache.http.impl.nio.reactor.AbstractIODispatch.timeout(AbstractIODispatch.java:175)
at org.apache.http.impl.nio.reactor.BaseIOReactor.sessionTimedOut(BaseIOReactor.java:261)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.timeoutCheck(AbstractIOReactor.java:502)
at org.apache.http.impl.nio.reactor.BaseIOReactor.validate(BaseIOReactor.java:211)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:280)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.net.SocketTimeoutException: 10,000 milliseconds timeout on connection http-outgoing-0 [ACTIVE]
... 11 common frames omitted
CIO (which doesn't seem to accept specifying timeouts at all)
kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 15000 ms
at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:149)
at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:119)
at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:493)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:272)
at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:68)
at java.base/java.lang.Thread.run(Thread.java:834)
TomCat Documentation
It would be good to have some docs on how to deploy using TomCat, and also how to configure SSL and also to have app run off of sub-route (i.e. https://domain.com/my-app-route)
Custom response validation is not running when default is disabled
[Outdated doc] Ktor quickstart contains outdated reference to version before 1.0
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1950
Ktor Version and Engine Used (client or server and name)
https://ktor.io/quickstart/quickstart/gradle.html#intellij-improve-the-app-with-the-application-object
Describe the bug
Ktor is currently at version 1.3.2, but in the quickstart docs it is mentioned that is not yet 1.0. Referenced line is: "Since Ktor is not yet 1.0, we have custom Maven repositories for distributing our early preview artifacts."
Expected behavior
Line should be corrected.
Document usage of Bearer token in Http Client
In https://ktor.io/docs/features-auth.html:
We should add support for bearer token authorization; it's the most common one in APIs for apps
Documentation on Cancelling Requests in Client.
Should cover multiplatform scenarios also
Client Testing topic contains code that does not compile
https://ktor.io/docs/http-client-testing.html
Code sample probably is using an old version of the API:
request.url.fullUrl
-> request.url.toString()
Docker doc is incorrect / does not work
https://ktor.io/docs/docker.html
The example does not work and is outdated.
Documentation search doesn't work on the website
It seems that the search functionality doesn't work in the docs.
Chrome Version 88.0.4324.192, macOS.
Generator
Wizard: Different serialization engines should be made mutually exclusive
Adding "Gson" and "Kotlinx.Serialization" dependencies in the same wizard project generates the following code:
install(ContentNegotiation) {
gson {
}
}
install(ContentNegotiation) {
json()
}
// . . .
routing {
get("/json/gson") {
call.respond(mapOf("hello" to "world"))
}
}
routing {
get("/json/kotlinx-serialization") {
call.respond(mapOf("hello" to "world"))
}
}
This (expectedly) crashes at first run:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
Caused by: io.ktor.application.DuplicateApplicationFeatureException: Conflicting application feature is already installed with the same key as `ContentNegotiation`
at io.ktor.application.ApplicationFeatureKt.install(ApplicationFeature.kt:82)
at com.example.ApplicationKt.module(Application.kt:27)
at com.example.ApplicationKt.module$default(Application.kt:21)
Rename build system options in generator
Should be: Gradle Kotlin, Gradle Groovy, Maven
Infrastructure
Unbound public symbol for public io.ktor.network.sockets/SocketTimeoutException when iosArm64 framework
When declaring a dependency on Ktor io.ktor:ktor-network
and io.ktor:ktor-client-ios
, this leads to a compiler error on iosArm64
. Tested using kotlin 1.4.31
and ktor 1.5.2
I created a minimal reproducer here:
https://github.com/PaulWoitaschek/KtorSocketBug
To reproduce it, run ./gradlew linkDebugFrameworkIosArm64
> Task :linkDebugFrameworkIosArm64 FAILED
e: Compilation failed: Unbound symbols not allowed
Unbound public symbol for public io.ktor.network.sockets/SocketTimeoutException.<init>|-4324620762574333298[0] public io.ktor.network.sockets/SocketTimeoutException.<init>|-4324620762574333298[0]
* Source files:
* Compiler version info: Konan: 1.4.31 / Kotlin: 1.4.31
* Output kind: FRAMEWORK
e: java.lang.AssertionError: Unbound symbols not allowed
Unbound public symbol for public io.ktor.network.sockets/SocketTimeoutException.<init>|-4324620762574333298[0] public io.ktor.network.sockets/SocketTimeoutException.<init>|-4324620762574333298[0]
at org.jetbrains.kotlin.ir.util.SymbolTableKt.noUnboundLeft(SymbolTable.kt:1119)
at org.jetbrains.kotlin.psi2ir.Psi2IrTranslator.generateModuleFragment(Psi2IrTranslator.kt:89)
at org.jetbrains.kotlin.backend.konan.PsiToIrKt.psiToIr(PsiToIr.kt:160)
at org.jetbrains.kotlin.backend.konan.ToplevelPhasesKt$psiToIrPhase$1.invoke(ToplevelPhases.kt:135)
at org.jetbrains.kotlin.backend.konan.ToplevelPhasesKt$psiToIrPhase$1.invoke(ToplevelPhases.kt)
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$namedOpUnitPhase$1.invoke(PhaseBuilders.kt:97)
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$namedOpUnitPhase$1.invoke(PhaseBuilders.kt:95)
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:94)
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:30)
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:94)
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:41)
at org.jetbrains.kotlin.backend.konan.KonanDriverKt.runTopLevelPhases(KonanDriver.kt:29)
at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:78)
at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:35)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:88)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:76)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:45)
at org.jetbrains.kotlin.cli.common.CLITool$Companion.doMainNoExit(CLITool.kt:227)
at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:286)
at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:270)
at org.jetbrains.kotlin.util.UtilKt.profileIf(Util.kt:27)
at org.jetbrains.kotlin.util.UtilKt.profile(Util.kt:21)
at org.jetbrains.kotlin.cli.bc.K2Native$Companion.mainNoExitWithGradleRenderer(K2Native.kt:285)
at org.jetbrains.kotlin.cli.bc.K2NativeKt.mainNoExitWithGradleRenderer(K2Native.kt:485)
at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1.invoke(main.kt:43)
at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1.invoke(main.kt)
at org.jetbrains.kotlin.cli.utilities.MainKt.mainImpl(main.kt:17)
at org.jetbrains.kotlin.cli.utilities.MainKt.daemonMain(main.kt:43)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.runInProcess(KotlinToolRunner.kt:124)
at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.run(KotlinToolRunner.kt:73)
at org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile.compile(KotlinNativeTasks.kt:334)
at org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink.compile(KotlinNativeTasks.kt:657)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$2.run(ExecuteActionsTaskExecuter.java:494)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:479)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:462)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$400(ExecuteActionsTaskExecuter.java:105)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.executeWithPreviousOutputFiles(ExecuteActionsTaskExecuter.java:273)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:251)
at org.gradle.internal.execution.steps.ExecuteStep.lambda$executeOperation$1(ExecuteStep.java:66)
at java.base/java.util.Optional.orElseGet(Optional.java:362)
at org.gradle.internal.execution.steps.ExecuteStep.executeOperation(ExecuteStep.java:66)
at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:34)
at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:47)
at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:44)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:44)
at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:34)
at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:72)
at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:42)
at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:53)
at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:39)
at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:44)
at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:77)
at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:58)
at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:54)
at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:32)
at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:57)
at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:38)
at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:63)
at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:30)
at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:176)
at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:76)
at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:47)
at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:43)
at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:32)
at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:39)
at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:25)
at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:102)
at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:95)
at java.base/java.util.Optional.map(Optional.java:258)
at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:55)
at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:39)
at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:83)
at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:44)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:96)
at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:52)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:83)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:54)
at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:74)
at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:88)
at java.base/java.util.Optional.orElseGet(Optional.java:362)
at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:88)
at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:34)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:46)
at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:34)
at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:43)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution$3.withWorkspace(ExecuteActionsTaskExecuter.java:286)
at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:43)
at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:33)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:40)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:30)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:54)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:40)
at org.gradle.internal.execution.impl.DefaultExecutionEngine.execute(DefaultExecutionEngine.java:41)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:183)
at java.base/java.util.Optional.orElseGet(Optional.java:362)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:183)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:173)
at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:62)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
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:200)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:411)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:398)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:391)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:377)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
at java.base/java.lang.Thread.run(Thread.java:832)
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':linkDebugFrameworkIosArm64'.
> Compilation finished with errors
* Try:
Run with --stacktrace option to get the stack trace. 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
BUILD FAILED in 11s
2 actionable tasks: 2 executed
Autoreload not working with 1.5.x when using embeddedServer NOT in debug mode
The 1.5.x update broke the auto-reload feature.
I made an example repo here, when switching from 1.4.x to 1.5.x the auto-reload break (not working anymore).
You can reproduce by running ./gradlew run
and ./gradlew build -t
and switch versions.
Autoreload doesn't work with embeddedServer
Autoreload was broken in 1.5.0 (KTOR-1184) when using embeddedServer with module specified as a parameter.
For example:
embeddedServer(Jetty, 8080, module = Application::main, watchPaths = listOf(File(".").canonicalPath))
I have ./gradlew -t classes
rebuilding changes. Ktor notices changed classes ("INFO ktor.application - Changes in application detected.") and reloads the application, but is using old classes. Dropping the watchPaths
parameter doesn't make a difference. Autoreload works with the same code using Ktor 1.4.3.
Another workaround is to configure the module as if using application.conf:
val environment = applicationEngineEnvironment {
this.config = MapApplicationConfig().apply { put("ktor.application.modules", listOf("path.to.module")) }
}
embeddedServer(Jetty, environment)
These tests were done using version 1.5.2.
IntelliJ IDEA Plugin
Ktor: Fold internal stack frames for HTTP server
Run Ktor app:
import io.ktor.application.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
fun main(args: Array<String>): Unit = EngineMain.main(args)
@Suppress("unused") // Referenced in application.conf
@JvmOverloads
fun Application.module(testing: Boolean = false) {
embeddedServer(Netty, host = "127.0.0.1", port = 8000) {
routing {
get("/hello") {
TODO()
}
}
}.start(wait = true)
}
Open http://localhost:8080/app
Full stacktrace is shown while we can collapse some framework internal frames by default
Adding features should not create sample routes
Choosing both Routing and Kotlinx.Serialization results in the following snippet:
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
json()
}
routing {
get("/") {
call.respondText("Hello World!")
}
}
routing {
get("/json/kotlinx-serialization") {
call.respond(mapOf("hello" to "world"))
}
}
}
These two should probably be merged. Otherwise it looks like every route needs to be in its own routing block.
Reuse Package Search to add dependencies for Ktor Features in Plugin
Quick action on a Application.module(...) to generate tests for a given module with all the endpoints
Link to GitHub for Features is incorrect
If we're linking to the specific code for a feature on GitHub (which should probably be indicated in the metadata for the feature and only linked if present), it should link to the specific feature. Currently all the ones we ship out of the box point to generic https://github.com/ktorio/ktor
Also, please note the text should say
"Source on GitHub" instead of "See features's GitHub"
Wizard: Creating Maven project with Kotlinx.Serialization results in Could not find artifact org.jetbrains.kotlin:kotlin-maven-serialization:pom
Could not find artifact org.jetbrains.kotlin:kotlin-maven-serialization:pom:${kotlin.version} in central (https://repo.maven.apache.org/maven2)
I assume this is the offending block:
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-serialization</artifactId>
<version>${kotlin.version}</version>
</dependency>
Wizard: WebSocket feature adds redundant qualifier name
Remove README From generated projects
With a barebones Ktor project as generated by the Wizard, the stubbed-out code snippets in the README.md cause IntelliJ to believe we're committing very faulty software when importing our project into Git (and, likely, other VCS). IJ shows 50+ errors (not warnings).
This looks very scary, and makes it feel like something is broken with the setup even when there isn't. Please either annotate the README with some kind of flag to let IntelliJ know not to check all the code fences, or otherwise exclude the README file from code analysis.
Tested on:
IntelliJ IDEA 2021.1 Beta (Ultimate Edition)
Build #IU-211.6305.21, built on March 4, 2021
IntelliJ IDEA EAP User
Expiration date: April 4, 2021
Runtime version: 11.0.10+9-b1341.18 x86_64
VM: Dynamic Code Evolution 64-Bit Server VM by JetBrains s.r.o.
macOS 10.14.6
GC: G1 Young Generation, G1 Old Generation
Memory: 4096M
Cores: 8
Registry: ide.images.show.chessboard=true
Non-Bundled Plugins: com.jetbrains.darkPurpleTheme (1.2), org.nik.presentation-assistant (1.0.9), wu.seal.tool.jsontokotlin (3.6.1), org.asciidoctor.intellij.asciidoc (0.32.26), stardust (1.5.2105), com.jetbrains.intellij.api.watcher (6.46.0), intellij.ktor (1.5.1-eap-1)
Kotlin: 211-1.4.21-release-IJ6305.1
Wizard: "Static Content" feature generates invalid index.html
The feature generates the following code in resources/static/index.html
:
<html>
<head>
</head>
<body>
<h1>Hello Ktor!<h1>
</body>
</html>
As IntelliJ IDEA notes correctly, the h1
tag is not being closed properly. Should be </h1>
.
Wizard: Gradle Groovy project with Kotlinx Serialization uses inconsistent quotes
plugins {
id 'application'
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
id "org.jetbrains.kotlin.plugin.serialization" version "1.4.10" // <== these should probably also be single quotes – no need to switch.
}
Plugin does not generate a route for CSS DSL
Wizard: Unused import directive in build.gradle.kts
import org.jetbrains.kotlin.gradle.dsl.Coroutines
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
Quick action to generate MockEngine tests if currently in a test module
Testing in IDE: Action to generate tests for given Ktor routes
Wizard: Basic run configuration is not always created
If I create a project with ContentNegotiation, Gson, and Routing, I don't get a run configuration automatically configured. I need to press the green gutter play icon, or run the run
Gradle task.
Adding a bunch more features seems to sometimes include a default "ApplicationKt" run configuration.
Wizard: Selecting MAVEN adds multiple Bintray references to pom.xml
For some reason it also add numerous entries
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>ktor</id>
<url>https://kotlin.bintray.com/ktor</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
Outdated Kotlin version in Ktor plugin
Ktor plugin generates a project with Kotlin 1.4.10
, but the latest version is 1.4.31
Wizard: Markdown Preview throws exceptions
Occurred 10 times in the last 5 or so minutes, just doing basic scrolling and picking and choosing of features.
java.lang.NoSuchMethodError: 'void org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanel.render()'
at io.ktor.initializr.intellij.features.KtorFeatureDetailsPageComponent$showKtorFeature$3$1.run(KtorFeatureDetailsPageComponent.kt:263)
at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:776)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:746)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:969)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:839)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:449)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:808)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$9(IdeEventQueue.java:448)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:781)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:502)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
IntelliJ IDEA 2021.1 Beta (Ultimate Edition)
Build #IU-211.6305.21, built on March 4, 2021
IntelliJ IDEA EAP User
Expiration date: April 4, 2021
Runtime version: 11.0.10+9-b1341.18 x86_64
VM: Dynamic Code Evolution 64-Bit Server VM by JetBrains s.r.o.
macOS 10.14.6
GC: G1 Young Generation, G1 Old Generation
Memory: 4096M
Cores: 8
Registry: ide.images.show.chessboard=true
Non-Bundled Plugins: com.jetbrains.darkPurpleTheme (1.2), org.nik.presentation-assistant (1.0.9), wu.seal.tool.jsontokotlin (3.6.1), org.asciidoctor.intellij.asciidoc (0.32.26), stardust (1.5.2105), com.jetbrains.intellij.api.watcher (6.46.0), intellij.ktor (1.5.1-eap-1)
Kotlin: 211-1.4.21-release-IJ6305.1
Samples
ktor-samples/client-mpp Android Studio doesn't see imports in iosMain
In ktor-samples/client-mpp
Android Studio doesn't see imported dependencies in the iosMain
sourceSet:
MacOS 11.1
Android Studio 4.1.1
XCode 12.3
ktor-samples/client-mpp doesn't build client-mpp-ios in Android Studio
Error message when trying to build the project
iOS configuration of ktor-samples/client-mpp
in Android Studio:
/Users/lukaszkalnik/projects/learning/ktor-samples/client-mpp/client-mpp-ios/ViewController.swift:3:8: error: no such module 'client_mpp'
import client_mpp
^
MacOS 11.1
Android Studio 4.1.1
XCode 12.3
Android Studio logs attached.
Server
Serialization for sealed classes gives different results when used through Ktor
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1743
Ktor Version and Engine Used (client or server and name)
1.32, server
Describe the bug
Sealed classes are stringified to differently when using kotlinx.serialization through Ktor content negotiation
To Reproduce
Steps to reproduce the behavior:
- Create class with list:
@Serializable
class SealedList(val list: List<Test>)
@Serializable
sealed class Test {
@Serializable
class A() : Test()
@Serializable
class B() : Test()
}
- Setup contentNegotiation:
fun Application.installContentNegotiation() {
install(ContentNegotiation) {
json(json = Json(DefaultJsonConfiguration.copy(encodeDefaults = false, prettyPrint = true)))
}
}
- Setup routing:
get("testing") {
val message = SealedList(listOf(Test.A(), Test.B()))
val json = Json(JsonConfiguration.Default.copy(prettyPrint = true))
println(json.stringify(SealedList.serializer(), message))
call.respond(message)
}
- Test it
startTestServer {
val call = handleRequest(HttpMethod.Get,"testing") {
jsonAcceptType()
}
println(call.response.content)
}
Expected behavior
Json created "by hand" should be equal to this created by Ktor. Results:
- By hand:
{
"list": [
{
"type": "navigationData.server.router.Test.A"
},
{
"type": "navigationData.server.router.Test.B"
}
]
}
2 By ktor:
{
"list": [
["navigationData.server.router.Test.A", {
}
],
["navigationData.server.router.Test.B", {
}
]
]
}
Maybe I didn't understand something, or Ktor needs more data, but I couldn't find it in documentation:
https://ktor.io/servers/features/content-negotiation/serialization-converter.html
httpMethod is not affected by X-Http-Method-Override (in opposite to docs)
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1825
Ktor Version and Engine Used (client or server and name)
ktor 1.3.2 (Netty)
Describe the bug
https://api.ktor.io/1.3.2/io.ktor.request/http-method.html states:
Returns request HTTP method possibly overridden via header X-Http-Method-Override
However it doesn't work. httpMethod() returns POST while headers[" X-Http-Method-Override"] returns GET.
To Reproduce
Execute POST request with X-Http-Method-Override: GET header.
Expected behavior
Returns 'GET' or documentation is not correct.
Screenshots
Is that correct that routing API also ignores X-Http-Method-Override or it's a bug ?
Mandatory Path Segment parameter can be empty, if no explicit route with trailing / is defined
The route handler for /res/{parameter}
will now also match for /res/
. Before /res
and /res/
would match the "getAll" resource.
This means any "trailing" path segment parameter is implicitly treated as optional. So all handlers need to check if the parameter is empty string and then either
- redirect to
/res
- copy&paste the handler of
/res
to the empty string code branch - give a
404
or400
I guess "fixing" the behavior of /res
== /res/
in 1.5 is opinionated, so there should be a real option/flag to ignore trailing slashes.
Regression starting from 1.5 ( https://youtrack.jetbrains.com/issue/KTOR-372 ), works as expected in 1.4.3
The docs seem to be partially broken now for mandatory path segment parameters.
{param} matches a path segment and captures it as a parameter named param. This path segment is mandatory, but you can make it optional by adding a question mark: {param?}.
Allow to configure features for subroutes
Imagine that you have feature X and install it. Now you want to set up the feature config for the specific subroute.
In the current state, there is no generic way to do this. Users can change the config of the feature after install (ex, Authentication
feature) but only globally, and to do this, they need to understand how internal systems work.
Also, implementing a new DSL method in routing can break a priority of routes (see https://youtrack.jetbrains.com/issue/KTOR-613)
Suggested solution:
API
Not all the features can be dynamically configured in routing. For example, feature that starts a background process on install should not know anything about routing. To separate them in API we will create the new interface:
public interface RoutingScopedFeature<in TPipeline : Pipeline<*, ApplicationCall>, TConfiguration : Any, TFeature : Any> :
ApplicationFeature<TPipeline, TConfiguration, TFeature> {
...
/**
* Feature installation script
*/
public fun install(pipeline: TPipeline): TFeature
}
Note that the install
function doesn't have a configuration block as a parameter on the installation, because it can be modified in routing.
The new overload for the install
function will be introduced
public fun <Config, Feature : RoutingScopedFeature<*, Config, *>> Route.install(
feature: Feature,
configBuilder: Config.() -> Unit
)
To receive actual configuration inside the feature, the user will need to use the configurationBlock
property inside the interceptor.
pipeline.intercept(phase) {
val configuration = Configuration().apply(configurationBlock)
...
}
Usage example (Authentication
feature):
install(Authentication) {
form { validate { c -> UserIdPrincipal(c.name) } }
}
routing {
route(...) {
install(Authentication) {
form("2") {
validate { it.name.takeIf { it == "bbb" }?.let { UserIdPrincipal(it) } }
}
}
get("/") { }
}
}
Implementation
To check full (but draft) implementation with tests and migration of the Authentication
feature, please check the rsinukov/feature-config
branch.
Migration
We can gradually add dynamic configuration functionality to existing features by changing their base interface from ApplicationFeature
to DynamicConfigFeature
. The migration of each feature will require minimal efforts (can be seen in the rsinukov/feature-config
branch for the Authentication
feature).
Unhandled get freezes with `CIO` server
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1962
Ktor Version and Engine Used (client or server and name)
Server CIO
, 1.3.2
Describe the bug
If we don't call call.respond
the server freezes and holds the connection with CIO
server(without any message)
The Netty
treats this sample as correct response and respond with status code Unauthorized
To Reproduce
Make the request with CIO
server:
get("unauthorized") {
call.response.status(HttpStatusCode.Unauthorized)
call.response.header(HttpHeaders.WWWAuthenticate, "Basic realm=\"TestServer\", charset=UTF-8")
}
Expected behavior
Respond with 5xx Internal server error
or a correct response in that case
uninstallFeature function not working properly
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1887
Ktor Version and Engine Used (client or server and name)
1.3.2, server/client, CIO engine
Describe the bug
I don't see uninstallFeature function, un-intercepting the pipeline.
Tested with DefaultHeaders and CORS.
To Reproduce
Server code,
fun main() {
embeddedServer(CIO, host = "localhost", port = 8080) {
install(CORS) {
host("AHost.com")
}
install(DefaultHeaders)
uninstallAllFeatures() // This is where uninstallFeature will be called
routing {
get {
call.respond("Hello")
}
}
}.start(wait = true)
}
A client code (typically a simple get request),
fun main() = runBlocking {
val client = HttpClient(CIO)
client.get<HttpResponse>(port = 8080) {
header(HttpHeaders.Accept, ContentType.Application.Json)
header(HttpHeaders.Origin, "http://AHost.com")
}.run {
println(headers)
}
}
Result,
Headers [Vary=[Origin], Access-Control-Allow-Origin=[http://AHost.com], Date=[Fri, 22 May 2020 00:21:53 GMT], Server=[ktor-server-core/1.3.2 ktor-server-core/1.3.2], Content-Length=[5], Content-Type=[text/plain; charset=UTF-8]]
Expected behavior
Features actually be uninstalled. Meaning they won't intercept the pipeline anymore.
The route "/" ends up as "///" in Route.toString()
This test passes -- note the ///
@Test
internal fun routeSlashes() {
withTestApplication({
routing {
get("/") {
call.respond("OK: ${(call as RoutingApplicationCall).route}")
}
}
}) {
with(handleRequest(HttpMethod.Get, "/")) {
assertEquals("OK: ///(method:GET)", response.content)
}
}
}
If the route is declared with get("")
, Route.toString()
results in /(method:GET)
, which is what I would expect in both cases.
JWK Public Key of type "EC"
Ktor supports the use of ES256, ES384, and ES512 as JWK algorithm, but the used library jwks-rsa-java doesn't support the key type EC.
This results in an exception InvalidPublicKeyException, when Ktor tries to create the public key. Is there an option/workaround to use EC encryption?
CORS
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1656
Ktor Version and Engine Used (client or server and name)
1.3.0
Describe the bug
CORS can't pass on some none standard orgin like chrome-extension://mfgdmpfihlmdekaclngibpjhdebndhdj
To Reproduce
open some new page in chrome or other browser,just call any api,CORS throw would error(brower side).
Expected behavior
should be AnyOrigin
pass by CORS, when we config it as host("*")
or anyHost()
Unexpected exception when using Session feature: "Using blocking primitives on this dispatcher is not allowed"
kotlin: 1.4.20
kotlinx.coroutines: 1.4.2
Sessions feature is configured in a rather standard manner:
install(Sessions) {
cookie<UserIdPrincipal>(
Cookies.SESSION_ID,
storage = directorySessionStorage(sessionStorageRootDir)
) {
cookie.path = "/"
cookie.httpOnly = true
cookie.extensions["SameSite"] = "Lax"
}
}
When it is called during a pipeline execution, an unexpected exception is thrown.
java.lang.IllegalStateException: Using blocking primitives on this dispatcher is not allowed. Consider using async channel instead or use blocking primitives in withContext(Dispatchers.IO) instead.
at io.ktor.utils.io.jvm.javaio.BlockingKt.ensureParkingAllowed(Blocking.kt:302) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.access$ensureParkingAllowed(Blocking.kt:1) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.InputAdapter.<init>(Blocking.kt:30) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream(Blocking.kt:20) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream$default(Blocking.kt:20) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1$1.invokeSuspend(CacheStorage.kt:15) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1$1.invoke(CacheStorage.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.DirectoryStorage.read(DirectoryStorage.kt:46) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1.invokeSuspend(CacheStorage.kt:15) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1.invoke(CacheStorage.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache$container$1.invokeSuspend(Cache.kt:82) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache$container$1.invoke(Cache.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.BaseCache$getOrCompute$2$1.invokeSuspend(Cache.kt:38) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
(Coroutine boundary) ~[?:?]
at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:101) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at io.ktor.sessions.ReferenceCache.getOrCompute$suspendImpl(Cache.kt:86) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage.read(CacheStorage.kt:20) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.SessionTrackerById.load(SessionTrackerById.kt:34) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.SessionsKt.receiveSessionData(Sessions.kt:215) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.Sessions$Feature$install$1.invokeSuspend(Sessions.kt:259) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:110) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:102) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:101) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:142) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.CallLogging$Feature$install$2.invokeSuspend(CallLogging.kt:139) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:118) ~[ktor-server-host-common-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:118) ~[ktor-server-netty-jvm-1.4.3.jar:1.4.3]
Caused by: java.lang.IllegalStateException: Using blocking primitives on this dispatcher is not allowed. Consider using async channel instead or use blocking primitives in withContext(Dispatchers.IO) instead.
at io.ktor.utils.io.jvm.javaio.BlockingKt.ensureParkingAllowed(Blocking.kt:302) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.access$ensureParkingAllowed(Blocking.kt:1) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.InputAdapter.<init>(Blocking.kt:30) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream(Blocking.kt:20) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream$default(Blocking.kt:20) ~[ktor-io-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1$1.invokeSuspend(CacheStorage.kt:15) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1$1.invoke(CacheStorage.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.DirectoryStorage.read(DirectoryStorage.kt:46) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1.invokeSuspend(CacheStorage.kt:15) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage$referenceCache$1.invoke(CacheStorage.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache$container$1.invokeSuspend(Cache.kt:82) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache$container$1.invoke(Cache.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.BaseCache$getOrCompute$2$1.invokeSuspend(Cache.kt:38) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[kotlin-stdlib-1.4.20.jar:1.4.20-release-308 (1.4.20)]
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:342) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:27) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:91) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt.async(Unknown Source) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:84) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt.async$default(Unknown Source) ~[kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at io.ktor.sessions.BaseCache$getOrCompute$2.apply(Cache.kt:37) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.BaseCache$getOrCompute$2.apply(Cache.kt:31) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660) ~[?:1.8.0_251]
at io.ktor.sessions.BaseCache.getOrCompute(Cache.kt:36) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache.getOrCompute$suspendImpl(Cache.kt:86) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.ReferenceCache.getOrCompute(Cache.kt) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.BaseTimeoutCache.getOrCompute(Cache.kt:163) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.CacheStorage.read(CacheStorage.kt:20) ~[ktor-server-sessions-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.SessionTrackerById.load(SessionTrackerById.kt:34) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.SessionsKt.receiveSessionData(Sessions.kt:215) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.Sessions$Feature$install$1.invokeSuspend(Sessions.kt:63) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.sessions.Sessions$Feature$install$1.invoke(Sessions.kt) ~[ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:110) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:102) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$interceptCall$2.invoke(StatusPages.kt) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:194) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:101) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:142) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.StatusPages$Feature$install$2.invoke(StatusPages.kt) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.CallLogging$Feature$install$2.invokeSuspend(CallLogging.kt:139) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.features.CallLogging$Feature$install$2.invoke(CallLogging.kt) [ktor-server-core-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:118) [ktor-server-host-common-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt) [ktor-server-host-common-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77) [ktor-utils-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:118) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:55) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:111) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at kotlinx.coroutines.BuildersKt.launch(Unknown Source) [kotlinx-coroutines-core-jvm-1.4.2.jar:?]
at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:43) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:34) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [netty-transport-4.1.54.Final.jar:4.1.54.Final]
at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:61) [netty-transport-4.1.54.Final.jar:4.1.54.Final]
at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:370) [netty-transport-4.1.54.Final.jar:4.1.54.Final]
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500) [netty-transport-4.1.54.Final.jar:4.1.54.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at io.ktor.server.netty.EventLoopGroupProxy$Companion$create$factory$1$1.run(NettyApplicationEngine.kt:216) [ktor-server-netty-jvm-1.4.3.jar:1.4.3]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-common-4.1.54.Final.jar:4.1.54.Final]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_251]
CIO server always start on "0.0.0.0" - does not respect "connector" configuration
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1959
CIO Server ktor-server-cio:1.3.2-1.4-M2
Describe the bug
CIO server always start on "0.0.0.0" - does not respect "connector" configuration
To Reproduce
Steps to reproduce the behavior:
embeddedServer(CIO, applicationEngineEnvironment {
module { ... }
connector {
host = "192.168.0.1"
port = 8080
}
})
Server will start on "0.0.0.0:8080" instead of "192.168.0.1:8080"
Expected behavior
Server respect connector configuration.
server/netty: IllegalReferenceCountException
I got this random error out of nowhere. I'm not even using the Ktor server. It's just random bots on the web making HTTP calls to it.
I have no idea what request, call or response it was.
2021-01-19 21:21:41.833 E Unhandled exception caught for CoroutineName(call-handler)
io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
at io.netty.util.internal.ReferenceCountUpdater.toLiveRealRefCnt(ReferenceCountUpdater.java:74)
at io.netty.util.internal.ReferenceCountUpdater.release(ReferenceCountUpdater.java:138)
at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:100)
at io.netty.handler.codec.http.DefaultFullHttpRequest.release(DefaultFullHttpRequest.java:103)
at io.netty.util.ReferenceCountUtil.release(ReferenceCountUtil.java:88)
at io.ktor.server.netty.NettyApplicationCall.finishComplete(NettyApplicationCall.kt:53)
at io.ktor.server.netty.NettyApplicationCall.finishSuspend(NettyApplicationCall.kt:46)
at io.ktor.server.netty.NettyApplicationCall$finishSuspend$1.invokeSuspend(NettyApplicationCall.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.ktor.server.netty.EventLoopGroupProxy$Companion$create$factory$1$1.run(NettyApplicationEngine.kt:227)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:832)
Installed features: CallLogging, Compression, DefaultHeaders, CORS, XForwardedHeaderSupport, WebSockets (unused)
Routes: 1x get
, 1x post
(both for GraphQL queries)
Enabled-by-default development mode breaks reflection by overriding classloader
I use reflection via Reflections for discovering implementations of services inside my module.
However, Ktor's development mode uses an OverridingClassLoader
, which seems to break things.
The following snippet...
val matchers = ref.getSubTypesOf(UrlMatcher::class.java).map { cl ->
val new = cl.getDeclaredConstructor().newInstance()
new
}
...now tries to cast a class across class loader boundaries, which is not allowed on the JVM:
Caused by: java.lang.ClassCastException: class io.sebi.urldecoder.urlmatcher.ArdMatcher cannot be cast to class io.sebi.urlmatcher.UrlMatcher (io.sebi.urldecoder.urlmatcher.ArdMatcher is in unnamed module of loader 'app'; io.sebi.urlmatcher.UrlMatcher is in unnamed module of loader io.ktor.server.engine.OverridingClassLoader$ChildURLClassLoader @37f1104d)
This was a super confusing issue for me. I ended up solving it by turning off the development mode in my application.conf
:
ktor {
development = false
}
HTTP 200 has no "OK" Status Line
Problem Description
For Ktor version 1.5.2, when I return a response object (using the Jackson ContentNegotiation strategy), the response header begins with HTTP/1.1 200
, e.g.:
HTTP/1.1 200
Transfer-Encoding: chunked
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
This conflicts with some middleware/PaaS/etc., in my case Heroku, which expect HTTP/1.1 200 OK
-- specifically that OK
provided in the status line. (check their documentation on this matter at this link.) I'm not sure this is a bug from your perspective, and if not, then I would instead ask for a feature which allows me to specify the header format in a template string.
Notes
This occurs for all of the following scenarios:
call.response.status(OK)
call.respond(it)
call.respond(OK, it)
call.respond(it)
`defaultResource` outside of static block no longer works in 1.5.2
This is a regression in 1.5.2 (from 1.5.0).
The following snippet inside a top-level routing
block works fine in 1.5.0:
static("/") {
resources("frontend")
}
defaultResource("frontend/index.html")
i.e. I can access localhost:9000 and get served the contents of frontend/index.html
.
In 1.5.2, this throws a 404 Not Found error.
I had to rewrite the snippet as follows to get the desired behavior again:
static("/") {
resources("frontend")
defaultResource("frontend/index.html")
}
Routing issue with "/" route breaking-change
I just updated my Ktor based projet to 1.5.2 from 1.4.3 and realized some of my routes stopped working.
To explain, I have a structured routing and a base controller to repeat basic CRUD route for several of my business objects. The code is as follow :
private fun baseRoutes(route: Route) {
route.get("/") {
all(call.parameters["count"]?.toInt(), call.parameters["offset"]?.toLong(), true).send(call)
}
route.get("/{id}") {
get(call.parameters["id"]?.toLongOrNull()).send(call)
}
route.post("/") {
create(call.receiveData()).send(call)
}
route.patch("/{id}") {
update(call.parameters["id"]?.toLongOrNull(), call.receiveData()).send(call)
}
route.delete("/{id}") {
delete(call.parameters["id"]?.toLong()).send(call)
}
}
The problem is that with the new version of Ktor, route.get("/")
forces the trailing slash at the end of the request, /v1/places
is not equivalent to /v1/places/
anymore and /v1/places
leads to a 404 error.
To fix that, I now use route.get
instead of route.get("/")
but I do not know if that is an issue or not.
Digest authentication: cannot successfully pass authentication using curl or web browser
To reproduce run the server using the following code snippet:
fun main() {
embeddedServer(Netty, port = 8081) {
install(Authentication) {
digest("digest") {
val password = "123"
realm = "testrealm@host.com"
digestProvider { name, realm ->
"$name:$realm:$password".toByteArray()
}
}
}
routing {
authenticate("digest") {
get("/digest") {
call.respondText("ok")
}
}
}
}.start(wait = true)
}
Send the following HTTP request using curl
:
curl -v 'http://localhost:8081/digest' --digest -u guest:123
I expect successfully pass authentication and get a response with a status code 200 but instead, I get the following response:
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8081 (#0)
* Server auth using Digest with user 'guest'
> GET /digest HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< WWW-Authenticate: Digest realm="testrealm@host.com", nonce="43f48979ca35e2a5", algorithm="MD5"
< Content-Length: 0
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8081/digest'
* Found bundle for host localhost: 0x7fbe62518750 [can pipeline]
* Could pipeline, but not asked to!
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (::1) port 8081 (#0)
* Server auth using Digest with user 'guest'
> GET /digest HTTP/1.1
> Host: localhost:8081
> Authorization: Digest username="guest", realm="testrealm@host.com", nonce="43f48979ca35e2a5", uri="/digest", response="ee8c56f444996c77cd1e5f7570035372", algorithm="MD5"
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
* Authentication problem. Ignoring this.
< WWW-Authenticate: Digest realm="testrealm@host.com", nonce="76d13a440ec99b1c", algorithm="MD5"
< Content-Length: 0
<
* Connection #0 to host localhost left intact
* Closing connection 0
Test Infrastructure
ktor request coroutines no longer resolve synchronously in tests on JVM after 1.5.1
In our project we have API clients with Reaktive interface (using Single, Completable) that use ktor, and hence coroutines under the hood.
In tests, we use Reaktive's test helpers so that our tests look like this:
apiClient.doStuff()
.test()
.assertCompleted()
// other checks follow, like checking calls on dependencies' mocks etc.
After trying to bump ktor from 1.4.* to 1.5.3 (we later identified that the 1.5.1 is enough to trigger the issue), these tests started failing but only on JVM. Adding a sleep(1000)
call between .test()
and .assertCompleted()
makes the test green again, another potential workaround is using Reaktive's blockingGet
instead, but ideally we'd like to keep such tests sync.
Under the hood, our tests rely on MockEngine
from ktor. Dispatchers.Unconfined
is used for everything in test setup.
Other
`static` route definitions depend on order, overwrite following route definitions
Consider the following snippet:
routing {
route("/api") {
get("example") {
call.respond(mapOf("hello" to "world"))
}
}
// Static feature. Try to access `/static/ktor_logo.svg`
static("/static") {
resources("static")
}
static("/") {
resources("frontend")
defaultResource("frontend/index.html")
}
}
accessing /api/example gives a 200 OK GET.
Now, consider the same snippet, slightly reordered:
routing {
// Static feature. Try to access `/static/ktor_logo.svg`
static("/static") {
resources("static")
}
static("/") {
resources("frontend")
defaultResource("frontend/index.html")
}
route("/api") {
get("example") {
call.respond(mapOf("hello" to "world"))
}
}
}
Accessing /api/example now gets me a 404 Not Found. Upon cursory review, I haven't seen anything about routes being order-dependent in the docs, only some tangentially related discussion on StackOverflow without an authoritative answer.
Upgrading from 1.4.3 to 1.5.2 introduced a routing precedence
This is a cross-post from channel #ktor in Slack. I was asked by Rustam to file a issue here.
When I upgraded from 1.4.3 to 1.5.2, all of my routes serving GET requests yielded a HTTP error 404 on accessing them. To me it seems, that in 1.5.2 the order of registering routes has become significant, while in 1.4.3 it didn't matter.
In the example below, context "/groot" yields 404.
If staticResources() is called after groot(), route "/groot" becomes available as expected.
class SomeApp {
fun Application.main(testing: Boolean = false) {
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
routing {
underpants() // serves context "/underpants"
staticResources() // serves root context "/"
groot() // serves context "/groot"
}
}
}
// Route for static context
fun Routing.staticResources() {
// Serves frontend resources embedded in application jar
static("/") {
defaultResource("index.html", "web-resource")
resources("web-resource")
}
}
fun Routing.underpants(
) {
route("/underpants") {
get {
val map: HashMap<Int, String> = hashMapOf(1 to "Collect underpants.", 2 to "?", 3 to "Profit!")
call.respond(map)
}
}
}
fun Routing.groot(
) {
route("/groot") {
get {
val map: HashMap<Int, String> = hashMapOf(1 to "I", 2 to "am", 3 to "groot!")
call.respond(map)
}
}
}
Start ktor server on random port
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/909
Ktor Version
1.1.2
Ktor Engine Used(client or server and name)
Netty server
JVM Version, Operating System and Relevant Context
JDK 11
Feedback
Being able to get the local port when starting ktor on a random port:
embeddedServer(Netty, port = 0)
The information is available in case of netty on channel, but I can not get this information of NettyApplicationEngine
itself
Adding Ktor to Kotlin Multiplatform Mobile Results in Unresolved Reference Http
I can't get Ktor to work in a KMM project, I just get a Unresolved reference: HttpClient error when trying to reference any Ktor classes. If I try to manually add the ktor import it says Unresolved reference io. Other dependencies like Kermit resolve fine, it seems to just be Ktor with the issue. Here's my simple steps to reproduce:
In Android Studio (I have tried both 4.1.3 and 4.2 Beta 6), I go File -> New -> KMM Application.
In the shared module build.gradle.kts I add the dependencies for the ktor client:
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:1.5.2")
}
}
In the Greeting class in commonMain I try creating an HttpClient like it says in the Ktor documents https://kotlinlang.org/docs/mobile/use-ktor-for-networking.html#select-an-engine:
class Greeting {
val httpClient: HttpClient = HttpClient()
fun greeting(): String {
return "Hello, ${Platform().platform}!"
}
}
I get the Unresolved reference: HttpClient. The ktor imports don't work.
Things I have tried:
Adding the Android and iOS client dependencies as well.
Adding enableFeaturePreview("GRADLE_METADATA") to settings.gradle.kts as suggested here: How to fix 'Unresolved reference: HttpClient' with ktor-client-core targeting linuxX64
Cleaning, syncing with gradle, invalidate cashes and restart, closing AS and re-opening, building the project.
I really have no idea why this doesn't work, it seems like it the simplest possible setup. Here's my build.gradle files and settings file (which were auto generated from new KMM project wizard)
shared module build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
id("com.android.library")
}
kotlin {
android()
ios {
binaries {
framework {
baseName = "shared"
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:1.5.2")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation("com.google.android.material:material:1.2.1")
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13")
}
}
val iosMain by getting
val iosTest by getting
}
}
android {
compileSdkVersion(29)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(24)
targetSdkVersion(29)
}
}
val packForXcode by tasks.creating(Sync::class) {
group = "build"
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64"
val framework = kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
val targetDir = File(buildDir, "xcode-frameworks")
from({ framework.outputDirectory })
into(targetDir)
}
tasks.getByName("build").dependsOn(packForXcode)
Project build.gradle.kts
buildscript {
repositories {
gradlePluginPortal()
jcenter()
google()
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10")
classpath("com.android.tools.build:gradle:4.0.1")
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
settings.gradle.kts
pluginManagement {
repositories {
google()
jcenter()
gradlePluginPortal()
mavenCentral()
}
}
rootProject.name = "core"
include(":androidApp")
include(":shared")
Here is my stackoverflow post https://stackoverflow.com/questions/66848774/adding-ktor-to-kotlin-multiplatform-mobile-results-in-unresolved-reference-http and another from another user that seems to be experiencing the same problem https://stackoverflow.com/questions/66630659/cannot-import-io-ktor-to-common-module-of-kmm-in-android-studio
Test a POST with MultiPart using TestApplicationEngine does not success or fail
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1936
Ktor 1.3.2, TestApplicationEngine
When running a test that sends post with multipart it runs forever and never stops.
No problem when server really runs. It's just the test.
To Reproduce
Server:
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
}
routing {
post("/") {
val mp = try {
call.receiveMultipart()
} catch (e: Exception) {
call.application.log.error("Getting multipart error"")
null
}
if (mp == null) call.respond(HttpStatusCode.BadRequest, "Null value")
else call.respond(HttpStatusCode.OK,"OK")
}
}
}
Test:
internal class ApplicationKtTest {
private val boundary = "***bbb***"
private val sysTempDir = System.getProperty("java.io.tmpdir") ?: "/tmp"
private val multipart = listOf(PartData.FileItem({ byteArrayOf(1, 2, 3).inputStream().asInput() }, {}, headersOf(
HttpHeaders.ContentDisposition,
ContentDisposition.File
.withParameter(ContentDisposition.Parameters.Name, "file")
.withParameter(ContentDisposition.Parameters.FileName, "test.jpg")
.toString()
)))
@Test
fun testUploadApplication() = testApp {
handlePost("/", boundary, multipart).apply {
println("============= RESPONSE ====================")
println(response.content)
println("=================================")
}
}
}
private fun TestApplicationEngine.handlePost(uri: String,
boundary: String,
multipart: List<PartData.FileItem>,
setup: TestApplicationRequest.() -> Unit = {}
): TestApplicationCall {
return handleRequest(method = HttpMethod.Post, uri = uri) {
addHeader(HttpHeaders.ContentType,
ContentType.MultiPart.FormData.withParameter("boundary", boundary).toString()
)
setBody(boundary, multipart)
setup()
}
}
private fun testApp(callback: TestApplicationEngine.() -> Unit): Unit {
withTestApplication({ module(true) }, callback)
}
The test runs forever and gives no result. if addHeader
and setBody
are commented there is a response.
Expected behavior
The test should print OK
Repository
If you just want to clone a test repository.
ApplicationConfig: how to iterate over keys and values of config
I would like to support a generic config for my application.
Kindly consider this example.
I need to create java.util.Properties
object to instantiate KafkaProducer
API doc
http://kafka.apache.org/21/javadoc/index.html?org/apache/kafka/clients/producer/KafkaProducer.html
There could be common configs and producer-specific configs.
to get an "effective" config I need to merge two maps - producer config and common properties.
I couldn't find the way how I get all key,value pairs from ApplicationConfig
application.conf
example
kafka {
bootstrap-servers = ["my.confluent.cloud:9092"]
properties {
ssl.endpoint.identification.algorithm = https
sasl.mechanism = PLAIN
request.timeout.ms = 20000
retry.backoff.ms = 500
sasl.jaas.config = "org.apache.kafka.common.security.plain.PlainLoginModule required username=${?USER} password=${?SECRET};"
security.protocol = SASL_SSL
}
consumer {
group.id = ktor-app
key.deserializer = org.apache.kafka.common.serialization.StringDeserializer
value.deserializer = org.apache.kafka.common.serialization.StringDeserializer
}
producer {
key.serializer = org.apache.kafka.common.serialization.StringSerializer
value.serializer = org.apache.kafka.common.serialization.StringSerializer
}
Kindly advise
Native does not build on linux machine
Need to install missing dependencies:
libncurses5 libncursesw5 libtinfo5
Fix Dokka building for master
The list allCompileKotlinTasks
was removed due to refactoring
Fix existing samples
The project doesn't compile
developmentMode is on by default in tests
Hi,
First of all, thank you for creating and maintaining ktor, we enjoy using it a lot! The issue we notice that after v1.5.2, developmentMode value is set as true by default in tests (more precisely it is on when assertions are enabled) -> https://github.com/ktorio/ktor/blob/de742d1adfbbb994bedda198e6739c724e62a974/ktor-utils/jvm/src/io/ktor/util/PlatformUtils.kt#L15-L16
This issue causes our test cases to fail since, in developmentMode, the application is started in a different classloader which leads to some problems with our test setup. We had to override the `io.ktor.development` value to solve it for now.
We believe that this behavior is unintended and a bug. Could you please take a look and provide a better developmentMode resolving method?
Add support for Velocity Tools
Subsystem
Server ktor-features/ktor-velocity
Motivation
Velocity templating is not very useful without being able to use the standard Velocity Tools.
Solution
This PR adds a VelocityTool feature. It is used as follow:
install(VelocityTools) {
engine {
// whatever engine configuration you need
setProperty("resource.loader", "string")
addProperty("resource.loader.string.name", "myRepo")
addProperty("resource.loader.string.class", StringResourceLoader::class.java.name)
addProperty("resource.loader.string.repository.name", "myRepo")
}
addDefaultTools() // add a default tool
tool("foo", MyCustomTool::class.java) // add a custom tool
}
InsufficientSpaceException trying to build ByteReadPacket
Periodically (not every time) during running tests in https://github.com/rsocket/rsocket-kotlin using ktor-io exception appeared trying to build ByteReadPacket using buildPacket {}
Exception in thread "DefaultDispatcher-worker-9 @coroutine#122840" io.ktor.utils.io.core.InsufficientSpaceException: Not enough free space to write long integer of 8 bytes, available 7 bytes.
at io.ktor.utils.io.core.BufferPrimitivesKt.writeLong(BufferPrimitives.kt:1081)
at io.ktor.utils.io.core.OutputPrimitivesKt.writeLongFallback(OutputPrimitives.kt:50)
at io.ktor.utils.io.core.OutputPrimitivesKt.writeLong(OutputPrimitives.kt:45)
at io.rsocket.kotlin.frame.KeepAliveFrame.writeSelf(KeepAliveFrame.kt:33)
at io.rsocket.kotlin.frame.Frame.toPacket(Frame.kt:36)
at io.rsocket.kotlin.internal.RSocketState$start$3.invokeSuspend(RSocketState.kt:143)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.selects.SelectBuilderImpl.resumeWith(Select.kt:306)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Invalid assertion for existence of the key in the key store
CommandLine.kt
requireNotNull(getKey(sslKeyAlias, sslPrivateKeyPassword.toCharArray()) == null)
is always successful
Incorrect grammar in exception messages
Multiple instance of "has been" in exception messages such as
Request timeout has been expired
Fix testUnsafeHeaders for Js platform
Fix tests failing due to compiler error. The following error is in references
Fix flaky ClientSocketTest.testSelfConnect[jvm]
https://ktor.beta.teamcity.com/buildConfiguration/Ktor_KtorMatrixCoreMacOSJava11/21952
https://ktor.beta.teamcity.com/buildConfiguration/Ktor_KtorMatrixCoreWindowsJava11/21949
https://ktor.beta.teamcity.com/buildConfiguration/Ktor_KtorMatrixCoreMacOSJava8/21953
https://ktor.beta.teamcity.com/buildConfiguration/Ktor_KtorMatrixCoreWindowsJava8/21951
Default Headers feature adds duplicated Server header
The first call.receiveText() hangs until timeout
Ktor Server version
1.4.3, 1.5.2 (both affected)
Feedback
I’ve recently developed a simple Ktor app and organised a hosting for it on Apache Webserver + Tomcat.
The app has a post { … } route used for processing HTTP POST requests.
post("/api/v1/meeting") {
logger.trace { "Before call.receiveText()" }
val text = call.receiveText()
logger.trace { "After call.receiveText()" }
...
}
The route works fine for all requests except the first one (and sometimes 2 or 3). Additional tracing shows that request processing stucks on the line "call.receiveText()", where there is an attempt to read incoming JSON body of HTTP POST.
The request is stuck until network timeout.
Every following POST request with exactly the same content is processed fine. Assuming some lazy loading issue, I put “load-on-startup” in tomcat servlet setting - it didn’t affect the result.
Attaching the logs:
Apache access.log (you can see the timeout at the end)
Apache error.log (no clues)
Tomcat catalina logs (no clues)
Servlet log (only the first tracing message is in the log)
Tried to add ktor {.development=true } into application.conf, it didn't bring additional info.
P.S. the issue is reported following the JB request in https://stackoverflow.com/questions/66437014/ktor-first-call-receivetext-gets-timeout
Broken delimiter occurred
Hi, 1.5.2 broke file ktor uploads. I used Java11 client to upload image in my tests and "Broke delimeter occured" error poped up. 1.5.1 works just fine.
val boundary = BigInteger(256, java.util.Random()).toString().substring(0, 70)
val body = ofMimeMultipartData(paramName, fileContent, fileName, contentType, boundary)
val request = HttpRequest.newBuilder()
.header("Accept-Language", "en")
.header("Authorization", "Bearer ${this.apiKey}")
.header("Content-Type", "multipart/form-data;boundary=$boundary")
.POST(body)
.uri(URI.create(urlStr))
.build();
val client = HttpClient.newHttpClient()
val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
This is the stack trace on the server:
2021-03-09 10:28:03.209 [ERROR] io.ktor.application.Application - Unknown error: Broken delimiter occurred
java.io.IOException: Broken delimiter occurred
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invokeSuspend(Delimited.kt:58)
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invoke(Delimited.kt)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend$suspendImpl(ByteBufferChannel.kt:1822)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend(ByteBufferChannel.kt)
at io.ktor.utils.io.DelimitedKt.skipDelimiterSuspend(Delimited.kt:56)
at io.ktor.utils.io.DelimitedKt.skipDelimiter(Delimited.kt:51)
at io.ktor.http.cio.MultipartKt$parseMultipart$1.invokeSuspend(Multipart.kt:378)
(Coroutine boundary)
at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:101)
at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:142)
at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:82)
at io.ktor.features.CallLogging$Feature$install$2.invokeSuspend(CallLogging.kt:139)
at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:82)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:124)
at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:82)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:123)
Caused by: java.io.IOException: Broken delimiter occurred
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invokeSuspend(Delimited.kt:58)
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invoke(Delimited.kt)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend$suspendImpl(ByteBufferChannel.kt:1822)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend(ByteBufferChannel.kt)
at io.ktor.utils.io.DelimitedKt.skipDelimiterSuspend(Delimited.kt:56)
at io.ktor.utils.io.DelimitedKt.skipDelimiter(Delimited.kt:51)
at io.ktor.http.cio.MultipartKt$parseMultipart$1.invokeSuspend(Multipart.kt:378)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuation.resumeWith(DispatchedContinuation.kt:188)
at io.ktor.utils.io.internal.CancellableReusableContinuation.resumeWith(CancellableReusableContinuation.kt:93)
at io.ktor.utils.io.ByteBufferChannel.resumeWriteOp(ByteBufferChannel.kt:2190)
at io.ktor.utils.io.ByteBufferChannel.bytesRead(ByteBufferChannel.kt:912)
at io.ktor.utils.io.ByteBufferChannel.readAsMuchAsPossible(ByteBufferChannel.kt:508)
at io.ktor.utils.io.ByteBufferChannel.readAvailable$suspendImpl(ByteBufferChannel.kt:703)
at io.ktor.utils.io.ByteBufferChannel.readAvailable(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteBufferChannel.readAvailableSuspend(ByteBufferChannel.kt:747)
at io.ktor.utils.io.ByteBufferChannel$readAvailableSuspend$2.invokeSuspend(ByteBufferChannel.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.ktor.server.netty.EventLoopGroupProxy$Companion$create$factory$1$1.run(NettyApplicationEngine.kt:227)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:834)
Endpoint returns Code 500 and prints Unhandled: GET - /myendpoint, io failed in the console
I am developing a Ktor Web Server on Kotlin Version 1.4.30 and Ktor Version 1.5.2 and im routing an endpoint at the path /myendpoint. The endpoint takes some parameters from the request, and does multiple API Calls with it, I added print lines all over those to see if anyone was returning a faulty result that caused it to fail, didnt seem like it. The endpoint works on average every 10-15 tries, on the other tries I just see [eventLoopGroupProxy-4-3] INFO Application - Unhandled: GET - /myendpoint, io failed in the console. The fact that my print lines didnt seem to hit when debugging my API Calls leads me to believe this might be a Ktor issue. Help would be appreciated
Crash with Firebase Performance in iOS
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1040
Ktor Version
1.1.3
Ktor Engine Used(client or server and name)
Client iOS with default engine
Feedback (Bug)
When adding Firebase Performance to an iOS application, the app crashes when making a request with Ktor.
I've forked the sample mmp project and added the Firebase Performance library. The application crashes due to Firebase Performance. It might be because Firebase intercepts all network requests and it might be incompatible with Ktor.
You can find the sample mmp client crashing with Firebase Performance here https://github.com/eduardbosch/ktor-samples/commit/35bb435b4138ef966eeb9c44cbaf632bfaaeb305
Remember to add the GoogleServices-Info.plist to prevent Firebase to crash when it is initialised.
I hope this can help Ktor! 🎉
CIO Server Engine fails for requests with more than 32 headers
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1095
Ktor Version
1.1.5
Ktor Engine Used(client or server and name)
CIO server
JVM Version, Operating System and Relevant Context
1.8.0_131
Feedback
When using the CIO server engine, requests with more than 32 headers are rejected with error 400 and no further diagnostic.
I used the debugger to further track the cause of this error and found that during header parsing an exception is thrown:
kotlin.NotImplementedError: An operation is not implemented: Implement headers overflow
This is due to the following declaration in file HttpHeaderMap.kt:
package io.ktor.http.cio
import io.ktor.http.cio.internals.*
import io.ktor.util.*
import kotlinx.io.pool.*private const val EXPECTED_HEADERS_QTY = 32
which effectively limits the number of headers to 32.
This error does not appear with the Jetty engine.
Implementation for Fix client bootstrap problems
Release 1.5.2
[iOS] HttpClient MockEngine + KotlinxSerializer giving "No transformation found ..."
Problem
I'm using mock engine to test our app. The mock engine request handler sends JSON string as body, the correct content type and status 200.
Invoking the mocked request on Android works, but invoking the same request on iOS fails:
No transformation found: class io.ktor.utils.io.ByteChannelNative -> class kotlin.collections.List
with response from <url>:
status: 200 OK
response headers:
Content-Type: application/json; charset=UTF-8
Expected
Both iOS and Android mocked engine should work
DoubleReceiveException not thrown when calling receive twice
HttpResponse.receive()
used to throw a DoubleReceiveException
when called twice, however after upgrading from ktor 1.3.2 to 1.5.2, it is no longer doing so.
private suspend fun requestNCSI(client: HttpClient): HttpResponse {
val url = "http://www.msftncsi.com/ncsi.txt"
return client.request<HttpResponse>(url) {
method = HttpMethod.Get
}
}
should("throw an exception when receiving more than once.") {
shouldThrow<DoubleReceiveException> {
val call = requestNCSI(client)
call.receive<String>()
call.receive<String>()
}
}
Wizard: Typo: Github -> GitHub
JS: Referer header is being overwriten.
I am Appending A Header in Ktor Client Request like shown in attached Image ,
Everything work as expected on JVM and Android platforms.
but when running in browser(Kotlin/JS) , this referer gets overwitten by https://localhost/
tried on chrome and firefox both.
Application feature WebSockets is not installed
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.uclan.remotecamera.androidApp, PID: 11981
io.ktor.application.MissingApplicationFeatureException: Application feature WebSockets is not installed
Encountered using the following server setup:
fun start() {
Log.d(TAG, "Attempting to start broadcast server")
embeddedServer(Netty, port) {
install(WebSockets) {
routing {
val connections =
Collections.synchronizedSet<DefaultWebSocketSession?>(LinkedHashSet())
webSocket("/exchange") {
connections.add(this)
for (frame in incoming) {
if (frame is Frame.Text) {
val msg = frame.readText()
Log.d(TAG, "Received msg '$msg'")
connections.forEach {
it.outgoing.send(Frame.Text(msg))
}
}
}
}
}
}
}.start(wait = true)
}
and the following dependencies:
implementation("io.ktor:ktor-websockets:1.5.2")
implementation("io.ktor:ktor-server-netty:1.5.2")
implementation("io.ktor:ktor-client-websockets:1.5.2")
implementation("io.ktor:ktor-client-cio:1.5.2")
Client code seems to be unaffected
1.5.1
released 28th January 2021
Client
http client (native macos_x64) v0.9.5-rc13 Memory leaks found
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/593
Whenever I try and do a simple http request on macOs the program freezes and reports a memory leak:
an example where I publish the latest eap13 branch commit to localMaven which is tagged (v0.9.5-rc13):
buildscript {
repositories {
jcenter()
maven { url 'https://plugins.gradle.org/m2/' }
maven { url 'https://dl.bintray.com/jetbrains/kotlin-native-dependencies' }
maven {url 'http://dl.bintray.com/kotlin/kotlin-dev'}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.9.2-dev-4008"
}
}
allprojects {
project.ext {
//make extras set inside buildscript available in all modules
kotlin_version = '1.3.0-rc-51'
kotlin_coroutines_version = '0.26.1-eap13'
}
}
apply plugin: 'kotlin-platform-native'
repositories {
mavenLocal()
jcenter()
}
components.main {
targets = ["macos_x64"]
outputKinds = [EXECUTABLE]
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:0.26.1-eap13")
//you need to clone ktor and publish to local maven
implementation "io.ktor:ktor-client-ios:0.9.4-SNAPSHOT"
}
}
example program:
import kotlinx.coroutines.runBlocking
import io.ktor.client.engine.ios.*
import io.ktor.client.*
import io.ktor.http.*
import io.ktor.client.request.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
import platform.darwin.*
internal val ApplicationDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue())
fun main(args: Array<String>) = runBlocking<Unit> {
val api = ApplicationApi()
api.about {
println(it)
}
delay(3000L)
return@runBlocking
}
internal class NsQueueDispatcher(
private val dispatchQueue: dispatch_queue_t
) : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatch_async(dispatchQueue) {
block.run()
}
}
}
class ApplicationApi {
private val client = HttpClient()
fun about(callback: (String) -> Unit) {
launch(ApplicationDispatcher) {
val result: String = client.get {
url {
protocol = URLProtocol.HTTPS
port = 443
host = "tools.ietf.org"
encodedPath = "rfc/rfc1866.txt"
}
}
callback(result)
}
}
}
when run:
/opt/teamcity-agent/work/4d622a065c544371/runtime/src/main/cpp/Memory.cpp:1124: runtime
assert: Memory leaks found
Abort trap: 6
java.nio.charset.IllegalCharsetNameException: %s
Some users of my app are getting the following crash:
Fatal Exception: java.nio.charset.IllegalCharsetNameException: %s
at java.nio.charset.Charset.checkName(Charset.java:317)
at java.nio.charset.Charset.lookup2(Charset.java:519)
at java.nio.charset.Charset.lookup(Charset.java:497)
at java.nio.charset.Charset.forName(Charset.java:563)
at io.ktor.http.ContentTypesKt.charset(ContentTypesKt.java:271)
at io.ktor.http.HttpMessagePropertiesKt.charset(HttpMessagePropertiesKt.java:79)
at io.ktor.client.statement.HttpStatementKt.readText(HttpStatementKt.java:169)
at io.ktor.client.statement.HttpStatementKt.readText$default(HttpStatementKt.java:168)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidationKt.java:36)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(BaseContinuationImpl.java:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.java:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.java:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.java:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.java:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.java:665)
Looks like that for some reason the charset seems to be %s
. I am not sure why that is the case. However, I do not think that this should crash the app. I think it would be better if Ktor could just fall back to UTF8 when the charset is invalid. Would that be possible in Ktor?
I am using a manually built version of Ktor based on this commit: https://github.com/Thomas-Vos/ktor/commit/6dceed335d2de689dd30e3cb1d62d571c8061041
InvalidMutabilityException when using withContext and SavedHttpCall
Calling HttpResponse.readText is throwing this exception.
I'm using Dispatchers.Main only.
I'm not explicitly trying to save any HTTP calls, just making one-time calls.
I've tried both Ktor 1.5.1 and 1.5.0
Coroutines 1.4.2-native-mt
Kotlin 1.4.20
Caused by: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen io.ktor.client.call.SavedHttpCall@3f8be38
at 0 jgosdk 0x00000001109e102f kfun:kotlin.Throwable#<init>(kotlin.String?){} + 95 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Throwable.kt:23:37)
at 1 jgosdk 0x00000001109d982d kfun:kotlin.Exception#<init>(kotlin.String?){} + 93 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:44)
at 2 jgosdk 0x00000001109d9a9d kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 93 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:44)
at 3 jgosdk 0x0000000110a1276d kfun:kotlin.native.concurrent.InvalidMutabilityException#<init>(kotlin.String){} + 93 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:22:60)
at 4 jgosdk 0x0000000110a13f4f ThrowInvalidMutabilityException + 431 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:92:11)
at 5 jgosdk 0x0000000110b18e00 MutationCheck + 128
at 6 jgosdk 0x0000000110da9e38 kfun:io.ktor.client.call.SavedHttpCall.<set-responseContent>#internal + 104 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/SavedCall.kt:23:13)
at 7 jgosdk 0x0000000110daa4e4 kfun:io.ktor.client.call.SavedHttpCall.$getResponseContentCOROUTINE$7#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 1412 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/SavedCall.kt:30:13)
at 8 jgosdk 0x0000000110daa676 kfun:io.ktor.client.call.SavedHttpCall#getResponseContent(){}io.ktor.utils.io.ByteReadChannel + 246 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/SavedCall.kt:28:22)
at 9 jgosdk 0x0000000110da78eb kfun:io.ktor.client.call.HttpClientCall.$receiveCOROUTINE$5#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 2379 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/HttpClientCall.kt:82:72)
at 10 jgosdk 0x0000000110da85c2 kfun:io.ktor.client.call.HttpClientCall#receive(io.ktor.client.call.TypeInfo){}kotlin.Any + 322 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/HttpClientCall.kt:75:20)
at 11 jgosdk 0x0000000110dec056 kfun:io.ktor.client.statement.$readTextCOROUTINE$97#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 1942 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/statement/HttpStatement.kt:170:45)
at 12 jgosdk 0x0000000110dec4c2 kfun:io.ktor.client.statement#readText@io.ktor.client.statement.HttpResponse(io.ktor.utils.io.charsets.Charset?){}kotlin.String + 322 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/statement/HttpStatement.kt:168:16)
at 13 jgosdk 0x0000000110dec67f kfun:io.ktor.client.statement#readText$default@io.ktor.client.statement.HttpResponse(io.ktor.utils.io.charsets.Charset?;kotlin.Int){}kotlin.String + 303 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/statement/HttpStatement.kt:168:16)
CIO WebSockets client incorrectly sends Sec-WebSocket-Extensions header even if empty
I have observed that the CIO WebSockets client is sending out a Sec-WebSocket-Extensions
header in the initial handshake even if no extension has been specified. This is causing issues with RFC 6455 compliant servers as (if my understanding is correct) the spec mandates that at least one extension be provided if the header exists (otherwise fail).
https://tools.ietf.org/html/rfc6455#section-9.1
https://tools.ietf.org/html/rfc2616#section-2.1 (ABNF notes)
I am not familiar enough with the Ktor code to submit a PR with a fix, but as a workaround a simple plugin can be used to strip the header from the request before sending it to the server:
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.util.*
/**
* Intercept outgoing HTTP requests to strip the `Sec-WebSocket-Extensions` header if it is empty.
*
* This feature should be installed after the `WebSockets` feature has been installed.
*/
internal class StripBlankSWSEHeader {
private fun stripHeader(context: HttpRequestBuilder) {
if (HttpHeaders.SecWebSocketExtensions in context.headers
&& context.headers[HttpHeaders.SecWebSocketExtensions]?.isBlank() == true) {
context.headers.remove(HttpHeaders.SecWebSocketExtensions)
}
}
class EmptyConfig
internal companion object Feature : HttpClientFeature<EmptyConfig, StripBlankSWSEHeader> {
override val key: AttributeKey<StripBlankSWSEHeader> = AttributeKey("StripEmptyWSExtensionHeader")
override fun prepare(block: EmptyConfig.() -> Unit): StripBlankSWSEHeader {
return StripBlankSWSEHeader()
}
override fun install(feature: StripBlankSWSEHeader, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.Render) {
feature.stripHeader(context)
proceed()
}
}
}
}
In the client configuration the StripBlankSWSEHeader
feature must be registered AFTER the WebSockets
feature:
install(WebSockets)
install(StripBlankSWSEHeader) // must be registered AFTER WebSockets feature
'%3D' inside query of redirect target location will be replaced to '='
When the server returns 302 with the URL has '%3D' (inside query part), the Ktor replaces '%3D' with '=' so some server returns invalid content (in this case, 403).
Test code:
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.runBlocking
fun main(): Unit = runBlocking {
val url = "https://dl.bintray.com/anatawa12/maven/com/anatawa12/forge/ForgeGradle/1.2-1.0.0/ForgeGradle-1.2-1.0.0-javadoc.jar"
// also in CIO
//val factory = io.ktor.client.engine.cio.CIO
val factory = io.ktor.client.engine.apache.Apache
val clientNoFollow = HttpClient(io.ktor.client.engine.cio.CIO) {
followRedirects = false
expectSuccess = false
}
val response = clientNoFollow.get<HttpResponse>(url)
println("status: " + response.status)
println("location: " + response.headers["Location"])
println("location has %3D: " + response.headers["Location"]!!.contains("%3D"))
check(response.headers["Location"]!!.contains("%3D"))
val clientWithFollow = HttpClient(io.ktor.client.engine.cio.CIO)
try {
val got = clientWithFollow.get<ByteArray>(url)
} catch (e: ResponseException) {
println("error: " + e)
println("error url: " + e.response.request.url)
println("error url has %3D: " + e.response.request.url.toString().contains("%3D"))
}
}
Log:
status: 302
location: https://d29vzk4ow07wi7.cloudfront.net/d133d0ab57f0e1a996fa1367eb3e5c7ebbd1860a5d505a69791d7746457acb0a?response-content-disposition=attachment%3Bfilename%3D%22ForgeGradle-1.2-1.0.0-javadoc.jar%22&Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHAqOi8vZDI5dnprNG93MDd3aTcuY2xvdWRmcm9udC5uZXQvZDEzM2QwYWI1N2YwZTFhOTk2ZmExMzY3ZWIzZTVjN2ViYmQxODYwYTVkNTA1YTY5NzkxZDc3NDY0NTdhY2IwYT9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPWF0dGFjaG1lbnQlM0JmaWxlbmFtZSUzRCUyMkZvcmdlR3JhZGxlLTEuMi0xLjAuMC1qYXZhZG9jLmphciUyMiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTYxMjkzNzUyOX0sIklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiIwLjAuMC4wLzAifX19XX0_&Signature=ch~WD7z9cBluu3KWX2pIsmCd7pfVQ~WnM2QEg6wLuDPFi5pr2e4DnHApdAX9A92dxJW68WYllbKB1pNRkJ6RjtW~3eip545DnUcSBYGT1vfiaxNzp80QYsg~EUo~l4eHFYVoywXXPNgEPRXPIZ2vHGv7H7aeGJ5fbhzyunSmdstRnfUpfKLL5XmsDjpvVY4RYy1NSHHG70BU3y6UP1nqdu1hEdzBhRFKwZ~0uj8~2S4OF6Daaj71jkRWF49TQS~aNpvrkaAZojUilWEwppXZNcCekXu7Bt95oJDESsNQf0bu0gDw5eNtAfy2ZzN~a~lXVTV3ORy3kVanNwiVJzcf~g__&Key-Pair-Id=APKAIFKFWOMXM2UMTSFA
location has %3D: true
error: io.ktor.client.features.ClientRequestException: Client request(https://d29vzk4ow07wi7.cloudfront.net/d133d0ab57f0e1a996fa1367eb3e5c7ebbd1860a5d505a69791d7746457acb0a?response-content-disposition=attachment%3Bfilename=%22ForgeGradle-1.2-1.0.0-javadoc.jar%22&Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHAqOi8vZDI5dnprNG93MDd3aTcuY2xvdWRmcm9udC5uZXQvZDEzM2QwYWI1N2YwZTFhOTk2ZmExMzY3ZWIzZTVjN2ViYmQxODYwYTVkNTA1YTY5NzkxZDc3NDY0NTdhY2IwYT9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPWF0dGFjaG1lbnQlM0JmaWxlbmFtZSUzRCUyMkZvcmdlR3JhZGxlLTEuMi0xLjAuMC1qYXZhZG9jLmphciUyMiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTYxMjkzNzUzMH0sIklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiIwLjAuMC4wLzAifX19XX0_&Signature=XwmQYLdJSkb~0fNr36ebj1vzn1KXmufRtaP9Mj9y9thRAtpAbyImJU-31VhtEhV56ijN2-tBtT5m0rNPSq-f6AEvngoEZURUUpE-vGVb1UFwiGnVnTuDKQJUvao3gZTj-fq9kRCLosJGYJiVbLG3GTXQKYoxL9fQh2ttwS3eFKAyFylcjyvsmSkEAekomfjB8OhUpgc~B6CE0Vg6aOm3ZBaKNAtfX4~cccnFrz4PB836WD68y8qX9jN5IQMCAiDy0881chyPIjGM7c30aycANw5c8StHSRy~gWo5WL7TGpq19X5q2pK-sT8Vq0I3AJJZ95XW9N3GWj0WR7IKqOQTAg__&Key-Pair-Id=APKAIFKFWOMXM2UMTSFA) invalid: 403 Forbidden. Text: "<?xml version="1.0" encoding="UTF-8"?><Error><Code>AccessDenied</Code><Message>Access denied</Message></Error>"
error url: https://d29vzk4ow07wi7.cloudfront.net/d133d0ab57f0e1a996fa1367eb3e5c7ebbd1860a5d505a69791d7746457acb0a?response-content-disposition=attachment%3Bfilename=%22ForgeGradle-1.2-1.0.0-javadoc.jar%22&Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHAqOi8vZDI5dnprNG93MDd3aTcuY2xvdWRmcm9udC5uZXQvZDEzM2QwYWI1N2YwZTFhOTk2ZmExMzY3ZWIzZTVjN2ViYmQxODYwYTVkNTA1YTY5NzkxZDc3NDY0NTdhY2IwYT9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPWF0dGFjaG1lbnQlM0JmaWxlbmFtZSUzRCUyMkZvcmdlR3JhZGxlLTEuMi0xLjAuMC1qYXZhZG9jLmphciUyMiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTYxMjkzNzUzMH0sIklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiIwLjAuMC4wLzAifX19XX0_&Signature=XwmQYLdJSkb~0fNr36ebj1vzn1KXmufRtaP9Mj9y9thRAtpAbyImJU-31VhtEhV56ijN2-tBtT5m0rNPSq-f6AEvngoEZURUUpE-vGVb1UFwiGnVnTuDKQJUvao3gZTj-fq9kRCLosJGYJiVbLG3GTXQKYoxL9fQh2ttwS3eFKAyFylcjyvsmSkEAekomfjB8OhUpgc~B6CE0Vg6aOm3ZBaKNAtfX4~cccnFrz4PB836WD68y8qX9jN5IQMCAiDy0881chyPIjGM7c30aycANw5c8StHSRy~gWo5WL7TGpq19X5q2pK-sT8Vq0I3AJJZ95XW9N3GWj0WR7IKqOQTAg__&Key-Pair-Id=APKAIFKFWOMXM2UMTSFA
error url has %3D: false
MockEngine doest work for Android
After updating to 1.5.2 Android target doest work. But it is ok for iOS
at 1.4.3 is ok for both platforms iOS and Android.
MockEngine {
// this code is not called on request as result mock doesn't work
}
Outdated doc string for FormPart
Documentation for FormPart
says
@param value content, could be [String], [Number] or [Input]
https://github.com/ktorio/ktor/blob/e03bafda3b3d72fcac166e46cf55e5d2d9383660/ktor-client/ktor-client-core/common/src/io/ktor/client/request/forms/formDsl.kt#L17
But later in this file Input
is deprecated
@Suppress("UNUSED_PARAMETER")
@Deprecated(
"Input is not reusable. Please use [InputProvider] instead.",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("appendInput(key, headers) { /* create fresh input here */ }")
)
public fun append(key: String, value: Input, headers: Headers = Headers.Empty) {
error("Input is not reusable. Please use [InputProvider] instead.")
}
And direct usage will cause exception
public fun formData(vararg values: FormPart<*>): List<PartData> {
...
is Input -> error("Can't use [Input] as part of form: $value. Consider using [InputProvider] instead.")
...
}
Remove dependency on 'text-encoding' npm package
It's deprecated and looks like TextEncoder and TextDecoder work on majority of environments.
Changing `requestTimeoutMillis` in config of HttpTimeout feature doesn't change the CIO's timeout
Ktor 1.5.0
I am using CIO Engine which has a default timeout of 15000 milliseconds, however, I changed the timeout for a particular request using the HttpRequestBuilder.timeout
extension function, but I still get ConnectTimeoutException after 15000 milliseconds.
Gradle:
dependencies {
....
def ktorVersion = "1.5.0"
implementation "io.ktor:ktor-client-core:$ktorVersion"
implementation "io.ktor:ktor-client-cio:$ktorVersion"
implementation "io.ktor:ktor-client-serialization:$ktorVersion"
implementation "io.ktor:ktor-client-logging-jvm:$ktorVersion"
....
}
HttpClient:
HttpClient(CIO) {
defaultRequest {
host = HOST
header("Content-Type", "application/json")
}
Logging {
logger = object : Logger {
override fun log(message: String) {
Timber.i(message)
}
}
level = LogLevel.ALL
}
install(HttpTimeout)
}
Request:
httpClient.post("${HOST_URL}/users/new") {
timeout {
requestTimeoutMillis = 30000
}
body = requestBody
}
HTTP Client exception is masked by JobCancellationException with Ktor 1.5.0
STR: perform http GET with ktor Apache client to a resource which replies with truncated content
AR:
kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelled}@eb5ab92
at kotlinx.coroutines.JobSupport.getChildJobCancellationCause(JobSupport.kt:711)
at kotlinx.coroutines.JobSupport.createCauseException(JobSupport.kt:717)
at kotlinx.coroutines.JobSupport.cancelMakeCompleting(JobSupport.kt:692)
at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:664)
at kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:634)
at kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1465)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1500)
at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:897)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:860)
at kotlinx.coroutines.JobSupport.cancelMakeCompleting(JobSupport.kt:693)
at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:664)
at kotlinx.coroutines.JobSupport.childCancelled(JobSupport.kt:648)
at kotlinx.coroutines.ChildHandleNode.childCancelled(JobSupport.kt:1466)
at kotlinx.coroutines.JobSupport.cancelParent(JobSupport.kt:358)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:332)
at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:897)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:860)
at kotlinx.coroutines.JobSupport.makeCompleting$kotlinx_coroutines_core(JobSupport.kt:803)
at kotlinx.coroutines.JobImpl.completeExceptionally(JobSupport.kt:1326)
at io.ktor.client.engine.apache.ApacheRequestProducer.failed(ApacheRequestProducer.kt:72)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.executionFailed(DefaultClientExchangeHandlerImpl.java:98)
at org.apache.http.impl.nio.client.AbstractClientExchangeHandler.failed(AbstractClientExchangeHandler.java:426)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.exception(HttpAsyncRequestExecutor.java:163)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:276)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:81)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:39)
at org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:114)
at org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:162)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:337)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:315)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:276)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)
... 1 more
Caused by: org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body (expected: 178; received: 0)
at org.apache.http.impl.nio.codecs.LengthDelimitedDecoder.read(LengthDelimitedDecoder.java:89)
at io.ktor.client.engine.apache.ApacheResponseConsumer$consumeContent$1.invoke(ApacheResponseConsumer.kt:52)
at io.ktor.client.engine.apache.ApacheResponseConsumer$consumeContent$1.invoke(ApacheResponseConsumer.kt:18)
at io.ktor.utils.io.ByteBufferChannel.writeAvailable(ByteBufferChannel.kt:1631)
at io.ktor.utils.io.ByteWriteChannel$DefaultImpls.writeAvailable$default(ByteWriteChannel.kt:85)
at io.ktor.client.engine.apache.ApacheResponseConsumer.consumeContent(ApacheResponseConsumer.kt:51)
at org.apache.http.impl.nio.client.MainClientExec.consumeContent(MainClientExec.java:329)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.consumeContent(DefaultClientExchangeHandlerImpl.java:157)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.inputReady(HttpAsyncRequestExecutor.java:336)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:265)
... 10 more
ER: ConnectionClosedException
is not masked
org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body (expected: 178; received: 0)
at org.apache.http.impl.nio.codecs.LengthDelimitedDecoder.read(LengthDelimitedDecoder.java:89)
at io.ktor.client.engine.apache.ApacheResponseConsumer$consumeContent$1.invoke(ApacheResponseConsumer.kt:52)
at io.ktor.client.engine.apache.ApacheResponseConsumer$consumeContent$1.invoke(ApacheResponseConsumer.kt:18)
at io.ktor.utils.io.ByteBufferChannel.writeAvailable(ByteBufferChannel.kt:1631)
at io.ktor.utils.io.ByteWriteChannel$DefaultImpls.writeAvailable$default(ByteWriteChannel.kt:85)
at io.ktor.client.engine.apache.ApacheResponseConsumer.consumeContent(ApacheResponseConsumer.kt:51)
at org.apache.http.impl.nio.client.MainClientExec.consumeContent(MainClientExec.java:329)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.consumeContent(DefaultClientExchangeHandlerImpl.java:157)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.inputReady(HttpAsyncRequestExecutor.java:336)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:265)
Logging tests fails due to floating log entries
Sometimes a log entry may appear at the "wrong" place (before or after expected) so the testLogResponseWithException
and siblings are flaky.
CIO native selector doesn't select new descriptors
CIO native selector doesn't select new descriptors and unable to stop when closing because cancelled interests are not processed as well.
OkHTTP client engine tries to close the connection twice during the closing handshake
Here's how I noticed:
When I cancel the coroutine containing the WebSocket connection, KTor closes the connection with a 1011
. If the server then responds by closing the connection without a status code, KTor will get a 1005
and it'll try to pass it to OkHTTP.
However, 1005
is a reserved status, so OkHTTP refuses it when Ktor passes it in the following lines: https://github.com/ktorio/ktor/blob/6053fa44a61fdfc5474ff909c0224e56877b5da1/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpWebsocketSession.kt#L62
Here's an example of a traceback you'd get:
java.util.concurrent.CancellationException: ActorCoroutine was cancelled
at kotlinx.coroutines.ExceptionsKt.CancellationException(Exceptions.kt:22)
at kotlinx.coroutines.channels.ActorCoroutine.onCancelling(Actor.kt:134)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:332)
at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:916)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:875)
at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:840)
at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:111)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: java.lang.IllegalArgumentException: Code 1005 is reserved and may not be used.
at okhttp3.internal.ws.WebSocketProtocol.validateCloseCode(WebSocketProtocol.kt:134)
at okhttp3.internal.ws.RealWebSocket.close(RealWebSocket.kt:435)
at okhttp3.internal.ws.RealWebSocket.close(RealWebSocket.kt:427)
at io.ktor.client.engine.okhttp.OkHttpWebsocketSession$outgoing$1.invokeSuspend(OkHttpWebsocketSession.kt:62)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
I think there are technically two different issues happening at the same time:
- Ktor is trying to close the connection twice. The first time with a
1011
status (which I can confirm in the server logs) and the second time with a1005
(because the server didn't provide a closing status code). - Ktor is trying to close the connection with a reserved status code.
Java client logging tests are fluky
LoggingTest.testRequestContentTypeInLog
and LoggingTest.testRequestAndResponseBody
are fluky with Java client.
Quick investigation after fixing KTOR-1598 showed that the client dispatcher gets shutdown too early so the response body handling coroutines was unable to resume due to RejectedExecutionException
.
The reason is that coroutines jobs structure was wrong and the client infrastructure was waiting for the wrong job instance.
Response channel is always cancelled with Logging feature
val channel = HttpClient {
Logging {
}
}.get<ByteReadChannel>("/test")
channel.isClosedForRead == true
// channel is always closed with exception here
HttpTimeoutTest.testConnect are flaky
Tests HttpTimeoutTest.testConnectTimeout
and testConnectTimeoutPerRequestAttributes
somtimes may fail because of the unexpected exception text that is heavily system-dependant.
This is not a test issue but a real bug in okhttp integration
HttpTimeout.testSocketTimeoutWriteFail is flaky
Tests testSocketTimeoutWriteFail* sometimes may pass because of missing failures on Apache.
The possible reason is that sleep duration is only one second while timeouts are checked by apache every selection (that is also 1sec by default). So if the test server is too fast or selection is a little bit slower, we may get a response too early so no timeout happens in this case.
Jetty: requests to resources, that doesn't respond with HTTP/2, lead to unexpected behaviour
To reproduce run the following code:
fun main() {
val client = HttpClient(Jetty) {}
runBlocking {
val r = client.get<String>("http://www.google.com")
println(r)
}
}
As a result, org.eclipse.jetty.io.EofException
will be thrown:
Exception in thread "main" org.eclipse.jetty.io.EofException
at org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:283)
at org.eclipse.jetty.io.WriteFlusher.flush(WriteFlusher.java:422)
at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:277)
at org.eclipse.jetty.io.AbstractEndPoint.write(AbstractEndPoint.java:381)
at org.eclipse.jetty.http2.HTTP2Flusher.process(HTTP2Flusher.java:259)
at org.eclipse.jetty.util.IteratingCallback.processing(IteratingCallback.java:241)
at org.eclipse.jetty.util.IteratingCallback.iterate(IteratingCallback.java:223)
at org.eclipse.jetty.http2.HTTP2Session.newStream(HTTP2Session.java:543)
at io.ktor.client.engine.jetty.JettyHttpRequestKt$executeRequest$jettyRequest$1.invoke(JettyHttpRequest.kt:40)
at io.ktor.client.engine.jetty.JettyHttpRequestKt$executeRequest$jettyRequest$1.invoke(JettyHttpRequest.kt)
at io.ktor.client.engine.jetty.UtilsKt.withPromise(utils.kt:14)
at io.ktor.client.engine.jetty.JettyHttpRequestKt.executeRequest(JettyHttpRequest.kt:39)
at io.ktor.client.engine.jetty.JettyHttpRequestKt$executeRequest$1.invokeSuspend(JettyHttpRequest.kt)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: java.nio.channels.AsynchronousCloseException
at java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:501)
at org.eclipse.jetty.io.ChannelEndPoint.flush(ChannelEndPoint.java:263)
... 18 more
If I change the URL to http://example.com
then the app will hang indefinitely, here is a stacktrace:
"main" #1 prio=5 os_prio=0 cpu=337.00ms elapsed=56.16s tid=0x00007f8e98018800 nid=0x79547 waiting on condition [0x00007f8e9eb77000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
- parking to wait for <0x00000006890f1518> (a kotlinx.coroutines.BlockingCoroutine)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:234)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:87)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at MainKt.main(main.kt:86)
at MainKt.main(main.kt)
"Reference Handler" #2 daemon prio=10 os_prio=0 cpu=0.36ms elapsed=56.11s tid=0x00007f8e98311000 nid=0x79550 waiting on condition [0x00007f8e80382000]
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.7/Native Method)
at java.lang.ref.Reference.processPendingReferences(java.base@11.0.7/Reference.java:241)
at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.7/Reference.java:213)
"Finalizer" #3 daemon prio=8 os_prio=0 cpu=1.63ms elapsed=56.11s tid=0x00007f8e98315000 nid=0x79551 in Object.wait() [0x00007f8e80281000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(java.base@11.0.7/Native Method)
- waiting on <0x00000006890f2008> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@11.0.7/ReferenceQueue.java:155)
- waiting to re-lock in wait() <0x00000006890f2008> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@11.0.7/ReferenceQueue.java:176)
at java.lang.ref.Finalizer$FinalizerThread.run(java.base@11.0.7/Finalizer.java:170)
"Signal Dispatcher" #4 daemon prio=9 os_prio=0 cpu=0.22ms elapsed=56.10s tid=0x00007f8e9832a000 nid=0x79552 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 cpu=497.80ms elapsed=56.10s tid=0x00007f8e9832c000 nid=0x79553 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
No compile task
"C1 CompilerThread0" #6 daemon prio=9 os_prio=0 cpu=306.59ms elapsed=56.10s tid=0x00007f8e9832e000 nid=0x79554 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
No compile task
"Sweeper thread" #7 daemon prio=9 os_prio=0 cpu=6.70ms elapsed=56.09s tid=0x00007f8e98330000 nid=0x79555 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Common-Cleaner" #8 daemon prio=8 os_prio=0 cpu=0.37ms elapsed=56.03s tid=0x00007f8e98381800 nid=0x79556 in Object.wait() [0x00007f8e47efd000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(java.base@11.0.7/Native Method)
- waiting on <0x00000006890f27e0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@11.0.7/ReferenceQueue.java:155)
- waiting to re-lock in wait() <0x00000006890f27e0> (a java.lang.ref.ReferenceQueue$Lock)
at jdk.internal.ref.CleanerImpl.run(java.base@11.0.7/CleanerImpl.java:148)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
at jdk.internal.misc.InnocuousThread.run(java.base@11.0.7/InnocuousThread.java:134)
"Monitor Ctrl-Break" #9 daemon prio=5 os_prio=0 cpu=8.39ms elapsed=55.91s tid=0x00007f8e98695800 nid=0x79557 runnable [0x00007f8e479d4000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(java.base@11.0.7/Native Method)
at java.net.SocketInputStream.socketRead(java.base@11.0.7/SocketInputStream.java:115)
at java.net.SocketInputStream.read(java.base@11.0.7/SocketInputStream.java:168)
at java.net.SocketInputStream.read(java.base@11.0.7/SocketInputStream.java:140)
at sun.nio.cs.StreamDecoder.readBytes(java.base@11.0.7/StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(java.base@11.0.7/StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(java.base@11.0.7/StreamDecoder.java:178)
- locked <0x00000006890f4d00> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(java.base@11.0.7/InputStreamReader.java:185)
at java.io.BufferedReader.fill(java.base@11.0.7/BufferedReader.java:161)
at java.io.BufferedReader.readLine(java.base@11.0.7/BufferedReader.java:326)
- locked <0x00000006890f4d00> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(java.base@11.0.7/BufferedReader.java:392)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)
"Service Thread" #10 daemon prio=9 os_prio=0 cpu=0.04ms elapsed=55.91s tid=0x00007f8e98696800 nid=0x79558 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"ktor-jetty-dispatcher-worker-1" #11 daemon prio=5 os_prio=0 cpu=230.54ms elapsed=55.68s tid=0x00007f8e989de000 nid=0x7955a waiting on condition [0x00007f8e46ec0000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:357)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.park(CoroutineScheduler.kt:783)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.tryPark(CoroutineScheduler.kt:728)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:711)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
"ktor-jetty-dispatcher-worker-2" #12 daemon prio=5 os_prio=0 cpu=9.27ms elapsed=55.68s tid=0x00007f8e989df000 nid=0x7955b waiting on condition [0x00007f8e46dbf000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:357)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.park(CoroutineScheduler.kt:783)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.tryPark(CoroutineScheduler.kt:728)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:711)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
"ktor-jetty-client-qtp-13" #13 prio=5 os_prio=0 cpu=6.95ms elapsed=55.47s tid=0x00007f8e340da000 nid=0x7955d runnable [0x00007f8e46575000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPoll.wait(java.base@11.0.7/Native Method)
at sun.nio.ch.EPollSelectorImpl.doSelect(java.base@11.0.7/EPollSelectorImpl.java:120)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(java.base@11.0.7/SelectorImpl.java:124)
- locked <0x000000069f414300> (a sun.nio.ch.Util$2)
- locked <0x000000069f4142a8> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(java.base@11.0.7/SelectorImpl.java:141)
at org.eclipse.jetty.io.ManagedSelector$SelectorProducer.select(ManagedSelector.java:472)
at org.eclipse.jetty.io.ManagedSelector$SelectorProducer.produce(ManagedSelector.java:409)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produceTask(EatWhatYouKill.java:360)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:184)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:135)
at org.eclipse.jetty.io.ManagedSelector$$Lambda$65/0x0000000800176c40.run(Unknown Source)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
"ktor-jetty-client-qtp-14" #14 prio=5 os_prio=0 cpu=126.50ms elapsed=55.47s tid=0x00007f8e340dc000 nid=0x7955e waiting on condition [0x00007f8e46474000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
- parking to wait for <0x000000069f418170> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:234)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@11.0.7/AbstractQueuedSynchronizer.java:2123)
at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:382)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.idleJobPoll(QueuedThreadPool.java:875)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:925)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
"ktor-jetty-client-qtp-15" #15 prio=5 os_prio=0 cpu=0.27ms elapsed=55.47s tid=0x00007f8e340dd800 nid=0x7955f waiting on condition [0x00007f8e46373000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
- parking to wait for <0x000000069fc076c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:234)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@11.0.7/AbstractQueuedSynchronizer.java:2211)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.reservedWait(ReservedThreadExecutor.java:309)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:379)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
"ktor-jetty-client-qtp-16" #16 prio=5 os_prio=0 cpu=2.68ms elapsed=55.46s tid=0x00007f8e340df800 nid=0x79560 waiting on condition [0x00007f8e46272000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
- parking to wait for <0x000000069f418170> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:234)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@11.0.7/AbstractQueuedSynchronizer.java:2123)
at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:382)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.idleJobPoll(QueuedThreadPool.java:875)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:925)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
"ktor-jetty-client-qtp-17" #17 prio=5 os_prio=0 cpu=0.10ms elapsed=55.46s tid=0x00007f8e340e1000 nid=0x79561 waiting on condition [0x00007f8e46171000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
- parking to wait for <0x000000069f418170> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:234)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@11.0.7/AbstractQueuedSynchronizer.java:2123)
at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:382)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.idleJobPoll(QueuedThreadPool.java:875)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:925)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
"ktor-jetty-client-qtp-18" #18 prio=5 os_prio=0 cpu=0.07ms elapsed=55.46s tid=0x00007f8e340e3000 nid=0x79562 waiting on condition [0x00007f8e46070000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
- parking to wait for <0x000000069f418170> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:234)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@11.0.7/AbstractQueuedSynchronizer.java:2123)
at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:382)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.idleJobPoll(QueuedThreadPool.java:875)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:925)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
"ktor-jetty-client-qtp-19" #19 prio=5 os_prio=0 cpu=0.07ms elapsed=55.46s tid=0x00007f8e340e5000 nid=0x79563 waiting on condition [0x00007f8e45f6f000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
- parking to wait for <0x000000069f418170> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:234)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@11.0.7/AbstractQueuedSynchronizer.java:2123)
at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:382)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.idleJobPoll(QueuedThreadPool.java:875)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:925)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
"ktor-jetty-client-qtp-20" #20 prio=5 os_prio=0 cpu=0.06ms elapsed=55.46s tid=0x00007f8e340e7000 nid=0x79564 waiting on condition [0x00007f8e45e6e000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
- parking to wait for <0x000000069f418170> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@11.0.7/LockSupport.java:234)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@11.0.7/AbstractQueuedSynchronizer.java:2123)
at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:382)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.idleJobPoll(QueuedThreadPool.java:875)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:925)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
"Scheduler-779376001-1" #21 prio=5 os_prio=0 cpu=10.86ms elapsed=55.42s tid=0x00007f8e34117000 nid=0x79565 waiting on condition [0x00007f8e45b6d000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@11.0.7/Native Method)
- parking to wait for <0x000000069f415c20> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@11.0.7/LockSupport.java:194)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@11.0.7/AbstractQueuedSynchronizer.java:2081)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@11.0.7/ScheduledThreadPoolExecutor.java:1170)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@11.0.7/ScheduledThreadPoolExecutor.java:899)
at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@11.0.7/ThreadPoolExecutor.java:1054)
at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@11.0.7/ThreadPoolExecutor.java:1114)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@11.0.7/ThreadPoolExecutor.java:628)
at java.lang.Thread.run(java.base@11.0.7/Thread.java:834)
"Attach Listener" #22 daemon prio=9 os_prio=0 cpu=8.56ms elapsed=55.08s tid=0x00007f8e4805c800 nid=0x79567 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"VM Thread" os_prio=0 cpu=16.20ms elapsed=56.11s tid=0x00007f8e98309000 nid=0x7954f runnable
"GC Thread#0" os_prio=0 cpu=22.88ms elapsed=56.16s tid=0x00007f8e98041800 nid=0x7954a runnable
"GC Thread#1" os_prio=0 cpu=22.20ms elapsed=55.66s tid=0x00007f8e5c001000 nid=0x7955c runnable
"G1 Main Marker" os_prio=0 cpu=0.51ms elapsed=56.15s tid=0x00007f8e980cc800 nid=0x7954b runnable
"G1 Conc#0" os_prio=0 cpu=0.07ms elapsed=56.14s tid=0x00007f8e980ce000 nid=0x7954c runnable
"G1 Refine#0" os_prio=0 cpu=0.50ms elapsed=56.13s tid=0x00007f8e9823b800 nid=0x7954d runnable
"G1 Young RemSet Sampling" os_prio=0 cpu=10.66ms elapsed=56.13s tid=0x00007f8e9823d800 nid=0x7954e runnable
"VM Periodic Task Thread" os_prio=0 cpu=42.21ms elapsed=55.91s tid=0x00007f8e98698000 nid=0x79559 waiting on condition
JNI global refs: 14, weak refs: 0
If I make the same requests with curl: curl --http2-prior-knowledge http://example.com
then I get an understandable error:
curl: (16) Error in the HTTP2 framing layer
Java client freeze
Java client tests may freeze from time to time on CI. Debugging showed up that it's a bug.
"Unfinished workers detected" using client on native
To reproduce run ./gradlew runDebugExecutableMacosX64
on the attached project.
As a result, I get unexpected Unfinished workers detected, 1 workers leaked!
warning and the program finishes with 134 exit code.
As a workaround, I can add Platform.isMemoryLeakCheckerActive = false
line to prevent this behavior.
Here is a code example that after execution reports Unfinished workers detected, 100 workers leaked!
warning:
fun main() {
runBlocking {
repeat(100) {
val client = HttpClient(Curl)
val data = client.get<String> {
url("http://google.com")
}
println(data)
client.close()
}
}
}
ResponseException is no longer serializable starting from 1.4.0 (breaking change)
Previously a ResponseException could be serialized. See: https://github.com/ktorio/ktor/issues/1256. The Transient annotation was removed in a recent commit for the HttpResponse
field. Now an exception is thrown when serializing a ResponseException.
Stack trace:
io.ktor.utils.io.concurrent.SharedJvmKt$threadLocal$1
java.io.NotSerializableException: io.ktor.utils.io.concurrent.SharedJvmKt$threadLocal$1
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)
at ExceptionsTest.serialize(ExceptionsTest.kt:26)
at ExceptionsTest.testResponseExceptionSerializable(ExceptionsTest.kt:17)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:119)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
at java.base/java.lang.Thread.run(Thread.java:834)
Here is a test to reproduce the issue:
import io.ktor.client.call.*
import io.ktor.client.features.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.util.date.*
import io.ktor.utils.io.*
import java.io.*
import kotlin.coroutines.*
import kotlin.test.*
class ExceptionsTest {
@Test
fun testResponseExceptionSerializable() {
val exception = createResponseException()
val serialized = serialize(exception)
val deserialized = deserialize(serialized)
deserialized as ResponseException
}
private fun serialize(obj: Any): ByteArray {
val b = ByteArrayOutputStream()
val o = ObjectOutputStream(b)
o.writeObject(obj)
return b.toByteArray()
}
private fun deserialize(bytes: ByteArray): Any? {
val b = ByteArrayInputStream(bytes)
val o = ObjectInputStream(b)
return o.readObject()
}
}
private fun createResponseException(): ResponseException = ResponseException(object : HttpResponse() {
override val call: HttpClientCall
get() = TODO("Not yet implemented")
override val status: HttpStatusCode
get() = TODO("Not yet implemented")
override val version: HttpProtocolVersion
get() = TODO("Not yet implemented")
override val requestTime: GMTDate
get() = TODO("Not yet implemented")
override val responseTime: GMTDate
get() = TODO("Not yet implemented")
override val content: ByteReadChannel
get() = TODO("Not yet implemented")
override val headers: Headers
get() = TODO("Not yet implemented")
override val coroutineContext: CoroutineContext
get() = TODO("Not yet implemented")
override fun toString(): String = "FakeCall"
}, cachedResponseText = "Fake text")
ClosedReceiveChannelException when making request with CIO engine using a proxy to https
version: 1.4.3
when use a http proxy, get the exception.
use no proxy, it's ok.
code
val httpClient = HttpClient(CIO) {
engine {
proxy = httpProxy
}
}
val c : String = httpClient.get(url)
build.gradle.kts
// ...
dependencies {
implementation("org.jetbrains.kotlinx", "kotlinx-serialization-json", "1.0.1")
implementation("io.ktor", "ktor-client-core", "1.4.3")
implementation("io.ktor", "ktor-client-cio", "1.4.3")
implementation("io.ktor", "ktor-client-serialization-jvm", "1.4.3")
implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.4.2")
// test
}
exception:
Channel was closed
kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed
at kotlinx.coroutines.channels.Closed.getReceiveException(AbstractChannel.kt:1107)
at kotlinx.coroutines.channels.AbstractChannel$ReceiveElement.resumeReceiveClosed(AbstractChannel.kt:906)
at kotlinx.coroutines.channels.AbstractSendChannel.helpClose(AbstractChannel.kt:322)
at kotlinx.coroutines.channels.AbstractSendChannel.close(AbstractChannel.kt:251)
at kotlinx.coroutines.channels.ChannelCoroutine.close(ChannelCoroutine.kt)
at kotlinx.coroutines.channels.SendChannel$DefaultImpls.close$default(Channel.kt:105)
at io.ktor.network.tls.TLSClientHandshake$input$1.invokeSuspend(TLSClientHandshake.kt:87)
(Coroutine boundary)
at kotlinx.coroutines.CompletableDeferredImpl.await(CompletableDeferred.kt:86)
at io.ktor.client.engine.cio.Endpoint.execute(Endpoint.kt:76)
at io.ktor.client.engine.cio.CIOEngine.execute(CIOEngine.kt:81)
at io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:86)
(Coroutine creation stacktrace)
at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:122)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
at io.ktor.client.engine.HttpClientEngine$DefaultImpls.executeWithinCallContext(HttpClientEngine.kt:81)
at io.ktor.client.engine.HttpClientEngine$install$1.invokeSuspend(HttpClientEngine.kt:66)
at io.ktor.client.engine.HttpClientEngine$install$1.invoke(HttpClientEngine.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.client.features.HttpSend$DefaultSender.execute(HttpSend.kt:128)
at io.ktor.client.features.HttpSend$Feature$install$1.invokeSuspend(HttpSend.kt:92)
at io.ktor.client.features.HttpSend$Feature$install$1.invoke(HttpSend.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.proceedWith(SuspendFunctionGun.kt:123)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:86)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invoke(HttpCallValidator.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invokeSuspend(HttpRequestLifecycle.kt:37)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invoke(HttpRequestLifecycle.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.client.HttpClient.execute(HttpClient.kt:188)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at myfunction(...)
at LoginTest$testLogin$1.invokeSuspend(LoginTest.kt:18)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at mytest(...)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:205)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:201)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.stop(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:133)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
at java.base/java.lang.Thread.run(Thread.java:834)
at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:101)
at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:101)
at io.ktor.client.features.HttpSend$DefaultSender.execute(HttpSend.kt:129)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:86)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invokeSuspend(HttpRequestLifecycle.kt:37)
at io.ktor.client.HttpClient.execute(HttpClient.kt:188)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at myfunction(...)
at mytest(...)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invokeSuspend(HttpRequestLifecycle.kt:37)
at io.ktor.client.HttpClient.execute(HttpClient.kt:188)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at myfunction(...)
at mytest(...)
at io.ktor.client.HttpClient.execute(HttpClient.kt:188)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at myfunction(...)
at mytest(...)
Caused by: kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed
at kotlinx.coroutines.channels.Closed.getReceiveException(AbstractChannel.kt:1107)
at kotlinx.coroutines.channels.AbstractChannel$ReceiveElement.resumeReceiveClosed(AbstractChannel.kt:906)
at kotlinx.coroutines.channels.AbstractSendChannel.helpClose(AbstractChannel.kt:322)
at kotlinx.coroutines.channels.AbstractSendChannel.close(AbstractChannel.kt:251)
at kotlinx.coroutines.channels.ChannelCoroutine.close(ChannelCoroutine.kt)
at kotlinx.coroutines.channels.SendChannel$DefaultImpls.close$default(Channel.kt:105)
at io.ktor.network.tls.TLSClientHandshake$input$1.invokeSuspend(TLSClientHandshake.kt:87)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Android engine generates uncatchable exceptions
The Ktor Android client is generation some exceptions which are uncatchable with a simple try-catch block. When the try-catch block is directly in the coroutine than it is working as expected and the exception is catched, however if the exception is thrown inside a function that is called from the coroutine than it is not.
I made a little sample app which i have attached below. In this sample we have multiple layers which consists of datasource, repository and viewmodel layer. If I try to catch the exception in the viewModel inside the withContext block I can make it work, however I want to handle the exception in the repository layer which is crashing the application.
I am using ktor-client-core, ktor-client-android and ktor-client-serialization, all of version 1.5.1 .
class MainActivity : AppCompatActivity() {
private lateinit var postRepository: PostRepository
private lateinit var httpClient: HttpClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
httpClient = HttpClient(Android) {
install(JsonFeature)
}
postRepository = PostRepository(
PostRemoteDataSource(PostDtoMapper(), httpClient)
)
lifecycleScope.launch() {
val result = withContext(Dispatchers.IO) {
// runCatching {
postRepository.getPosts()
// }
}
result.fold(
onSuccess = {
Log.d(
this@MainActivity::class.simpleName,
"Success loading movies, count = ${it.size}"
)
},
onFailure = {
Log.e(this@MainActivity::class.simpleName, "Failed loading movies", it)
}
)
}
}
}
class PostRepository(
private val remotePostDataSource: PostRemoteDataSource
) {
suspend fun getPosts(): Result<List<Post>> {
val remotePosts = try {
Result.success(remotePostDataSource.getPosts())
} catch (error: Throwable) {
Result.failure(error)
}
.onSuccess {
// additional data manipulation
}
// val remotePosts = remotePostDataSource.getPosts()
return remotePosts
}
}
data class Post(
val id: Int,
val userId: Int,
val title: String,
val body: String,
)
class PostDtoMapper {
fun mapFromNetwork(item: PostDto): Post = Post(
item.id,
item.userId,
item.title,
item.body
)
}
class PostRemoteDataSource(
private val postDtoMapper: PostDtoMapper,
private val httpClient: HttpClient
) {
suspend fun getPosts(): List<Post> {
return httpClient.get<List<PostDto>>("https://jsonplaceholder.typicode.com/posts")
.map { item -> postDtoMapper.mapFromNetwork(item) }
}
}
@Serializable
data class PostDto(
@SerialName("userId") val userId: Int,
@SerialName("id") val id: Int,
@SerialName("title") val title: String,
@SerialName("body") val body: String,
)
feature interceptor ignored: ktor client
https://github.com/ktorio/ktor/blob/master/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/Auth.kt#L39
—> I implemented the auth feature using 90% of the code from your example.
At this moment:
scope.feature(HttpSend)!!.intercept { origin, context ->
println("foo bar") // never happened
}
CIO
: last working version is 1.4.3
,
Apache
, Jetty
: work with any ktor version,
OkHttp
doesn't work even with 1.4.3
.
No special logic, the example above is enough to reproduce.
The block inside intercept {}
is just not called.
[JS Client] Expected signal to be an instanceof AbortSignal
When webpacking in production mode for nodejs an application that uses Ktor JS client with new IR compialtion backend and running it, any request fails with Expected signal to be an instanceof AbortSignal
:
HttpClient: REQUEST: https://api.telegram.org/bot{SECRET!}/getUpdates
HttpClient: METHOD: HttpMethod(value=POST)
HttpClient: COMMON HEADERS
HttpClient: -> Accept: application/json
HttpClient: -> Accept-Charset: UTF-8
HttpClient: CONTENT HEADERS
HttpClient: -> Content-Length: 2
HttpClient: -> Content-Type: application/json
HttpClient: BODY Content-Type: application/json
HttpClient: BODY START
HttpClient: {}
HttpClient: BODY END
HttpClient: REQUEST https://api.telegram.org/bot{SECRET!}/getUpdates failed with exception: fm: Fail to fetch
BUILD SUCCESSFUL in 28s
23 actionable tasks: 10 executed, 13 up-to-date
Lg: Exception in completion handler ChildCompletion@1[job@2] for StandaloneCoroutine{Cancelled}@2
at D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:675063
at sv (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:675207)
at av (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:674502)
at D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:677631
at bv (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:677641)
at vy.Nv.makeCompletingOnce_5 (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:1082633)
at vy.cy.resumeWith_45 (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:1055327)
at eT.lm.resumeWith_116 (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:1049996)
at eT.lm.resumeWith_45 (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:1050123)
at FC (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:744184) {
cause: fm: Fail to fetch
at uR.invoke_283 (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:1338014)
at D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:841729
at processTicksAndRejections (internal/process/task_queues.js:93:5) {
cause: TypeError: Expected signal to be an instanceof AbortSignal
at new F (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:20975)
at D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:22298
at new Promise (<anonymous>)
at Module.K (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:22261)
at lR (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:841569)
at PA.doResume_99 (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:1329715)
at TA.execute_16 (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:1331643)
at CT.doResume_99 (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:1273492)
at CT.invoke_193 (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:1273073)
at um.i [as _$this_createCoroutineUnintercepted] (D:\Projects\telegram-bot-api-generator-for-kotlin\examples\js-bot\build\distributions\js-bot.js:1:821932)
}
}
If you need to reproduce, you can use kotlingram and run :examples:js-bot:runProductionWebpackExecutable
. You will need to have a Telegram bot token in environment called jsTestBotToken
.
If you want to reproduce in other project, keep in mind KTOR-2124, which has been taken into account in kotlingram with task :examples:js-bot:fixNodeFetchForWebpack
JSON feature fails with io.ktor.client.features.ClientRequestException: Client request invalid: 406 Not Acceptable
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1907
Ktor Version and Engine Used (client or server and name)
implementation "io.ktor:ktor-client-cio:$ktor_version"
implementation "io.ktor:ktor-client-json-jvm:$ktor_version"
implementation "io.ktor:ktor-client-gson:$ktor_version"
ext.ktor_version = "1.3.1"
JVM 1.8, with Kotlin v1.3.72
Describe the bug
With JSON support being enabled, it can not consume a REST API.
Example Implementation
import io.ktor.client.HttpClient
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.request.get
suspend fun main() {
val clusterJson: String = HttpClient().get("http://ubuntu:8082/v3/clusters")
println(clusterJson)
val client: V3Cluster = HttpClient() {
install(JsonFeature)
}.get("http://ubuntu:8082/v3/clusters")
}
data class V3Cluster(
val `data`: List<Data>
)
data class Data(
val attributes: Attributes,
val id: String,
val type: String
)
data class Attributes(
val cluster_id: String
)
This fails with
Exception in thread "main" io.ktor.client.features.ClientRequestException: Client request(http://ubuntu:8082/v3/clusters) invalid: 406 Not Acceptable
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidation.kt:35)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.features.HttpCallValidator.validateResponse(HttpCallValidator.kt:35)
at io.ktor.client.features.HttpCallValidator$Companion$install$2.invokeSuspend(HttpCallValidator.kt:96)
at io.ktor.client.features.HttpCallValidator$Companion$install$2.invoke(HttpCallValidator.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:273)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:141)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:161)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
at io.ktor.client.call.HttpClientCall.receive(HttpClientCall.kt:75)
at controllers.KafkaClusterKt.main(KafkaCluster.kt:105)
....
The first get request is processed correctly, but the second one fails with the provided stacktrace.
Expected behaviour
It should deserialize the json response into an object.
Without JSON feature, the request works fine and is producing the JSON as output. Also when using curl on the terminal, the API endpoint itself seems fine:
brandl@ubuntu:~$ curl http://ubuntu:8082/v3/clusters | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 594 100 594 0 0 84857 0 --:--:-- --:--:-- --:--:-- 84857
{
"links": {
"self": "http://rest-proxy:8082/v3/clusters",
"next": null
},
"data": [
{
"id": "crn:///kafka=CVyJNKWhSrmXmnRUKWo5cg",
"links": {
"self": "http://rest-proxy:8082/v3/clusters/CVyJNKWhSrmXmnRUKWo5cg"
},
"attributes": {
"cluster_id": "CVyJNKWhSrmXmnRUKWo5cg"
},
"relationships": {
"controller": {
"links": {
"related": "http://rest-proxy:8082/v3/clusters/CVyJNKWhSrmXmnRUKWo5cg/brokers/1"
}
},
"brokers": {
"links": {
"related": "http://rest-proxy:8082/v3/clusters/CVyJNKWhSrmXmnRUKWo5cg/brokers"
}
},
"topics": {
"links": {
"related": "http://rest-proxy:8082/v3/clusters/CVyJNKWhSrmXmnRUKWo5cg/topics"
}
}
},
"type": "KafkaCluster"
}
]
}
Not relevant for the ticket, but maybe some context is helpful: I'm trying to cusome the confluent broker config API v3 as described under https://github.com/confluentinc/kafka-rest/issues/676