Changelog 1.5 version
1.5.4
released 30th April 2021
Client
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.
Core
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.
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
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)
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!
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)
Infrastructure
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.
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
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.
Other
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
Fix version publishing for Kotlinx.html
Fix `releaseVersion` property
1.5.3
released 5th April 2021
Client
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
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
Docs
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
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
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
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>
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: WebSocket feature adds redundant qualifier name
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>
.
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: 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
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
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.
Other
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
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
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.