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)
1.6.6
released 29th November 2021
Client
Cookies that added to request got removed if HttpCookies plugin is installed
To reproduce run the following test:
@Test
fun test() = runBlocking {
val client = HttpClient(MockEngine) {
engine {
addHandler { request ->
assertEquals("test=value", request.headers["Cookie"])
respond("")
}
}
install(HttpCookies) { // Without HttpCookies plugin the test passes
storage = AcceptAllCookiesStorage()
}
}
client.get<Unit>("/example") {
cookie("test", "value")
}
}
Basic auth not sending second request
Code:
private val ktorHttpClient = HttpClient(OkHttp) {
install(Auth) {
basic {
credentials { BasicAuthCredentials(username = "[REDACTED]", password = "[REDACTED]") }
}
}
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
Log.d("Logger Ktor =>", message)
}
}
level = LogLevel.ALL
}
}
private suspend fun login() {
val url = "https://api.backblazeb2.com/b2api/v2/b2_authorize_account"
val resp = ktorHttpClient.get<HttpResponse>(url)
Log.d("BACKBLAZE", resp.readText())
}
Log output:
D/Logger Ktor =>: REQUEST: https://api.backblazeb2.com/b2api/v2/b2_authorize_account
D/Logger Ktor =>: METHOD: HttpMethod(value=GET)
COMMON HEADERS
-> Accept: */*
D/Logger Ktor =>: -> Accept-Charset: UTF-8
CONTENT HEADERS
-> Content-Length: 0
BODY Content-Type: null
D/Logger Ktor =>: BODY START
D/Logger Ktor =>: BODY END
D/EGL_emulation: eglMakeCurrent: 0xe73848a0: ver 2 0 (tinfo 0xe7383440)
D/Logger Ktor =>: RESPONSE: 401
METHOD: HttpMethod(value=GET)
D/Logger Ktor =>: FROM: https://api.backblazeb2.com/b2api/v2/b2_authorize_account
COMMON HEADERS
D/Logger Ktor =>: -> cache-control: max-age=0, no-cache, no-store
-> connection: keep-alive
D/Logger Ktor =>: -> content-length: 64
-> content-type: application/json;charset=utf-8
-> date: Mon, 22 Nov 2021 18:21:26 GMT
D/Logger Ktor =>: -> keep-alive: timeout=5
-> www-authenticate: BASIC realm="authorize_account"
D/Logger Ktor =>: BODY Content-Type: application/json; charset=utf-8
D/Logger Ktor =>: BODY START
D/Logger Ktor =>: {
"code": "bad_auth_token",
"message": "",
"status": 401
}
D/Logger Ktor =>: BODY END
E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-4
io.ktor.client.features.ClientRequestException: Client request(https://api.backblazeb2.com/b2api/v2/b2_authorize_account) invalid: 401 . Text: "{
"code": "bad_auth_token",
"message": "",
"status": 401
}"
From what I understand, upon receiving a 401, Ktor is supposed to send a second request with the Authorization
header. This is not happening. I have tried with expectSuccess = false
and it still doesn't happen. It does work with sendWithoutRequest { true }
.
Docs
EAP 2.0 Documentation mention non existing method `sendWithoutRequest`
The sendWithoutRequest
method mentionned here https://ktor.io/docs/eap/auth.html#digest is not available
I don't know if you expect bug report about documentation about EAP, sorry if you know the doc is outdated and plan to update it later.
And if I work my way to enable it, their is an NPE. As a workaround, I did :
install(Auth) {
providers+=object:AuthProvider by DigestAuthProvider({DigestAuthCredentials("foo", "bar")}, "MyRealm"){
val notFirst= Collections.synchronizedSet(mutableSetOf<String>())
override fun sendWithoutRequest(request: HttpRequestBuilder): Boolean {
return !notFirst.add(request.url.host+":"+request.url.port)
}
}
If not used, EACH call start without auth header.... So each call make 2 request (first the 401, then the 200)
Also, the nc
attribute of the authorization header is incremented even when the challenge is reset.
Website docs issue with CORS hosts subdomain
Here: https://ktor.io/docs/cors.html#hosts
it gives "subDomains" as an example like so: subDomains = listOf("en,de,es")
but I'm pretty sure it should be subDomains = listOf("en", "de", "es")
Server
corsCheckRequestHeaders false
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1722
Ktor Version and Engine Used (client or server and name)
ktor_version=1.3.1
server
Describe the bug
CORS feature installed in Application
install(CORS)
{
method(HttpMethod.Options)
header(HttpHeaders.XForwardedProto)
anyHost()
allowSameOrigin = false
allowCredentials = true
allowNonSimpleContentTypes = true
maxAgeDuration = 1.toDuration(DurationUnit.DAYS)
}
The preflight request of some browser contains Access-Control-Request-Headers, but the field-value of it is empty, function corsCheckRequestHeaders will return false, and the browser will get 403 status.
preflight request header
Host: xxx
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: https://xxx
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36 QBCore/4.0.1295.400 QQBrowser/9.0.2524.400 Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2875.116 Safari/537.36 NetType/WIFI MicroMessenger/7.0.5 WindowsWechat
Access-Control-Request-Headers:
Accept: /
Referer: https://xxx
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.5;q=0.4
corsCheckRequestHeaders()
private fun ApplicationCall.corsCheckRequestHeaders(): Boolean {
val requestHeaders =
request.headers.getAll(HttpHeaders.AccessControlRequestHeaders)
?.filter { !it.isBlank() } // add this line to fix the issue <<<<<<<<<<<<<<<<<<<<<<<
?.flatMap { it.split(",") }
?.map {
it.trim().toLowerCasePreservingASCIIRules()
} ?: emptyList()
return requestHeaders.none { it !in allHeadersSet }
}
Session cookie with BASE64 encoding fails to set correct cookie
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1447
Ktor Version and Engine Used (client or server and name)
1.2.5 and 1.3.0-beta-1, server, netty and CIO
Describe the bug
When using the base64 encoding for a session cookie, the correct cookie is not picked up.
In my repro example below I am basically calling /login
, which sets the session, and then /
, where the session is evaluated. When using base64 encoding, the second call doesn't succeed, as no session is found.
Important to notice that this works with all other encodings, and even with base64 in the test engine.
To Reproduce
Minimal repro here: https://github.com/AndreasVolkmann/ktor-session
Steps to reproduce the behavior:
- Start the server
- Go to
localhost:5000/login
which should returnLogged in
and set the session / cookie. - Go to
localhost:5000
where an error is shown
When changing the encoding, the above scenario works.
Expected behavior
The session cookie should be set and valid for the next call, just like with all other encodings and under test.
Tested with multiple browsers and postman.
Some Netty EngineMain properties are not set
Ktor Version
1.6.4
Ktor Engine Used
Netty Server
Behaviour
Only some of NettyApplicationEngine
's properties are loaded from ApplicationConfig
set when using EngineMain
.
Expected behaviour
After reading https://ktor.io/docs/engines.html#configure-engine I assumed it would be possible to configure all properties (besides the functions) via the configuration files.
Missing runningLimit
, requestReadTimeoutSeconds
and tcpKeepAlive
DropwizardMetrics does not append baseName to the 'per endpoint'-metrics breaking-change
When setting basename
install(DropwizardMetrics) {
registry = metricRegistry
baseName = "my.prefix"
}
metrics for endpoints does not get prefixed with the baseName I set.
When GET-ing /some/path/
I get metrics /some/path/(method:GET)
but I expect my.prefix./some/path/(method:GET)
I am using ktor 1.5.2
Development mode isn't taken into account for subroutes
To reproduce run the following code and make a GET / request:
fun main() {
val env = applicationEngineEnvironment {
developmentMode = true
connector {
port = 4444
}
module {
routing {
get("/") {
throw RuntimeException()
}
}
}
}
embeddedServer(Netty, env).start()
}
In the stack trace I observe a line: io.ktor.util.pipeline.SuspendFunctionGun.execute
. Since development mode is enabled I expect that only DebugPipelineContext
is used but for all routes, except for the Routing
, SuspendFunctionGun
is executed.
Other
Drop Old Patch Releases from API Docs to Reduce Space Consumption
We should drop everything before 1.6.6
URL port should be in 0..65535
Update Kotlin to 1.6.0
1.6.5
released 2nd November 2021
Client
Fix Flaky Timeout Priority Test
Core
Add Support for Apple Silicon Targets
Im trying to run the wizard project for KMP with Kotlin 1.5.30 on a M1 mac
I added this on my gradle
val iosTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget = when {
System.getenv("SDK_NAME")?.startsWith("iphoneos") == true -> ::iosArm64
System.getenv("NATIVE_ARCH")?.startsWith("arm") == true -> ::iosSimulatorArm64
else -> ::iosX64
}
iosTarget("ios") {
binaries {
framework {
baseName = "shared"
}
}
}
I could run the app on android and iOS, but then I added the Ktor dependencies and getting getting the following issue when I trying to run the app on an iOS simulator
Could not resolve all files for configuration ':shared:iosCompileKlibraries'.
> Could not resolve io.ktor:ktor-client-core:1.6.2.
Required by:
project :shared
> No matching variant of io.ktor:ktor-client-core:1.6.2 was found. The consumer was configured to find a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native', attribute 'org.jetbrains.kotlin.native.target' with value 'ios_simulator_arm64' but:
- Variant 'commonMainMetadataElements' capability io.ktor:ktor-client-core:1.6.2 declares a usage of 'kotlin-api' of a component:
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'common' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_simulator_arm64')
- Variant 'iosArm32ApiElements-published' capability io.ktor:ktor-client-core:1.6.2 declares a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native':
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_arm32' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_simulator_arm64'
- Variant 'iosArm32MetadataElements-published' capability io.ktor:ktor-client-core:1.6.2 declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native':
- Incompatible because this component declares a usage of 'kotlin-metadata' of a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_arm32' and the consumer needed a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_simulator_arm64'
- Variant 'iosArm64ApiElements-published' capability io.ktor:ktor-client-core:1.6.2 declares a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native':
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_arm64' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_simulator_arm64'
- Variant 'iosArm64MetadataElements-published' capability io.ktor:ktor-client-core:1.6.2 declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native':
- Incompatible because this component declares a usage of 'kotlin-metadata' of a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_arm64' and the consumer needed a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_simulator_arm64'
- Variant 'iosX64ApiElements-published' capability io.ktor:ktor-client-core:1.6.2 declares a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native':
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_x64' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_simulator_arm64'
- Variant 'iosX64MetadataElements-published' capability io.ktor:ktor-client-core:1.6.2 declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native':
- Incompatible because this component declares a usage of 'kotlin-metadata' of a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_x64' and the consumer needed a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.native.target' with value 'ios_simulator_arm64'
- Variant 'jsIrApiElements-published' capability io.ktor:ktor-client-core:1.6.2 declares a usage of 'kotlin-api' of a component:
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_simulator_arm64')
- Variant 'jsIrRuntimeElements-published' capability io.ktor:ktor-client-core:1.6.2:
- Incompatible because this component declares a usage of 'kotlin-runtime' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js' and the consumer needed a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_simulator_arm64')
- Variant 'jsLegacyApiElements-published' capability io.ktor:ktor-client-core:1.6.2 declares a usage of 'kotlin-api' of a component:
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_simulator_arm64')
- Variant 'jsLegacyRuntimeElements-published' capability io.ktor:ktor-client-core:1.6.2:
- Incompatible because this component declares a usage of 'kotlin-runtime' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'js' and the consumer needed a usage of 'kotlin-api' of a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_simulator_arm64')
- Variant 'jvmApiElements-published' capability io.ktor:ktor-client-core:1.6.2 declares an API of a component:
- Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
- Other compatible attribute:
- Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_simulator_arm64')
- Variant 'jvmRuntimeElements-published' capability io.ktor:ktor-client-core:1.6.2 declares a runtime of a component:
Bump kotlinx.serialization to 1.3.0
Remove internal require and switch to Stdlib
Use the stdlib require
function instead of io.ktor.utils.io.core.internal.require
, with has the same signature.
Host parameter may contain illegal symbols
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1862
Ktor Version and Engine Used (client or server and name)
io.ktor:ktor-server-cio:1.3.2
Describe the bug
"/", "?", "#", "@" symbols in 'Host' parameter value are prohibited(see https://tools.ietf.org/html/rfc2396#appendix-A)
To Reproduce
Steps to reproduce the behavior:
Run code:
import io.ktor.http.cio.parseRequest
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.runBlocking
fun main() {
listOf(
"""
POST / HTTP/1.1
Host: www/example.com
""".trimIndent(),
"""
POST / HTTP/1.1
Host: www.example?com
""".trimIndent(),
"""
POST / HTTP/1.1
Host: www.ex#mple.com
""".trimIndent(),
"""
POST / HTTP/1.1
Host: www.ex@mple.com
""".trimIndent()
).forEach {
test(it)
}
}
fun test(http: String) {
val request = runBlocking { parseRequest(ByteReadChannel(http)) }!!
println("method=${request.method};uri={${request.uri}};headers=[${request.headers}]")
}
Expected behavior
An error expected - exception or null result.
Docs
ktor-documentation code snippets - project e2e can't run
I can't make 'e2e' to start.
https://github.com/ktorio/ktor-documentation/tree/main/codeSnippets/snippets/e2e
Expected:
./gradlew :e2e:run
This has to work.
Infrastructure
Ktor client 1.6.5 not published for 1.6.5
Its seems that the publish infrastructure is broken again and
https://search.maven.org/artifact/io.ktor/ktor-client-core-watchosx64/1.6.5
Wasn't published properly. It's however referenced in the module https://repo1.maven.org/maven2/io/ktor/ktor-client-core/1.6.5/ktor-client-core-1.6.5.module
Could not find tvOS in ktor-client
Hi!
In version 1.6.4 there are no problems, tvOS is supported without problems.
When upgrading the version to 1.6.5, it cannot find tvOS support.
FAILURE: Build completed with 2 failures.
1: Task failed with an exception.
-----------
* What went wrong:
Execution failed for task ':shared:compileKotlinTvosArm64'.
> Could not resolve all files for configuration ':shared:tvosArm64CompileKlibraries'.
> Could not find io.ktor:ktor-client-core-tvosarm64:1.6.5.
Searched in the following locations:
- https://dl.google.com/dl/android/maven2/io/ktor/ktor-client-core-tvosarm64/1.6.5/ktor-client-core-tvosarm64-1.6.5.pom
- https://repo.maven.apache.org/maven2/io/ktor/ktor-client-core-tvosarm64/1.6.5/ktor-client-core-tvosarm64-1.6.5.pom
- https://oss.sonatype.org/content/repositories/snapshots/io/ktor/ktor-client-core-tvosarm64/1.6.5/ktor-client-core-tvosarm64-1.6.5.pom
Required by:
project :shared > io.ktor:ktor-client-core:1.6.5
> Could not find io.ktor:ktor-client-ios-tvosarm64:1.6.5.
Searched in the following locations:
- https://dl.google.com/dl/android/maven2/io/ktor/ktor-client-ios-tvosarm64/1.6.5/ktor-client-ios-tvosarm64-1.6.5.pom
- https://repo.maven.apache.org/maven2/io/ktor/ktor-client-ios-tvosarm64/1.6.5/ktor-client-ios-tvosarm64-1.6.5.pom
- https://oss.sonatype.org/content/repositories/snapshots/io/ktor/ktor-client-ios-tvosarm64/1.6.5/ktor-client-ios-tvosarm64-1.6.5.pom
Required by:
project :shared > io.ktor:ktor-client-ios:1.6.5
Server
NullPointerException if static file is not found
Currently, if you use a static file from the JVM resources like this
static {
resources()
}
and try accessing a file this error get's thrown instead of a 404
java.lang.NullPointerException: jarEntry must not be null
at io.ktor.http.content.JarFileContent.<init>(JarFileContent.kt:41)
at io.ktor.http.content.StaticContentResolutionKt.resourceClasspathResource(StaticContentResolution.kt:62)
at io.ktor.http.content.StaticContentResolutionKt.resolveResource(StaticContentResolution.kt:38)
at io.ktor.http.content.StaticContentResolutionKt.resolveResource$default(StaticContentResolution.kt:24)
at io.ktor.http.content.StaticContentKt$resources$1.invokeSuspend(StaticContent.kt:230)
at io.ktor.http.content.StaticContentKt$resources$1.invoke(StaticContent.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:246)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:116)
at com.homedia.backend.middleware.PublicKt$public$1$1.invokeSuspend(Public.kt:22)
at com.homedia.backend.middleware.PublicKt$public$1$1.invoke(Public.kt)
at com.homedia.backend.middleware.PublicKt$public$1$1.invoke(Public.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:246)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:116)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:136)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:79)
at io.ktor.routing.Routing.executeResult(Routing.kt:155)
at io.ktor.routing.Routing.interceptor(Routing.kt:39)
at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:107)
at io.ktor.routing.Routing$Feature$install$1.invoke(Routing.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:246)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:116)
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:110)
at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:246)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:116)
at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:102)
at io.ktor.features.StatusPages$interceptCall$2.invoke(StatusPages.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:194)
at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:101)
at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:142)
at io.ktor.features.StatusPages$Feature$install$2.invoke(StatusPages.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:246)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:116)
at io.ktor.metrics.micrometer.MicrometerMetrics$Feature$install$2.invokeSuspend(MicrometerMetrics.kt:209)
at io.ktor.metrics.micrometer.MicrometerMetrics$Feature$install$2.invoke(MicrometerMetrics.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:246)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:116)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:136)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:79)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:124)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:246)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:116)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:136)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:79)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:123)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:55)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:112)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:43)
at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:34)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:61)
at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:370)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.ktor.server.netty.EventLoopGroupProxy$Companion$create$factory$1$1.run(NettyApplicationEngine.kt:241)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:831)
Change default log level for CallLogging feature to INFO
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/319
The rational behind this proposal is that a production deployment of an application shall typically run with INFO
threshold on its logs (DEBUG
and TRACE
are typically used only for debugging of certain subsystems) and we expect that all requests shall be logged in a production deployment by default, since the log of requests represents the starting point for investigation of any production incidents.
PartialContent need responed content length when respcode = 206
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1995
now just range
Server CORS Plugin: Throw IllegalArgumentException if anyHost and allowCredentials is true
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials
install(CORS) {
anyHost()
allowCredentials = true
}
Expected: IAE during installing CORS plugin with this configuration
java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1652
Hi all
When I run my Ktor application with gradle run then I've got the following exception:
19:21:11.795 [main] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework
19:21:11.810 [main] DEBUG io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false
19:21:11.810 [main] DEBUG io.netty.util.internal.PlatformDependent0 - Java version: 11
19:21:11.811 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available
19:21:11.812 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available
19:21:11.812 [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available
19:21:11.814 [main] DEBUG io.netty.util.internal.PlatformDependent0 - direct buffer constructor: unavailable
java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled
at io.netty.util.internal.ReflectionUtil.trySetAccessible(ReflectionUtil.java:31)
at io.netty.util.internal.PlatformDependent0$4.run(PlatformDependent0.java:225)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:219)
at io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:273)
at io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:92)
at io.netty.channel.epoll.Native.loadNativeLibrary(Native.java:225)
at io.netty.channel.epoll.Native.<clinit>(Native.java:57)
at io.netty.channel.epoll.Epoll.<clinit>(Epoll.java:39)
at io.ktor.server.netty.EventLoopGroupProxy$Companion.create(NettyApplicationEngine.kt:189)
at io.ktor.server.netty.NettyApplicationEngine.<init>(NettyApplicationEngine.kt:74)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:22)
19:21:11.814 [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Bits.unaligned: available, true
19:21:11.815 [main] DEBUG io.netty.util.internal.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable
java.lang.IllegalAccessException: class io.netty.util.internal.PlatformDependent0$6 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @557caf28
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
at java.base/java.lang.reflect.Method.invoke(Method.java:558)
at io.netty.util.internal.PlatformDependent0$6.run(PlatformDependent0.java:335)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:326)
at io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:273)
at io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:92)
at io.netty.channel.epoll.Native.loadNativeLibrary(Native.java:225)
at io.netty.channel.epoll.Native.<clinit>(Native.java:57)
at io.netty.channel.epoll.Epoll.<clinit>(Epoll.java:39)
at io.ktor.server.netty.EventLoopGroupProxy$Companion.create(NettyApplicationEngine.kt:189)
at io.ktor.server.netty.NettyApplicationEngine.<init>(NettyApplicationEngine.kt:74)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:22)
the content of build.gradle.kts file
plugins {
application
kotlin("jvm") version "1.3.61"
}
group = "io.flatmap"
version = "1.0-SNAPSHOT"
val ktor_version = "1.3.0"
repositories {
mavenCentral()
jcenter()
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
compile("io.ktor:ktor-server-netty:$ktor_version")
compile("io.ktor:ktor-server-core:$ktor_version")
compile("ch.qos.logback:logback-classic:1.2.3")
testCompile(group = "junit", name = "junit", version = "4.12")
}
tasks {
compileKotlin {
kotlinOptions.jvmTarget = "11"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "11"
}
}
application {
mainClassName = "io.ktor.server.netty.EngineMain"
}
I am using Zulu OpenJDK 11:
java --version
openjdk 11.0.6 2020-01-14 LTS
OpenJDK Runtime Environment Zulu11.37+17-CA (build 11.0.6+10-LTS)
OpenJDK 64-Bit Server VM Zulu11.37+17-CA (build 11.0.6+10-LTS, mixed mode)
I thought, this is solved through https://github.com/ktorio/ktor/issues/1190.
Thanks
Other
Provide color on log out when running application.
Distinguishing green, yellow, red.
Incorrect grammar in notice about internal api
The last sentence is not required and is grammatically incorrect. Let's drop it please.
1.6.4
released 1st October 2021
Client
Installed Closeable features not closed when closing HttpClient
Hi! I'm writing a custom feature that saves cookies to a file when the installed HttpClient
closes. From reading the HttpClient.close()
function here, it seems that the intention is that the close()
call will cascade to installed Closeable
features; but I discovered that my feature's close()
function is not called due to seemingly another layer of indirection in HttpClientConfig.install()
.
This test below demonstrates the problem:
class FeatureCloseTest {
@Test
fun testFeatureCloseIsCalled() {
val client = HttpClient(MockEngine) {
engine { addHandler { respond("") } }
install(TestFeature)
}
client.close()
assert(client.feature(TestFeature)!!.called) // This fails
}
class TestFeature : Closeable {
var called = false
override fun close() {
called = true
}
companion object : HttpClientFeature<Unit, TestFeature> {
override val key: AttributeKey<TestFeature> = AttributeKey("TestFeature")
override fun install(feature: TestFeature, scope: HttpClient) = Unit
override fun prepare(block: Unit.() -> Unit): TestFeature = TestFeature()
}
}
}
I think this is a bug? I can work around it at the moment to explicitly call my feature's close()
function, but it would be good to get this fixed.
Core
Significant performance penalty with socket write() and file read() on JVM
Hi,
It seems that there are performance issues with the current implementation of TCP socket write and also with reading files. Attaching the performance comparison for ktor IO vs regular JVM raw IO:
Files:
--------------------------------------------------------------------
Ktor channel read (128MiB), 20 iterations
Avg: 1239.65ms Min: 1095ms, Max: 1723ms
Raw: 1095, 1104, 1113, 1113, 1113, 1122, 1123, 1126, 1151, 1184, 1249, 1251, 1254, 1254, 1278, 1314, 1351, 1399, 1476, 1723
--------------------------------------------------------------------
Random File Read (128MiB), 20 iterations
Avg: 75.4ms Min: 65ms, Max: 161ms
Raw: 65, 65, 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 70, 70, 75, 79, 83, 96, 161
--------------------------------------------------------------------
Stream Read (128MiB), 20 iterations
Avg: 58.5ms Min: 56ms, Max: 69ms
Raw: 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 58, 58, 59, 59, 59, 59, 59, 65, 69
Sockets:
--------------------------------------------------------------------
JVM socket write (128MiB), 20 iterations
Avg: 87.7ms Min: 79ms, Max: 112ms
Raw: 79, 81, 81, 82, 82, 82, 83, 83, 84, 84, 84, 86, 87, 90, 90, 91, 92, 96, 105, 112
--------------------------------------------------------------------
Ktor socket write (128MiB), 20 iterations
Avg: 675.3ms Min: 572ms, Max: 968ms
Raw: 572, 582, 583, 589, 598, 612, 633, 636, 651, 676, 682, 683, 686, 688, 689, 702, 727, 763, 786, 968
You can reproduce the performance measurements with the following tests:
https://gist.github.com/Malinskiy/2dbe05a38321417fd4f23e1872593806
https://gist.github.com/Malinskiy/0172b369040cabc074ccfea9a787a79c
This is the result of an investigation of the issue raised here https://github.com/Malinskiy/marathon/issues/462
Cheers,
Anton
ContentType.parse("text/html qqq") must fail with error
STR:
- invoke
ContentType.parse("text/html xxx")
ER: fail
AR:ContentType
with value"text/html xxx"
Docs
In multi-module application, a plugin installed in a single module will be executed for requests handled by any module.
Per Ktor docs https://ktor.io/docs/modules.html, installed plugins should only apply to the module in which they are installed. However we are seeing that if a plugin is installed in one module, but not another, the plugin is executed for requests handled by the module that DOES NOT have the plugin installed.This is either a bug, or the documentation is misleading or incomplete on how to separate plugins for different modules.
Example (source, and runnable JAR provided):
I have a Ktor application with 2 modules, each defining a distinct Route,
module1() /module1
module2() /module2
I have the CallLogging plugin installed in module1, but not in module2
However when making calls to routes in either module the CallLogging plugin is executed:
Calling http://localhost:8080/module1
we see in console, from CallLogging plugin
2021-10-07 10:36:30.671 [eventLoopGroupProxy-4-1] TRACE Application - 200 OK: GET - /module1
Calling http://localhost:8080/module2
we see in console, from CallLogging plugin
2021-10-07 10:37:57.069 [eventLoopGroupProxy-4-1] TRACE Application - 200 OK: GET - /module2
Explain method(HttpMethod.Options) in docs for CORS
method(HttpMethod.Options
Is always required in CORS feature because a browser always sends Options preflight request to check CORS (https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request)
But for some reason it is not documented
Please, also stay in sync about API that may be changed here (https://youtrack.jetbrains.com/issue/KTOR-2912)
Update JSON topics using code snippets from the 'codeSnippets' project
Update the 'Modules' topic
https://ktor.io/docs/modules.html
- add information on how to use modules depending on the way used to run a server (https://ktor.io/docs/engines.html#configure)
Generator
start.ktor.io - oauth not importing io.ktor.sessions.* in Security.kt by default causing build error
Summary
Attempting to create a starter project using start.ktor.io has a build error by default if you select Authentication OAuth.
Steps to Reproduce
- Navigate to start.ktor.io
- Maintain default settings
- Gradle Kotlin, Ktor 1.6.3, Netty
- Add Authentication OAuth Plugin
- Generate Project
- Attempt to build project
Detailed Description
The generated project includes a Security.kt in plugins to configure OAuth for Google. Within routing
for auth-oauth-google, there is a line of code:
call.sessions.set(UserSession(principal?.accessToken.toString()))
Attempting to then build the project, we get the following error:
> Task :compileKotlin FAILED
e: ...\ktor-sample\src\main\kotlin\com\example\plugins\Security.kt: (42, 30): Unresolved reference: sessions
e: ...\ktor-sample\src\main\kotlin\com\example\plugins\Security.kt: (42, 43): Unresolved reference: sessions
This is due to the following import missing in Security.kt: import io.ktor.sessions.*
Solution
When generating a project with OAuth, include import io.ktor.sessions.*
in Security.kt
Could not find artifact org.jetbrains.kotlinx:kotlinx-html-jvm:pom:0.7.2
To reproduce create a project with all features selected and the the following properties:
- Build system: Maven
- Ktor version 1.4.0
- Engine: Tomcat
- Configuration: HOCON file
After project generation, sync fails with the following error:
Could not find artifact org.jetbrains.kotlinx:kotlinx-html-jvm:pom:0.7.2 in kotlinx-html (https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven)
Also, build fails with an unresolved reference error:
/home/stexe/projects/ktor_plugin/ktor-sample7/src/main/kotlin/com/example/plugins/Sockets.kt:11:24
Kotlin: Unresolved reference: tls
IntelliJ IDEA Plugin
Ktor 2.0.0-eap-256 some dependecies are not imported
When creating a new Ktor project and targeting Ktor 2.0.0-eap-256, some dependencies are not imported by default in gradle file (despite being marked as needed in Plugins list).
These 3 are the ones that I had to import manually:
ktor-server-http-redirect
ktor-server-default-headers
ktor-server-content-negotiation
Env:
IDEA 2021.2.2 (Ultimate Edition)
Build #IU-212.5284.40, built on September 14, 2021
Runtime version: 11.0.12+7-b1504.28 x86_64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
macOS 10.15.7
GC: G1 Young Generation, G1 Old Generation
Memory: 2048M
Cores: 16
Kotlin: 212-1.5.10-release-IJ5284.40
Samples
GraalVM binary using CIO fails on start "Module function cannot be found"
Hi! I noticed the example you have in the repository builds successfully but fails upon execution.
21:21:09.621 [DefaultDispatcher-worker-1] INFO ktor.application - Autoreload is disabled because the development mode is off.
Exception in thread "DefaultDispatcher-worker-1" java.lang.ClassNotFoundException: Module function cannot be found for the fully qualified name 'io.ktorgraal.ApplicationKt$main$1.invoke'
at io.ktor.server.engine.internal.CallableUtilsKt.executeModuleFunction(CallableUtils.kt:27)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:332)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:331)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:356)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.launchModuleByName(ApplicationEngineEnvironmentReloading.kt:331)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$launchModuleByName(ApplicationEngineEnvironmentReloading.kt:30)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:319)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:310)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:338)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:310)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:143)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:277)
at io.ktor.server.cio.CIOApplicationEngine$serverJob$1$2.invokeSuspend(CIOApplicationEngine.kt:67)
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)
at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:553)
at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:192)
Exception in thread "main" java.lang.ClassNotFoundException: Module function cannot be found for the fully qualified name 'io.ktorgraal.ApplicationKt$main$1.invoke'
at io.ktor.server.engine.internal.CallableUtilsKt.executeModuleFunction(CallableUtils.kt:27)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:332)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:331)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:356)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.launchModuleByName(ApplicationEngineEnvironmentReloading.kt:331)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$launchModuleByName(ApplicationEngineEnvironmentReloading.kt:30)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:319)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:310)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:338)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:310)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:143)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:277)
at io.ktor.server.cio.CIOApplicationEngine$serverJob$1$2.invokeSuspend(CIOApplicationEngine.kt:67)
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)
at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:553)
at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:192)
Repo's gradle.properties file
ktor_version=1.6.2
kotlin_version=1.5.20
...
Let me know if I can help debug this further.
Server
MultiPartData.readAllParts throws IOException when the epilogue is omitted
Stack trace:
java.io.IOException: Broken delimiter occurred
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invokeSuspend(Delimited.kt:58)
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invoke(Delimited.kt)
at io.ktor.utils.io.DelimitedKt$skipDelimiterSuspend$2.invoke(Delimited.kt)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend$suspendImpl(ByteBufferChannel.kt:1822)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend(ByteBufferChannel.kt)
at io.ktor.utils.io.DelimitedKt.skipDelimiterSuspend(Delimited.kt:56)
at io.ktor.utils.io.DelimitedKt.skipDelimiter(Delimited.kt:51)
at io.ktor.http.cio.MultipartKt$parseMultipart$1.invokeSuspend(Multipart.kt:378)
Reproducible with the following command using the attached file:
nc localhost 8080 < req.txt
[Auth] [Interceptors] Phase Phase('Challenge') was not registered for this pipeline
Hi! I want to observe and store/retrieve state parameter in Ktor OAuth. But when I started researching I found that I can't insert phases before any Authentication phases.
Here's the code from my sandbox repo:
val writeAuthPhase = PipelinePhase("WriteAuth")
routing {
route("/test") {
val testRoute = authenticate {
get {
call.respondText("123")
}
}
val item = testRoute.items.find { it.name == "Challenge" }
println(item)
testRoute.insertPhaseBefore(Authentication.ChallengePhase, writeAuthPhase)
testRoute.intercept(writeAuthPhase) {
println(call.response.headers.allValues().toMap())
proceed()
}
println(testRoute.items)
}
}
And gradle run:
> Task :run
Phase('Challenge')
[Phase('Setup'), Phase('Monitoring'), Phase('Features'), Phase('Authenticate'), Phase('WriteAuth'), Phase('Challenge'), Phase('Call'), Phase('Fallback')]
12:44:25.889 [eventLoopGroupProxy-4-1] ERROR ktor.application - Unhandled: GET - /test
io.ktor.util.pipeline.InvalidPhaseException: Phase Phase('Challenge') was not registered for this pipeline
<stack trace>
What am I doing wrong?….. And how can I retrieve `state` URL parameter from OAuth response/requests?
insertPhaseBefore and insertPhaseAfter lead to different order
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1736
Ktor Version and Engine Used (client or server and name)
1.3.2 server
Describe the bug
Using nested Feature's interceptPipeline leads to different results when using insertPhaseBefore and insertPhaseAfter.
Example:
customA { customB(1) { get { call.respond("") } } }
Both added to Child of a Route via
pipeline.insertPhaseBefore(ApplicationCallPipeline.Features, customA/B) pipeline.intercept(customA/B)
Using after:
[Phase('Setup'), Phase('Monitoring'), Phase('Features'), Phase('customB'), Phase('Call'), Phase('Fallback')]
[Phase('Setup'), Phase('Monitoring'), Phase('Features'), Phase('customA'), Phase('Call'), Phase('Fallback')]
Using before
[Phase('Setup'), Phase('Monitoring'), Phase('customA'), Phase('Features'), Phase('Call'), Phase('Fallback')]
[Phase('Setup'), Phase('Monitoring'), Phase('customB'), Phase('Features'), Phase('Call'), Phase('Fallback')]
Expected behavior
I would expect that both variants put Phase(customA) first, then Phase(customB) when merging the pipelines.
Upon googling I found the similar issue here.
Ktor 1.6.3 crashes on restart due to java.lang.ClassNotFoundException: Didn't find class "java.nio.file.WatchService" on Android 24
Ktor version: 1.6.3
Android version: 24
Engine: Netty
Exception thrown when calling `fun stop(gracePeriodMillis: Long, timeoutMillis: Long)`:
Related commit: https://github.com/ktorio/ktor/commit/b3ca535610f232d506aa2d104cd20fa1c27ba665
Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Ljava/nio/file/WatchService;
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.getWatcher(ApplicationEngineEnvironmentReloading.kt:72)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.stop(ApplicationEngineEnvironmentReloading.kt:296)
at io.ktor.server.netty.NettyApplicationEngine.stop(NettyApplicationEngine.kt:222)
at *.*.*.core.components.net.server.KtorServer.stopService(KtorServer.kt:102)
at *.*.*.core.components.net.server.KtorServer.startService(KtorServer.kt:93)
...
at java.lang.Thread.run(Thread.java:761)
Caused by: java.lang.ClassNotFoundException: Didn't find class "java.nio.file.WatchService" on path:...
Other
Logging in Shutdown thread looks not informative
Subsystem
Server
Motivation
Logging in shutdown thread looks not informative:
14:02:10 INFO Thread-1 link.kotlin.server.ApplicationFactory Going to shutdown X
Solution
Set thread name to ShutdownHook thread
Native engines tests are not run outside of the ` ktor-client-tests` module
@Test
fun test() = clientTests {
test {
assertEquals(1, 2)
}
}
always succeeds
1.6.3
released 26th August 2021
Client
Auth Feature: token refresh works only on main thread in Kotlin/Native
Ktor version:
Ktor client 1.6.2, default iOS engine
Description:
Auth feature uses global constants during token refresh process which are not marked with @ThreadLocal
annotation. This makes it impossible to perform refresh from non-main thread in Kotlin/Native targets.
Stacktrace:
Caused by: kotlin.native.IncorrectDereferenceException: Trying to access top level value not marked as @ThreadLocal or @SharedImmutable from non-main thread
at 0 shared 0x000000010774e04f kfun:kotlin.Throwable#<init>(kotlin.String?){} + 95 (/Users/teamcity2/buildAgent/work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:23:37)
at 1 shared 0x000000010774697d kfun:kotlin.Exception#<init>(kotlin.String?){} + 93 (/Users/teamcity2/buildAgent/work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:44)
at 2 shared 0x0000000107746bed kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 93 (/Users/teamcity2/buildAgent/work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:44)
at 3 shared 0x000000010777bd3d kfun:kotlin.native.IncorrectDereferenceException#<init>(kotlin.String){} + 93 (/Users/teamcity2/buildAgent/work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/Runtime.kt:30:36)
at 4 shared 0x000000010779fd43 ThrowIncorrectDereferenceException + 131 (/Users/teamcity2/buildAgent/work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt:104:11)
at 5 shared 0x0000000107938b5e CheckGlobalsAccessible + 30
at 6 shared 0x0000000107cea6a5 kfun:io.ktor.http.auth.<get-TOKEN68_EXTRA>#internal + 21 (/Users/evgenygusev/ktor/ktor-http/common/src/io/ktor/http/auth/HttpAuthHeader.kt:16:9)
at 7 shared 0x0000000107cf0169 kfun:io.ktor.http.auth.isToken68#internal + 281 (/Users/evgenygusev/ktor/ktor-http/common/src/io/ktor/http/auth/HttpAuthHeader.kt:372:106)
at 8 shared 0x0000000107cebba1 kfun:io.ktor.http.auth.matchToken68#internal + 353 (/Users/evgenygusev/ktor/ktor-http/common/src/io/ktor/http/auth/HttpAuthHeader.kt:128:61)
at 9 shared 0x0000000107ceaaca kfun:io.ktor.http.auth#parseAuthorizationHeader(kotlin.String){}io.ktor.http.auth.HttpAuthHeader? + 874 (/Users/evgenygusev/ktor/ktor-http/common/src/io/ktor/http/auth/HttpAuthHeader.kt:51:19)
at 10 shared 0x0000000107d78161 kfun:io.ktor.client.features.auth.Auth.Feature.$install$lambda-1COROUTINE$1.invokeSuspend#internal + 3425 (/Users/evgenygusev/ktor/ktor-client/ktor-client-features/ktor-client-auth/common/src/io/ktor/client/features/auth/Auth.kt:51:38)
Bearer Token is Not Initialized after Clean
InvalidMutabilityException when using withContext and SavedHttpCall
Calling HttpResponse.readText is throwing this exception.
I'm using Dispatchers.Main only.
I'm not explicitly trying to save any HTTP calls, just making one-time calls.
I've tried both Ktor 1.5.1 and 1.5.0
Coroutines 1.4.2-native-mt
Kotlin 1.4.20
Caused by: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen io.ktor.client.call.SavedHttpCall@3f8be38
at 0 jgosdk 0x00000001109e102f kfun:kotlin.Throwable#<init>(kotlin.String?){} + 95 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Throwable.kt:23:37)
at 1 jgosdk 0x00000001109d982d kfun:kotlin.Exception#<init>(kotlin.String?){} + 93 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:44)
at 2 jgosdk 0x00000001109d9a9d kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 93 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:44)
at 3 jgosdk 0x0000000110a1276d kfun:kotlin.native.concurrent.InvalidMutabilityException#<init>(kotlin.String){} + 93 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:22:60)
at 4 jgosdk 0x0000000110a13f4f ThrowInvalidMutabilityException + 431 (/Users/teamcity1/teamcity_work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:92:11)
at 5 jgosdk 0x0000000110b18e00 MutationCheck + 128
at 6 jgosdk 0x0000000110da9e38 kfun:io.ktor.client.call.SavedHttpCall.<set-responseContent>#internal + 104 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/SavedCall.kt:23:13)
at 7 jgosdk 0x0000000110daa4e4 kfun:io.ktor.client.call.SavedHttpCall.$getResponseContentCOROUTINE$7#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 1412 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/SavedCall.kt:30:13)
at 8 jgosdk 0x0000000110daa676 kfun:io.ktor.client.call.SavedHttpCall#getResponseContent(){}io.ktor.utils.io.ByteReadChannel + 246 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/SavedCall.kt:28:22)
at 9 jgosdk 0x0000000110da78eb kfun:io.ktor.client.call.HttpClientCall.$receiveCOROUTINE$5#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 2379 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/HttpClientCall.kt:82:72)
at 10 jgosdk 0x0000000110da85c2 kfun:io.ktor.client.call.HttpClientCall#receive(io.ktor.client.call.TypeInfo){}kotlin.Any + 322 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/call/HttpClientCall.kt:75:20)
at 11 jgosdk 0x0000000110dec056 kfun:io.ktor.client.statement.$readTextCOROUTINE$97#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 1942 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/statement/HttpStatement.kt:170:45)
at 12 jgosdk 0x0000000110dec4c2 kfun:io.ktor.client.statement#readText@io.ktor.client.statement.HttpResponse(io.ktor.utils.io.charsets.Charset?){}kotlin.String + 322 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/statement/HttpStatement.kt:168:16)
at 13 jgosdk 0x0000000110dec67f kfun:io.ktor.client.statement#readText$default@io.ktor.client.statement.HttpResponse(io.ktor.utils.io.charsets.Charset?;kotlin.Int){}kotlin.String + 303 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/statement/HttpStatement.kt:168:16)
Core
Server: TLSConfigBuilder.addKeyStore: store.getCertificateChain could return null
Current:
val chain = store.getCertificateChain(certAlias)
Should be:
val chain: Array<Certificate>? = store.getCertificateChain(certAlias)
Returns:
the certificate chain ... or null if the given alias does not exist or does not contain a certificate chain
Docs
java.net.MalformedURLException: unknown protocol: ws KTOR android
as stated in documentation I want to create websocket in Server and connect to it from Android my code for Android:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val client = HttpClient {
install(WebSockets)
}
lifecycleScope.launchWhenCreated {
withContext(Dispatchers.IO) {
client.ws(
method = HttpMethod.Get,
host = "ws://192.168.43.4",
port = 23569, path = ""
) { // this: DefaultClientWebSocketSession
// Send text frame.
send("Hello, Text frame")
// Send text frame.
send(Frame.Text("Hello World"))
// Receive frame.
val frame = incoming.receive()
when (frame) {
is Frame.Text -> println(frame.readText())
is Frame.Binary -> println(frame.readBytes())
}
}
}
}
}
}
but it gives the following error:
java.net.MalformedURLException: unknown protocol: ws
at java.net.URL.<init>(URL.java:597)
at java.net.URL.<init>(URL.java:487)
at java.net.URL.<init>(URL.java:436)
at io.ktor.client.engine.android.AndroidClientEngine.getProxyAwareConnection(AndroidClientEngine.kt:102)
at io.ktor.client.engine.android.AndroidClientEngine.execute(AndroidClientEngine.kt:47)
at io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:86)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Deploy WAR on Tomcat
Hello there
I want to deploy my codes into tomcat sever, so I read this guide in ktor.io
But after modifying build file, when syncing project it shows error:
Plugin [id: 'kotlin'] was not found in any of the following sources:
this is my build file
buildscript {
ext.gretty_version = '2.0.0'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.akhikhl.gretty:gretty:$gretty_version"
}
}
plugins {
id 'application'
id 'org.jetbrains.kotlin.jvm' version '1.5.20'
id 'kotlin'
id 'war'
id 'org.akhikhl.gretty'
}
webAppDirName = 'webapp'
gretty {
contextPath = '/'
logbackConfigFile = 'resources/logback.xml'
}
sourceSets {
main.kotlin.srcDirs = [ 'src' ]
main.resources.srcDirs = [ 'resources' ]
}
repositories {
mavenCentral()
}
group "http://com.md"
version "0.0.1"
mainClassName = "com.md.ApplicationKt"
dependencies {
implementation "io.ktor:ktor-server-core:$ktor_version"
implementation "io.ktor:ktor-server-tomcat:$ktor_version"
implementation "io.ktor:ktor-server-servlet:$ktor_version"
implementation "ch.qos.logback:logback-classic:$logback_version"
testImplementation "io.ktor:ktor-server-tests:$ktor_version"
}
kotlin.experimental.coroutines = 'enable'
task run
afterEvaluate {
run.dependsOn(tasks.findByName("appRun"))
}
Generator
FUS report mixes up feature id and feature version
IntelliJ IDEA Plugin
Reuse Package Search to add dependencies for Ktor Features in Plugin
The wizard missing the Pebble plugin
Wizard: Creating a project without sample code creates `Application.configureRouting` without `routing`
The code generated:
fun Application.configureRouting() {
}
Even without sample code, it's probably preferable to at least generate the following stub:
fun Application.configureRouting() {
routing {
}
}
The 'Create Run Configuration automatically' option name is cropped
- Open Settings/Preferences.
- Go to the
Preferences | Languages & Frameworks | Ktor
page.
ACT: The 'Create Run Configuration automatically' option name is cropped
EXP: The option name is fully displayed.
Marketing
Add prominent warning on Ktor EAP page about artifacts not being permanently available
I just found this line in the Ktor EAP announcement blog post: “Important – EAP artifacts will not be kept indefinitely. We’ll be cleaning them up on a frequent basis.”
Could we get this information added to https://ktor.io/eap/ please? Really frustrating that it’s not more aggresively warned about.
I updated to 2.0.0-eap-132 a while ago and now I’m worried about my app not being buildable if that artifact gets nuked.
Server
SessionTrackerById - doesn't remove invalid session id
In the load function the session id key is put into call attributes.
In line 88 it is put into attributes again with the comment "remove".
This might be wrong, it should remove the session id, if I understand the mechanics correctly.
ktor does not support semicolon query parameter in Netty Engine
In accordance with https://www.w3.org/TR/2014/REC-html5-20141028/forms.html#url-encoded-form-data semicolon is legal character in http uri, e.g ?code=01;21
should be parsed as parameter code
with value 01;21
.
As for now it is parsed as code
with value 01
and additional parameter 21
.
The problem hidden in line https://github.com/ktorio/ktor/blob/main/ktor-server/ktor-server-netty/jvm/src/io/ktor/server/netty/NettyApplicationRequest.kt#L32 which exists there for a long time and probably could be fixed by switching constructor to QueryStringDecoder(uri, HttpConstants.DEFAULT_CHARSET, true, 1024, true)
. If there is no objectives I could suggest small path for this line
Original fix in Netty https://github.com/netty/netty/issues/8855
HOCON config not resolved in ServletApplicationEngine
When deploying a WAR via servlet engine with application.conf
file containing a system variable placeholder, the config does not get resolved, resulting in this exception:
com.typesafe.config.ConfigException$NotResolved: need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: ConfigReference(${?ENV_VARIABLE})
at com.typesafe.config.impl.ConfigReference.notResolved(ConfigReference.java:33)
at com.typesafe.config.impl.ConfigReference.valueType(ConfigReference.java:40)
at com.typesafe.config.impl.SimpleConfig.hasPath(SimpleConfig.java:96)
at io.ktor.config.HoconApplicationConfig.property(HoconApplicationConfig.kt:14)
It's becauseServletApplicationEngine.kt:49/51
is missing the .resolve()
call
...
hocon.withFallback(loadedKtorConfig) // missing resolve
} else {
hocon.withFallback(ConfigFactory.load()) // missing resolve
}
while CommandLine.kt:35
is not
val combinedConfig = argConfig.withFallback(fileConfig).withFallback(environmentConfig).resolve() // resolve present
Other
Release 1.6.3
1.6.2
released 29th July 2021
Client
kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen <object>@72c18
I am trying to upload an image as multiform data using KMM
For android, it's working fine, and able to upload it. But in iOS, I can upload only text data not byteArray.
This is how I created HTTP client
expect fun httpClient(config: HttpClientConfig<*>.() -> Unit = {}): HttpClient
actual fun httpClient(config: HttpClientConfig<*>.() -> Unit) = HttpClient(Ios) {
config(this)
engine {
configureRequest {
setAllowsCellularAccess(true)
}
}
}
@ThreadLocal
val jsonSerializer = kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
useAlternativeNames = false
}
internal class KtorClient {
private val nativeClient = httpClient {
install(Logging) {
level = LogLevel.HEADERS
logger = kTorLogger
}
install(JsonFeature) {
serializer = KotlinxSerializer(json = jsonSerializer)
}
install(HttpTimeout) {
requestTimeoutMillis = 15000L
connectTimeoutMillis = 15000L
socketTimeoutMillis = 15000L
}
install(Auth) {
bearer {
loadTokens { BearerTokens(accessToken = "", refreshToken = "") }
refreshTokens { response: HttpResponse ->
BearerTokens(accessToken = "", refreshToken = "")
}
}
}
}.also {
initLogger()
}
fun getNativeClient(): HttpClient {
return nativeClient
}
}
class ApiImpl constructor(private val httpClient: HttpClient = KtorClient().getNativeClient()) :
Api {
override suspend fun testFileUpload(
data: NewTicket, fileData: FileData
): HttpResponse {
val jsonData = Json.encodeToString(data)
val formPartTicket = FormPart(
key = "data",
value = jsonData,
headers = Headers.build {
append(
HttpHeaders.ContentType,
"multipart/form-data;"
)//"application/json;charset=utf-8"
append(HttpHeaders.ContentDisposition, "form-data;name=data")
})
val formPartAttachment = FormPart(key = "attachments",
value = fileData.data,
headers = Headers.build {
append(HttpHeaders.ContentType, "image/png")
append(
HttpHeaders.ContentDisposition,
"filename=${fileData.fileName}"
)
})
val response: HttpResponse = httpClient.submitFormWithBinaryData(
url = KtorClient.baseUrl + createTicket,
formData = formData {
append(formPartTicket)
//after adding byteArray, this gets crashed. Without below apped, all works fine
append(formPartAttachment)
}
)
return response
}
}
@Serializable
data class FileData(val fileName: String?, val data: ByteArray?)
On iOS side, for testing purpose I am converting local image to byteArray
let newTicket = NewTicket(customerId: "12345",subject: subject, description: description, cfTag: cfTag, subTag: subTag)
let url = Bundle.main.url(forResource: "image", withExtension: "png")
let iosPlat = IOSPlat()
//get byteArray from data
let bytesA = try! iosPlat.toByteArray(nsData: Data.init(contentsOf: url!))
let fileData = FileData("image.png",bytesA)
//Make api call like
api.testFileUpload(newTicket,fileData) {(response,erro) in
print(response)
}
@ThreadLocal
object IOSPlat {
var autoFileData = AtomicReference(FileData(null,null).freeze())
fun isFreezed() {
Napier.d("isFreezed ${autoFileData.value.isFrozen}")
}
fun setNewFileData(fileName: String?, data: ByteArray?) {
val fileData = FileData(fileName, data.freeze()).freeze()
autoFileData.value = fileData
}
fun getFileData(): FileData {
return autoFileData.value
}
fun toByteArray(nsData: NSData): ByteArray {
val data: CPointer<ByteVar> = nsData.bytes!!.reinterpret()
val arr = ByteArray(nsData.length.toInt()) { index -> data[index] }
return arr
}
}
Exception ->
I haven't added throw to a function to get iOS crash
Exception doesn't match @Throws-specified class list and thus isn't propagated from Kotlin to Objective-C/Swift as NSError.
It is considered unexpected and unhandled instead. Program will be terminated.
Uncaught Kotlin exception: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen <object>@72c18
at 0 sharedfaq 0x000000010d40076f kfun:kotlin.Throwable#<init>(kotlin.String?){} + 95 (/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/Throwable.kt:23:37)
at 1 sharedfaq 0x000000010d3f8f9d kfun:kotlin.Exception#<init>(kotlin.String?){} + 93 (/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:44)
at 2 sharedfaq 0x000000010d3f920d kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 93 (/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:44)
at 3 sharedfaq 0x000000010d430d8d kfun:kotlin.native.concurrent.InvalidMutabilityException#<init>(kotlin.String){} + 93 (/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:22:60)
at 4 sharedfaq 0x000000010d43258f ThrowInvalidMutabilityException + 431 (/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:93:11)
at 5 sharedfaq 0x000000010d59baf0 MutationCheck + 128
at 6 sharedfaq 0x000000010d7862b8 kfun:io.ktor.utils.io.pool.SingleInstancePool.<set-instance>#internal + 104 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/pool/Pool.kt:59:13)
at 7 sharedfaq 0x000000010d786880 kfun:io.ktor.utils.io.pool.SingleInstancePool#recycle(1:0){} + 496 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/pool/Pool.kt:94:9)
at 8 sharedfaq 0x000000010d774e8f kfun:io.ktor.utils.io.core.internal.ChunkBuffer#release(io.ktor.utils.io.pool.ObjectPool<io.ktor.utils.io.core.internal.ChunkBuffer>){} + 607 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/core/internal/ChunkBuffer.kt:69:27)
at 9 sharedfaq 0x000000010d7303c8 kfun:io.ktor.utils.io.core.AbstractInput.ensureNext#internal + 776 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/core/AbstractInput.kt:717:17)
at 10 sharedfaq 0x000000010d72eea5 kfun:io.ktor.utils.io.core.AbstractInput#ensureNext(io.ktor.utils.io.core.internal.ChunkBuffer){}io.ktor.utils.io.core.internal.ChunkBuffer? + 341 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/core/AbstractInput.kt:637:67)
at 11 sharedfaq 0x000000010d72ecda kfun:io.ktor.utils.io.core.AbstractInput#ensureNextHead(io.ktor.utils.io.core.internal.ChunkBuffer){}io.ktor.utils.io.core.internal.ChunkBuffer? + 202 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/core/AbstractInput.kt:634:69)
at 12 sharedfaq 0x000000010d78080a kfun:io.ktor.utils.io.core.internal#prepareReadNextHead__at__io.ktor.utils.io.core.Input(io.ktor.utils.io.core.internal.ChunkBuffer){}io.ktor.utils.io.core.internal.ChunkBuffer? + 394 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/core/internal/Unsafe.kt:128:16)
at 13 sharedfaq 0x000000010d75deac kfun:io.ktor.utils.io.core#readFully__at__io.ktor.utils.io.core.Input(kotlin.ByteArray;kotlin.Int;kotlin.Int){} + 892 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/core/InputArrays.kt:8:13)
at 14 sharedfaq 0x000000010d77178d kfun:io.ktor.utils.io.core#readBytes__at__io.ktor.utils.io.core.ByteReadPacket(kotlin.Int){}kotlin.ByteArray + 333 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/core/Strings.kt:174:35)
at 15 sharedfaq 0x000000010d771a12 kfun:io.ktor.utils.io.core#readBytes$default__at__io.ktor.utils.io.core.ByteReadPacket(kotlin.Int;kotlin.Int){}kotlin.ByteArray + 450 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/core/Strings.kt:171:8)
at 16 sharedfaq 0x000000010d9c5e13 kfun:io.ktor.client.engine.ios.$toNSDataCOROUTINE$4#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 2275 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-ios/darwin/src/io/ktor/client/engine/ios/IosUtils.kt:21:31)
at 17 sharedfaq 0x000000010d423c10 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 800 (/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:30:39)
at 18 sharedfaq 0x000000010d6b82d0 kfun:kotlinx.coroutines.DispatchedTask#run(){} + 2880 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt:106:71)
at 19 sharedfaq 0x000000010d63dfb1 kfun:kotlinx.coroutines.EventLoop#processUnconfinedEvent(){}kotlin.Boolean + 561 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/EventLoop.common.kt:70:18)
at 20 sharedfaq 0x000000010d6b6de5 kfun:kotlinx.coroutines.internal#resumeCancellableWith__at__kotlin.coroutines.Continuation<0:0>(kotlin.Result<0:0>;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?){0§<kotlin.Any?>} + 2245 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt:294:2)
at 21 sharedfaq 0x000000010d6c4d9f kfun:kotlinx.coroutines.intrinsics#startCoroutineCancellable__at__kotlin.coroutines.SuspendFunction1<0:0,0:1>(0:0;kotlin.coroutines.Continuation<0:1>;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?){0§<kotlin.Any?>;1§<kotlin.Any?>} + 671 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt:30:74)
at 22 sharedfaq 0x000000010d629ff4 kfun:kotlinx.coroutines#startCoroutineImpl(kotlinx.coroutines.CoroutineStart;0:1;kotlin.coroutines.Continuation<0:0>;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?;kotlin.coroutines.SuspendFunction1<0:1,0:0>){0§<kotlin.Any?>;1§<kotlin.Any?>} + 628 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/Builders.common.kt:192:37)
at 23 sharedfaq 0x000000010d6dddc6 kfun:kotlinx.coroutines.startCoroutine#internal + 1222 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:140:5)
at 24 sharedfaq 0x000000010d6dd8a1 kfun:kotlinx.coroutines#startAbstractCoroutine(kotlinx.coroutines.CoroutineStart;0:1;kotlinx.coroutines.AbstractCoroutine<0:0>;kotlin.coroutines.SuspendFunction1<0:1,0:0>){0§<kotlin.Any?>;1§<kotlin.Any?>} + 417 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:111:5)
at 25 sharedfaq 0x000000010d624f02 kfun:kotlinx.coroutines.AbstractCoroutine#start(kotlinx.coroutines.CoroutineStart;0:0;kotlin.coroutines.SuspendFunction1<0:0,1:0>){0§<kotlin.Any?>} + 162 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt:130:9)
at 26 sharedfaq 0x000000010d628cca kfun:kotlinx.coroutines#async__at__kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext;kotlinx.coroutines.CoroutineStart;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.CoroutineScope,0:0>){0§<kotlin.Any?>}kotlinx.coroutines.Deferred<0:0> + 666 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/Builders.common.kt:91:15)
at 27 sharedfaq 0x000000010d629079 kfun:kotlinx.coroutines#async$default__at__kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext?;kotlinx.coroutines.CoroutineStart?;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.CoroutineScope,0:0>;kotlin.Int){0§<kotlin.Any?>}kotlinx.coroutines.Deferred<0:0> + 697 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/Builders.common.kt:82:8)
at 28 sharedfaq 0x000000010d8a4474 kfun:io.ktor.client.engine.HttpClientEngine.$executeWithinCallContextCOROUTINE$11.invokeSuspend#internal + 1732 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/engine/HttpClientEngine.kt:80:16)
at 29 sharedfaq 0x000000010d8a499f kfun:io.ktor.client.engine.HttpClientEngine.executeWithinCallContext#internal + 383 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/engine/HttpClientEngine.kt:75:21)
CoreSimulator 732.17 - Device: iPhone 11 Pro Max (AA752351-AABC-4A64-9690-95698DE9A1F6) - Runtime: iOS 14.0 (18A372) - DeviceType: iPhone 11 Pro Max
CIO WebSockets client incorrectly sends Sec-WebSocket-Extensions header even if empty
I have observed that the CIO WebSockets client is sending out a Sec-WebSocket-Extensions
header in the initial handshake even if no extension has been specified. This is causing issues with RFC 6455 compliant servers as (if my understanding is correct) the spec mandates that at least one extension be provided if the header exists (otherwise fail).
https://tools.ietf.org/html/rfc6455#section-9.1
https://tools.ietf.org/html/rfc2616#section-2.1 (ABNF notes)
I am not familiar enough with the Ktor code to submit a PR with a fix, but as a workaround a simple plugin can be used to strip the header from the request before sending it to the server:
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.util.*
/**
* Intercept outgoing HTTP requests to strip the `Sec-WebSocket-Extensions` header if it is empty.
*
* This feature should be installed after the `WebSockets` feature has been installed.
*/
internal class StripBlankSWSEHeader {
private fun stripHeader(context: HttpRequestBuilder) {
if (HttpHeaders.SecWebSocketExtensions in context.headers
&& context.headers[HttpHeaders.SecWebSocketExtensions]?.isBlank() == true) {
context.headers.remove(HttpHeaders.SecWebSocketExtensions)
}
}
class EmptyConfig
internal companion object Feature : HttpClientFeature<EmptyConfig, StripBlankSWSEHeader> {
override val key: AttributeKey<StripBlankSWSEHeader> = AttributeKey("StripEmptyWSExtensionHeader")
override fun prepare(block: EmptyConfig.() -> Unit): StripBlankSWSEHeader {
return StripBlankSWSEHeader()
}
override fun install(feature: StripBlankSWSEHeader, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.Render) {
feature.stripHeader(context)
proceed()
}
}
}
}
In the client configuration the StripBlankSWSEHeader
feature must be registered AFTER the WebSockets
feature:
install(WebSockets)
install(StripBlankSWSEHeader) // must be registered AFTER WebSockets feature
NPE in ApacheRequestProducer when "http://" is requested
The following exception is being thrown when I'm trying to send a request to "http://"
java.lang.NullPointerException
at io.ktor.client.engine.apache.ApacheRequestProducer.<init>(ApacheRequestProducer.kt:38)
at io.ktor.client.engine.apache.ApacheEngine.execute(ApacheEngine.kt:36)
at io.ktor.client.engine.HttpClientEngine$executeWithinCallContext$2.invokeSuspend(HttpClientEngine.kt:86)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Core
ByteReadChannel.readUTF8Line() indefinitely returns empty lines when `\r` is not followed by `\n`
I'm getting a weird behaviour with ByteReadChannel.readUTF8Line()
. Basically if at some point in the channel there is a \r
followed by some regular text (not by \n
), then readUTF8Line()
will keep returning empty strings indefinitely. I would expect a single \r
to either be returned as-is as part of a line (strictly respecting the current doc), or to be considered a delimiter by itself (better IMO).
Some code to reproduce:
val chan = ByteChannel(autoFlush =true)
chan.writeFully("line 1\nline 2 is the last we see\rnever printed\r\nend".toByteArray())
chan.close()
repeat(10) {
println(chan.readUTF8Line(1000))
}
println("Done")
This outputs the 2 first lines, then 8 empty lines before "Done".
Docs
parseUrlEncodedParameters Documentation
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/902
I read this tutorial https://ktor.io/advanced/utilities.html. This states that parseUrlEncodedParameters(...)
is used to parse the form-url encoded data from the request body, but then I looked at the function source, the comment said Parse URL query parameters. Shouldn't be used for urlencoded forms because of "+" character.
I assume this function is only ment to be used on the actual URL parameter, but not on the body.
I also found a function parseQueryString(...)
which produces the correct result on the body.
Is this just an oversight in the docs, or am I missing something?
OAuth authentication documentation is confusing because of incomplete code example
Example in this documentation page contains calls of extension functions: loginFailedPage
, loggedInSuccessResponse
and loginPage
which aren't defined. So a user is confused and cannot use code from that example easily.
Also, the Locations
feature is used there without an installation block and the link to its documentation is missing.
Post from the forum https://discuss.kotlinlang.org/t/trying-to-get-ktor-examples-to-run/12296.
Fix 404 errors in ktor docs
Prometeus version is missing in Ktor docs
Here is the dependency snippet and it will no compile without a version. Probably we need to define some version first and point it in text.
https://ktor.io/docs/micrometer-metrics.html#add_dependencies
Incorrect Structured Markup (LD+JSON) on Ktor docs
Confusing/incorrect JWT auth documentation
In your JWT auth documentation (https://ktor.io/servers/features/authentication/jwt.html) the following code snippet is shown:
val jwtIssuer = environment.config.property("jwt.domain").getString()
val jwtAudience = environment.config.property("jwt.audience").getString()
val jwtRealm = environment.config.property("jwt.realm").getString()
install(Authentication) {
jwt {
realm = jwtRealm
verifier(makeJwtVerifier(jwtIssuer, jwtAudience))
validate { credential ->
if (credential.payload.audience.contains(jwtAudience)) JWTPrincipal(credential.payload) else null
}
}
}
private val algorithm = Algorithm.HMAC256("secret")
private fun makeJwtVerifier(issuer: String, audience: String): JWTVerifier = JWT
.require(algorithm)
.withAudience(audience)
.withIssuer(issuer)
.build()
The confusing/incorrect part relates to the audience validation: it seems to me (but I'm new to Ktor) that the verifier already ensures that the audience is contained in the JWT token (using withAudience(audience)
, yet it is then checked a second time in the validate {} block (if (credential.payload.audience.contains(jwtAudience))
).
This second validation check seems useless here, is this true? If not, what is its purpose?
CookieConfiguration should default to secure configuration and require user opt-out
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1089
Ktor Version
master
Feedback
Currently, the configuration defaults for the Session CookieConfiguration
defaults to an insecure configuration.
The power of defaults cannot be overstated in a security context.
Instead of defaulting to being insecure, the session cookie should instead default to secure.
Requiring the user to opt-out of the security of these Cookie protections means far fewer default servers will be vulnerable to HTTP downgrade attacks exposing session cookies and will prevent XXS attacks from being leveraged to steal sessions.
This is not technically a "security vulnerability", it's just an insecure default. This is not unique to Ktor, many libraries default to this "insecure by default" model, but there's no reason why Ktor needs to help perpetuate the insecurity.
I might classify this under CWE-276: Incorrect Default Permissions.
Docs section about testing with cookies is outdated
This issue was imported from GitHub issue: https://github.com/ktorio/ktorio.github.io/issues/236
https://ktor.io/docs/testing.html#preserving-cookies
The docs mention: This method defines a session context that will hold cookies, and exposes a CookieTrackerTestApplicationEngine.handleRequest extension method to perform requests in that context..
However there is no CookieTrackerTestApplicationEngine and cookiesSession takes a lambda without parameters.
Further down it mentions: cookiesSession is not included in Ktor itself, but you can add this boilerplate to use it, but it is already included.
HTTP/2 documentation outdated
This issue was imported from GitHub issue: https://github.com/ktorio/ktorio.github.io/issues/293
The documentation states that "Unfortunately, the JDK’s TLS implementation doesn’t have support for ALPN, so your application engine must be configured properly."
However, Oracles documentation doesn't state anything of that:
Java SE 11 Security Developer’s Guide
Is the whole HTTP/2 chapter obsolete?
IntelliJ IDEA Plugin
Ktor plugin raises StackOverflowError when opening some files
Steps:
- Open the codeSnippets project: https://github.com/ktorio/ktor-documentation/tree/main/codeSnippets#readme
- Open the
codeSnippets/snippets/auth/src/io/ktor/samples/auth/OAuthLoginApplication.kt
file.
=> Ktor plugin raises StackOverflowError.
IntelliJ IDEA: 2021.2 Beta
Ktor plugin: bundled 212.4746.2
Quick action on a Application.module(...) to generate tests for a given module with all the endpoints
Server
Embedded Netty Server with watch paths is crashing in API level 22 when calling stopping server
On implementing Ktor 1.5.0 Server-Netty our application is crashing when we call server.stop method due to NoClassDefFoundError for java/nio/FileSystems . what is minimum API level supported by Ktor 1.5.0
NoSuchMethodError: No virtual method getParameterCount on Android API 25 and lower
To reproduce run an Android application on Android API level 25 with the following code:
embeddedServer(Netty, port = 3333, watchPaths = emptyList()) {
routing {
get("/") {
call.respondText { "ewq" }
}
}
}.start()
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.ktor_android, PID: 6703
java.lang.NoSuchMethodError: No virtual method getParameterCount()I in class Ljava/lang/reflect/Constructor; or its super classes (declaration of 'java.lang.reflect.Constructor' appears in /system/framework/core-oj.jar)
at io.ktor.server.engine.internal.CallableUtilsKt.executeModuleFunction(CallableUtils.kt:43)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:317)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:316)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:341)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.launchModuleByName(ApplicationEngineEnvironmentReloading.kt:316)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$launchModuleByName(ApplicationEngineEnvironmentReloading.kt:30)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:304)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:295)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:323)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:295)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:136)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:268)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:174)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:29)
at io.ktor.server.engine.ApplicationEngine$DefaultImpls.start$default(ApplicationEngine.kt:56)
at com.example.ktor_android.MainActivity.onCreate(MainActivity.kt:99)
at android.app.Activity.performCreate(Activity.java:6679)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2618)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
The code above works as expected with Ktor 1.6.0.
X-Forwarded-Port Parse Exception when it contains comma separated list of ports
KTOR-2788 introduced a parse exception if the X-Forwarded-Port contains a comma separated list of ports (For X-Forwarded-For this case is handled correctly). The result is that all affected requests return status 500 and i had to revert back to 1.6.0.
Affected line:
"ApplicationEngineEnvironment was not started" when accessing application before server is started
To reproduce run the following code:
fun main() {
val server = embeddedServer(CIO) {}
println(server.application)
}
As a result, I get an unexpected exception:
Exception in thread "main" java.lang.IllegalStateException: ApplicationEngineEnvironment was not started
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.currentApplication(ApplicationEngineEnvironmentReloading.kt:93)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.getApplication(ApplicationEngineEnvironmentReloading.kt:78)
at io.ktor.server.engine.ApplicationEngine$DefaultImpls.getApplication(ApplicationEngine.kt:48)
at io.ktor.server.engine.BaseApplicationEngine.getApplication(BaseApplicationEngine.kt:21)
at ServerKt.main(server.kt:8)
at ServerKt.main(server.kt)