Changelog 1.6 version
1.6.8
released 15th March 2022
Docs
Broken link in docs for Ktor Testing
The link in the screenshot (https://ktor.io/docs/old/testing.html#map) attached goes to the following url which is a 404
Server
java.lang.StackOverflowError in logger on KTor 1.3.4
Services on ktor 1.3.4 are logging sometimes
java.lang.StackOverflowError: null
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:54)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:72)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:72)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:72)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:72)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:72)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:60)
at ch.qos.logback.classic.spi.ThrowableProxy.<init>(ThrowableProxy.java:72)
which might be related to https://github.com/Kotlin/kotlinx.coroutines/issues/1264,
however I can't find a place where JobCancellationException might have been caught and wrapped in another exception.
Route that is logging this is not doing anything in parallel.
This is only happening on ktor 1.3.4, never seen on earlier versions.
Other
Youtrack: Add 1.6.8
Backpost fix to 1.6.7. : Provides transitive vulnerable dependency ch.qos.logback:logback-classic:1.2.6, logback-core, jetty-http
testImplementation("io.ktor:ktor-server-tests:1.6.7")
leads to:
Provides transitive vulnerable dependency ch.qos.logback:logback-classic:1.2.6 Deserialization of Untrusted Data vulnerability pending CVSS allocation
Provides transitive vulnerable dependency ch.qos.logback:logback-core:1.2.6 Deserialization of Untrusted Data vulnerability pending CVSS allocation
Provides transitive vulnerable dependency org.eclipse.jetty:jetty-http:9.4.42.v20210604 Exposure of Sensitive Information to an Unauthorized Actor vulnerability pending CVSS allocation
https://advisory.checkmarx.net/advisory/vulnerability/CVE-2021-42550
1.6.7
released 8th December 2021
Client
CIO client bidirectional streaming
I'm working on a project which requires a client to stream a potentially large amount of binary data to a server and receive a stream in response. Because the client to server stream could be very long, I need to be able to read the response on the client side before the client has completed sending. From my testing, this does not appear to be possible using the CIO client.
Here is a small example which illustrates the limitation:
suspend fun main() {
runServer()
runClient()
}
suspend fun runServer() {
embeddedServer(Netty, port = 8763) {
routing {
post("/post") {
launch { readChannel("Server", call.receiveChannel()) }
launch { call.respond(createContent("Server")) }
}
}
}.also { it.start(false) }
}
suspend fun runClient() {
HttpClient(CIO).request<HttpStatement> {
url("http://localhost:8763/post")
method = HttpMethod.Post
body = createContent("Client")
}.execute { readChannel("Client", it.receive()) }
}
fun createContent(name: String) = createContent {
repeat(3) {
println("$name: Sending ${BYTES.size} bytes")
writeFully(BYTES)
flush()
delay(1000)
}
}
fun createContent(body: suspend ByteWriteChannel.() -> Unit) =
ChannelWriterContent(body, null)
suspend fun readChannel(name: String, channel: ByteReadChannel) {
val buffer = ByteArray(BYTES.size)
while (true) {
try { channel.readFully(buffer) }
catch(e: Exception) { break }
println("$name: Read ${buffer.size} bytes")
}
}
Running the above sample produces the following output:
Client: Sending 1024 bytes
Server: Read 1024 bytes
Server: Sending 1024 bytes
Client: Sending 1024 bytes
Server: Read 1024 bytes
Server: Sending 1024 bytes
Client: Sending 1024 bytes
Server: Read 1024 bytes
Server: Sending 1024 bytes
Client: Read 1024 bytes
Client: Read 1024 bytes
Client: Read 1024 bytes
As you can see, the client reads occur after all client sends which is not acceptable for my use case. Ideally, the output would look like this:
Client: Sending 1024 bytes
Server: Read 1024 bytes
Server: Sending 1024 bytes
Client: Read 1024 bytes
Client: Sending 1024 bytes
Server: Read 1024 bytes
Server: Sending 1024 bytes
Client: Read 1024 bytes
Client: Sending 1024 bytes
Server: Read 1024 bytes
Server: Sending 1024 bytes
Client: Read 1024 bytes
From what I can tell, it seems the HttpStatement.execute
method blocks until the client has sent it's entire payload.
Core
Incompatible change from io.ktor:ktor-server-core:1.6.5 to 1.6.6
Hi Team!
Within your class: io.ktor.http.URLBuilder.kt you changed the signiture of Url from
public data class Url
To
public data class Url internal constructor
Which prevents the usage for us. We are using it like:
private fun sanitize(url: Url): Url {
return Url(
protocol = url.protocol,
host = url.host,
specifiedPort = url.specifiedPort,
encodedPath = url.encodedPath,
parameters = sanitizeParameters(url.parameters),
fragment = url.fragment,
user = url.user,
password = url.password,
trailingQuery = url.trailingQuery,
)
}
Can you provide a fixed version e.g. 1.6.7 ? This would be the best for all customers of your library.
or provide a fix. I guess it should work somehow with the URLBuilder, but the new ParameterBuilder makes the compiler unhappy.
Generator
Implement frontend for redesigned Ktor Project Generator
Current frontend: http://start.ktor.io/
New Design: https://zpl.io/V0XkGkK
Current frontend repo: github.com/ktorio/ktor-plugin
Localization: no
Current questions:
- [x] Put together gatsbyjs based project to start working on generator
- [x] Make a decision whether develop generator as separate project (create a new repo in this case) or as a separate component and put them to Ktor landing
- [x] Discuss UX pitfalls such as ability to move between wizard steps, page with markdown layout, how to edit selected feature list.
- [ ] Do we need a dark theme? It needs to add that states to the design.
- [ ] Do we need a mobile version? (probably not, so we need a disclaimer in this case) How it should look like on a wide screen?
- [x] Margins and components are not consistent with webteam UI, is it ok to use webteam UI design system?
Current development tasks:
- [x] Fix saving staff to local storage (@pshenichniy.eugene)
- [x] API and react-actions refactoring (@Ekaterina_Zaikina)
- [x] Artifact control (@pshenichniy.eugene, @Ekaterina_Zaikina)
- [x] "Configuration in" control (@pshenichniy.eugene)
- [x] "Add sample code” control (@pshenichniy.eugene)
- [x] Simple third tab with link to Download, if not yet (@Ekaterina_Zaikina)
- [x] Fix the rest of warnings in console (@Ekaterina_Zaikina)
- [ ] Fix tests for redux store
- [x] Prettify loader for loading documentation for the selected plugin (@Ekaterina_Zaikina)
- [x] Apply comments after design review to the first step (@Ekaterina_Zaikina)
- [x] Apply comments after design review to the second step (@Ekaterina_Zaikina)
- [x] Create task for wording review and ask someone to review (@Ekaterina_Zaikina)
- [x] Design review of the third tab (@Ekaterina_Zaikina)
- [x] Add shortcuts navigation to the list of plugins (@Ekaterina_Zaikina)
- [x] Add google or/and FUS analytics
- [x] Improve header layout for narrow screens
IntelliJ IDEA Plugin
New Project Wizard: Ktor generator is empty on first open (UPAE from KtorProjectSettingsStep)
-
Open 221 EAP Nightly
-
File -> New Project
-
Go to Ktor in Generators sidebar
-
Ktor tab is empty. Next button is available.
-
Click Next button. Exception is thrown, nothing happens
kotlin.UninitializedPropertyAccessException: lateinit property projectSettingsTemplate has not been initialized
at io.ktor.initializr.intellij.settings.KtorProjectSettingsStep$initialProjectName$2.invoke(KtorProjectSettingsStep.kt:43)
at io.ktor.initializr.intellij.settings.KtorProjectSettingsStep$initialProjectName$2.invoke(KtorProjectSettingsStep.kt:42)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at io.ktor.initializr.intellij.settings.KtorProjectSettingsStep.getInitialProjectName(KtorProjectSettingsStep.kt:42)
at io.ktor.initializr.intellij.settings.KtorProjectSettingsStep.access$getInitialProjectName(KtorProjectSettingsStep.kt:33)
at io.ktor.initializr.intellij.settings.KtorProjectSettingsStep$projectNameProperty$1.invoke(KtorProjectSettingsStep.kt:48)
at io.ktor.initializr.intellij.settings.KtorProjectSettingsStep$projectNameProperty$1.invoke(KtorProjectSettingsStep.kt:47)
at com.intellij.openapi.observable.properties.AtomicLazyProperty$update$resolve$1.invoke(AtomicLazyProperty.kt:32)
at com.intellij.openapi.observable.properties.AtomicLazyProperty$update$1.apply(AtomicLazyProperty.kt:33)
at java.base/java.util.concurrent.atomic.AtomicReference.updateAndGet(AtomicReference.java:209)
at com.intellij.openapi.observable.properties.AtomicLazyProperty.update(AtomicLazyProperty.kt:33)
at com.intellij.openapi.observable.properties.AtomicLazyProperty.get(AtomicLazyProperty.kt:16)
at io.ktor.initializr.intellij.settings.KtorProjectSettingsStep.getLocation(KtorProjectSettingsStep.kt:239)
at io.ktor.initializr.intellij.settings.KtorProjectSettingsStep.validate(KtorProjectSettingsStep.kt:218)
at com.intellij.ide.projectWizard.ProjectTypeStep.validate(ProjectTypeStep.java:653)
at com.intellij.ide.util.newProjectWizard.AbstractProjectWizard.commitStepData(AbstractProjectWizard.java:236)
at com.intellij.ide.util.newProjectWizard.AbstractProjectWizard.doNextAction(AbstractProjectWizard.java:255)
at com.intellij.ide.wizard.AbstractWizard.proceedToNextStep(AbstractWizard.java:249)
at com.intellij.ide.wizard.AbstractWizard$5.actionPerformed(AbstractWizard.java:201)
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:6654)
at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3345)
at java.desktop/java.awt.Component.processEvent(Component.java:6419)
at java.desktop/java.awt.Container.processEvent(Container.java:2263)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5029)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4861)
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:2790)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4861)
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:885)
at com.intellij.ide.IdeEventQueue.dispatchMouseEvent(IdeEventQueue.java:814)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:737)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$6(IdeEventQueue.java:433)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:802)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$7(IdeEventQueue.java:432)
at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:106)
at com.intellij.ide.IdeEventQueue.performActivity(IdeEventQueue.java:598)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:430)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:873)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:478)
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.pumpEventsForFilter(EventDispatchThread.java:117)
at java.desktop/java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:190)
at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:235)
at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:233)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.desktop/java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:233)
at java.desktop/java.awt.Dialog.show(Dialog.java:1070)
at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl$MyDialog.show(DialogWrapperPeerImpl.java:701)
at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl.show(DialogWrapperPeerImpl.java:438)
at com.intellij.openapi.ui.DialogWrapper.doShow(DialogWrapper.java:1672)
at com.intellij.openapi.ui.DialogWrapper.show(DialogWrapper.java:1630)
at com.intellij.openapi.ui.DialogWrapper.showAndGet(DialogWrapper.java:1644)
at com.intellij.ide.impl.NewProjectUtil.createNewProject(NewProjectUtil.java:75)
at com.intellij.ide.actions.NewProjectAction.actionPerformed(NewProjectAction.java:21)
at com.intellij.openapi.actionSystem.ex.ActionUtil.lambda$performActionDumbAwareWithCallbacks$4(ActionUtil.java:244)
at com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks(ActionUtil.java:265)
at com.intellij.openapi.actionSystem.ex.ActionUtil.performActionDumbAwareWithCallbacks(ActionUtil.java:244)
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:6654)
at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3345)
at java.desktop/java.awt.Component.processEvent(Component.java:6419)
at java.desktop/java.awt.Container.processEvent(Container.java:2263)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5029)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4861)
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:2790)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4861)
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:885)
at com.intellij.ide.IdeEventQueue.dispatchMouseEvent(IdeEventQueue.java:814)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:737)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$6(IdeEventQueue.java:433)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:802)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$7(IdeEventQueue.java:432)
at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:119)
at com.intellij.ide.IdeEventQueue.performActivity(IdeEventQueue.java:598)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:430)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:873)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:478)
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)
- Close wizard, go to step 2. Ktor tab is usually shown now
EA link for exception
https://ea.jetbrains.com/browser/ea_reports/9040613
Reproduced on
- IDEA: 2022.1 EAP nightly, #IU-221.4286, built on February 10, 2022
- Kotlin: 221-1.6.20-RC-138-IJ4286
Ktor plugin in IDEA 2021.3 suggests migrating the 2.0.0-eap project to 1.6.6
- Run IDEA 2021.3.
- Open the https://github.com/ktorio/ktor-documentation/tree/main/codeSnippets project and switch to the 2.0.0 branch.
=> IDEA suggests migrating this project to 1.6.6.
P.S. Maybe it's related to absense of the 2.0.0-eap version in a plugin for IDEA 2021.3.
Other
Binary compatibility issue with Ktor 1.6.5 (affecting JDK 1.8 & 11 users)
I haven't checked the details yet but some tests using Ktor's testing module in our project detected a binary-compatibility issue.
As a workaround, we locked the version to 1.6.4 and it worked for us. https://github.com/slackapi/java-slack-sdk/commit/8a9063bf694376074a3a4bed93871416f48241c3
I hope this helps.
java.lang.NoSuchMethodError: java.nio.ByteBuffer.limit(I)Ljava/nio/ByteBuffer;
at io.ktor.utils.io.ByteBufferChannel.prepareBuffer(ByteBufferChannel.kt:231)
at io.ktor.utils.io.ByteBufferChannel.setupStateForWrite$ktor_io(ByteBufferChannel.kt:285)
at io.ktor.utils.io.ByteBufferChannel.writeAsMuchAsPossible(ByteBufferChannel.kt:3487)
at io.ktor.utils.io.ByteBufferChannel.writeFully$suspendImpl(ByteBufferChannel.kt:1425)
at io.ktor.utils.io.ByteBufferChannel.writeFully(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteWriteChannelKt.writeFully(ByteWriteChannel.kt:152)
at io.ktor.server.engine.BaseApplicationResponse$respondFromBytes$3$1.invokeSuspend(BaseApplicationResponse.kt:191)
at (Coroutine boundary.()
at io.ktor.server.engine.DefaultTransformKt$installDefaultTransformations$1.invokeSuspend(DefaultTransform.kt:35)
at test_locally.app.events.TestKt$main$2$1.invokeSuspend(test.kt:42)
at io.ktor.routing.Routing.executeResult(Routing.kt:154)
at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:107)
at io.ktor.server.testing.TestApplicationEngine$callInterceptor$1.invokeSuspend(TestApplicationEngine.kt:296)
at io.ktor.server.testing.TestApplicationEngine$2.invokeSuspend(TestApplicationEngine.kt:50)
at io.ktor.server.testing.TestApplicationEngine$handleRequest$pipelineJob$1.invokeSuspend(TestApplicationEngine.kt:295)
at io.ktor.server.testing.TestApplicationEngine$handleRequest$1.invokeSuspend(TestApplicationEngine.kt:153)