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)
Other
Release 1.6.2
Update serialization version to 1.2.2
Update vulnerable versions from sonatype report
1.6.1
released 30th June 2021
Client
java.nio.charset.IllegalCharsetNameException: %s
Some users of my app are getting the following crash:
Fatal Exception: java.nio.charset.IllegalCharsetNameException: %s
at java.nio.charset.Charset.checkName(Charset.java:317)
at java.nio.charset.Charset.lookup2(Charset.java:519)
at java.nio.charset.Charset.lookup(Charset.java:497)
at java.nio.charset.Charset.forName(Charset.java:563)
at io.ktor.http.ContentTypesKt.charset(ContentTypesKt.java:271)
at io.ktor.http.HttpMessagePropertiesKt.charset(HttpMessagePropertiesKt.java:79)
at io.ktor.client.statement.HttpStatementKt.readText(HttpStatementKt.java:169)
at io.ktor.client.statement.HttpStatementKt.readText$default(HttpStatementKt.java:168)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidationKt.java:36)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(BaseContinuationImpl.java:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.java:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.java:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.java:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.java:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.java:665)
Looks like that for some reason the charset seems to be %s
. I am not sure why that is the case. However, I do not think that this should crash the app. I think it would be better if Ktor could just fall back to UTF8 when the charset is invalid. Would that be possible in Ktor?
I am using a manually built version of Ktor based on this commit: https://github.com/Thomas-Vos/ktor/commit/6dceed335d2de689dd30e3cb1d62d571c8061041
crypto is undefined in IE11
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1814
Ktor Version and Engine Used (client or server and name)
Version 1.3.2
My project uses ktor-client-core and ktor-client-core-js
Describe the bug
I have a React web application built with the kotlin-wrappers and ktor as the http client. When loading the application in IE 11, the application fails to load and the console displays an error saying: crypto is undefined
This problem happens in IE 11 on both windows 7 and windows 10.
#1283 references this same problem.
To Reproduce
Steps to reproduce the behavior:
- Start web application. Application will not render because of crypto is undefined.
Expected behavior
Application should load and run without javascript exceptions from ktor.
Screenshots
If applicable, add screenshots to help explain your problem.
Postpone (and don't cache) name resolution in cio client
*What steps will reproduce the issue?
I think we have a regression of this issue: https://github.com/ktorio/ktor/issues/1358
As we can see the current retry logic continues to use the cached network address: https://github.com/ktorio/ktor/blob/main/ktor-client/ktor-client-cio/common/src/io/ktor/client/engine/cio/Endpoint.kt#L183
- Use the CIO engine for Ktor Client
- Connect to an endpoint.
- Turn off Wifi or cause network issue somehow
- Try to connect again and get java.nio.channels.UnresolvedAddressException
5 Turn on Wifi or stop causing network issue - Try to connect again and still get java.nio.channels.UnresolvedAddressException.
What is the expected result?
Once network issues are resolved, the client should be able to connect and not keep getting java.nio.channels.UnresolvedAddressException
Other info*
Stack trace log included.
refresh token gets stuck
I'm trying to setup the new Bearer Auth functionality(Auth | Ktor) but i'm pretty much stuck.
For what i understand the loadTokens
function is to add some initial values, while refreshTokens
is to handle the actual refresh.
So i've setup my multiplatform project this way:
install(Auth) {
bearer {
loadTokens {
sessionStorage.getAuthorizationTokenData()?.let {
// it will use this tokens to authenticate calls
BearerTokens(
accessToken = it.authorizationToken,
refreshToken = it.refreshToken
)
}
}
refreshTokens { unauthorizedResponse: HttpResponse ->
// on new 401 we will need to refresh the token
sessionStorage.getAuthorizationTokenData()?.let {
// use stored refresh token to get new token
val data = getAuthUseCases().refreshToken(
RefreshTokenRequestValue(
clientId = platform.clientId,
refreshToken = it.refreshToken
)
)
// save new tokens
sessionStorage.setAuthorizationTokenData(
AuthToken(
authorizationToken = data.token,
refreshToken = data.refreshToken
)
)
return@refreshTokens BearerTokens(
accessToken = data.token,
refreshToken = data.refreshToken
)
}
}
}
the actual refresh is done by using the ktor client itself so it is something along these lines
suspend fun refreshToken(
clientId: String,
refreshToken: String
): PostRefreshTokenResponse = ktorClient.post(
request = ApiRequest(
baseUrl = "http://example.com",
relativeUrl = "/auth/refresh",
body = PostRefreshTokenRequest(
clientId = clientId,
refreshToken = refreshToken
)
)
)
The authentication part is working ok, the refresh block is called, but whenever the token expires and i get another 401 the actual refresh is stuck, the function `refreshToken` is invoked, but never actually executed.
When trying to actually return only a BearerTokens
object it runs ok (but of course without a refreshed token) .
Core
ApplicationEngineEnvironmentBuilder.module { … } is executed twice on Exception
When initializing the application with applicationEngineEnvironment(…)
any exception on module { … }
will lead to the block being executed twice. This may lead to unexpected results.
For example:
val env = applicationEngineEnvironment {
config = HoconApplicationConfig(ConfigFactory.load())
module {
install(DefaultHeaders) {
header(HttpHeaders.Server, "My Server")
}
// simulate that something goes wrong
throw Exception("I am hidden.")
}
connector {
host = config.property("deployment.host").getString()
port = config.property("deployment.port").getString().toInt()
}
}
embeddedServer(Netty, env).start(true)
Here the real exception "I am hidden" is shadowed by
io.ktor.application.DuplicateApplicationFeatureException: Conflicting application feature is already installed with the same key as `Default Headers`
since the feature is installed twice.
The originates from this code in ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication
:
try {
launchModuleByName(name, currentClassLoader, newInstance)
} catch (_: Throwable) {
module(newInstance)
}
I would expect this to only catch exceptions specific to launchModuleByName
or handle errors differently.
The '-ea' flag works differently when running a server using Application.module and embeddedServer
According to KTOR-1055, the -ea
flag enables the development mode but works differently for running a server using Application.module
and embeddedServer
:
- Open the attached project in IDEA, uncomment the part for running a server with
EngineMain.main
. - Right-click the gutter icon next to
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
and select 'Modify Run Configuration'. - Add
-ea
to theVM options
field, and click OK. - Run the configuration using the
Run
button on the toolbar. - Open the web page:
http://0.0.0.0:8080
- Change
respondText
, rebuild the project, and refresh the page => all works as expected. - Now repeat the same steps for running the server with
embeddedServer
=> auto-reloading doesn't work when usingRun
in IDEA - it works only when running the configuration with theDebug
button.
P.S. The same behaviour for -Dio.ktor.development=true
passed to VM options
.
Docs
Full example for configuring response validation is missing
Link back to site from Docs
From docs it's not possible to go back to the home page for Ktor. Can we please add a link somewhere? Be it in the top left corner for instance on the Ktor logo?
Update ktor 1.5.0 docs. Deprecated "challenge" function for form auth in docs.
https://ktor.io/docs/basic.html#usage
Text from FormAuthenticationProvider class code.
/**
* Redirect to an URL provided by the given function.
* @property url is a function receiving [ApplicationCall] and [UserPasswordCredential] and returning an URL to redirect to.
*/
@Deprecated("Use challenge {} instead.", level = DeprecationLevel.ERROR)
public class Redirect(public val url: ApplicationCall.(UserPasswordCredential?) -> String) : FormAuthChallenge()
/**
* Respond with [HttpStatusCode.Unauthorized].
*/
@Deprecated("Use challenge {} instead.", level = DeprecationLevel.ERROR)
public object Unauthorized : FormAuthChallenge()
Auth Feature Code Snippet: form authentication the doesn't work
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/211
I've the below code, route "/" and "/bye" are working fine, but route "login" given blank page once!!
package blog
import kotlinx.html.*
import kotlinx.html.stream.* // for createHTML
import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.auth.*
import org.jetbrains.ktor.features.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.response.*
import org.jetbrains.ktor.routing.*
import org.jetbrains.ktor.request.* // for request.uri
import org.jetbrains.ktor.html.*
import org.jetbrains.ktor.pipeline.*
import org.jetbrains.ktor.host.* // for embededServer
import org.jetbrains.ktor.netty.* // for Netty
fun main(args: Array<String>) {
embeddedServer(Netty, 8080, watchPaths = listOf("BlogAppKt"), module = Application::module).start()
}
fun Application.module() {
install(DefaultHeaders)
install(CallLogging)
intercept(ApplicationCallPipeline.Call) {
if (call.request.uri == "/hi")
call.respondText("Test String")
}
install(Routing) {
get("/") {
call.respondText("""Hello, world!<br><a href="/bye">Say bye?</a>""", ContentType.Text.Html)
}
get("/bye") {
call.respondText("""Good bye! <br><a href="/login">Login?</a> """, ContentType.Text.Html)
}
route("/login") {
authentication {
formAuthentication { up: UserPasswordCredential ->
when {
up.password == "ppp" -> UserIdPrincipal(up.name)
else -> null
}
}
}
handle {
val principal = call.authentication.principal<UserIdPrincipal>()
if (principal != null) {
call.respondText("Hello, ${principal.name}")
} else {
val html = createHTML().html {
body {
form(action = "/login", encType = FormEncType.applicationXWwwFormUrlEncoded, method = FormMethod.post) {
p {
+"user:"
textInput(name = "user") {
value = principal?.name ?: ""
}
}
p {
+"password:"
passwordInput(name = "pass")
}
p {
submitInput() { value = "Login" }
}
}
}
}
call.respondText(html, ContentType.Text.Html)
}
}
}
}
}
Review documentation for the onUpload/onDownload client callbacks
Generator
Application.module code contains annotations and comments that make no sense
Add start.ktor.io to allowed hosts of the generator backend
Selecting custom package name in Ktor wizard still results in example.com import in ApplicationTest.kt
Infrastructure
Add watchosX64 as an architecture
The new simulator requires watchosX64 (added in Kotlin 1.5.0). Please add it to the supported architectures
Unbound public symbol for public io.ktor.network.sockets/SocketTimeoutException when iosArm64 framework
When declaring a dependency on Ktor io.ktor:ktor-network
and io.ktor:ktor-client-ios
, this leads to a compiler error on iosArm64
. Tested using kotlin 1.4.31
and ktor 1.5.2
I created a minimal reproducer here:
https://github.com/PaulWoitaschek/KtorSocketBug
To reproduce it, run ./gradlew linkDebugFrameworkIosArm64
> Task :linkDebugFrameworkIosArm64 FAILED
e: Compilation failed: Unbound symbols not allowed
Unbound public symbol for public io.ktor.network.sockets/SocketTimeoutException.<init>|-4324620762574333298[0] public io.ktor.network.sockets/SocketTimeoutException.<init>|-4324620762574333298[0]
* Source files:
* Compiler version info: Konan: 1.4.31 / Kotlin: 1.4.31
* Output kind: FRAMEWORK
e: java.lang.AssertionError: Unbound symbols not allowed
Unbound public symbol for public io.ktor.network.sockets/SocketTimeoutException.<init>|-4324620762574333298[0] public io.ktor.network.sockets/SocketTimeoutException.<init>|-4324620762574333298[0]
at org.jetbrains.kotlin.ir.util.SymbolTableKt.noUnboundLeft(SymbolTable.kt:1119)
at org.jetbrains.kotlin.psi2ir.Psi2IrTranslator.generateModuleFragment(Psi2IrTranslator.kt:89)
at org.jetbrains.kotlin.backend.konan.PsiToIrKt.psiToIr(PsiToIr.kt:160)
at org.jetbrains.kotlin.backend.konan.ToplevelPhasesKt$psiToIrPhase$1.invoke(ToplevelPhases.kt:135)
at org.jetbrains.kotlin.backend.konan.ToplevelPhasesKt$psiToIrPhase$1.invoke(ToplevelPhases.kt)
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$namedOpUnitPhase$1.invoke(PhaseBuilders.kt:97)
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$namedOpUnitPhase$1.invoke(PhaseBuilders.kt:95)
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:94)
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:30)
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:94)
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:41)
at org.jetbrains.kotlin.backend.konan.KonanDriverKt.runTopLevelPhases(KonanDriver.kt:29)
at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:78)
at org.jetbrains.kotlin.cli.bc.K2Native.doExecute(K2Native.kt:35)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:88)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:76)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:45)
at org.jetbrains.kotlin.cli.common.CLITool$Companion.doMainNoExit(CLITool.kt:227)
at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:286)
at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:270)
at org.jetbrains.kotlin.util.UtilKt.profileIf(Util.kt:27)
at org.jetbrains.kotlin.util.UtilKt.profile(Util.kt:21)
at org.jetbrains.kotlin.cli.bc.K2Native$Companion.mainNoExitWithGradleRenderer(K2Native.kt:285)
at org.jetbrains.kotlin.cli.bc.K2NativeKt.mainNoExitWithGradleRenderer(K2Native.kt:485)
at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1.invoke(main.kt:43)
at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1.invoke(main.kt)
at org.jetbrains.kotlin.cli.utilities.MainKt.mainImpl(main.kt:17)
at org.jetbrains.kotlin.cli.utilities.MainKt.daemonMain(main.kt:43)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.runInProcess(KotlinToolRunner.kt:124)
at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.run(KotlinToolRunner.kt:73)
at org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile.compile(KotlinNativeTasks.kt:334)
at org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink.compile(KotlinNativeTasks.kt:657)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$2.run(ExecuteActionsTaskExecuter.java:494)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:479)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:462)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$400(ExecuteActionsTaskExecuter.java:105)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.executeWithPreviousOutputFiles(ExecuteActionsTaskExecuter.java:273)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:251)
at org.gradle.internal.execution.steps.ExecuteStep.lambda$executeOperation$1(ExecuteStep.java:66)
at java.base/java.util.Optional.orElseGet(Optional.java:362)
at org.gradle.internal.execution.steps.ExecuteStep.executeOperation(ExecuteStep.java:66)
at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:34)
at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:47)
at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:44)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:44)
at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:34)
at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:72)
at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:42)
at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:53)
at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:39)
at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:44)
at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:77)
at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:58)
at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:54)
at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:32)
at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:57)
at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:38)
at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:63)
at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:30)
at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:176)
at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:76)
at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:47)
at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:43)
at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:32)
at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:39)
at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:25)
at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:102)
at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:95)
at java.base/java.util.Optional.map(Optional.java:258)
at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:55)
at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:39)
at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:83)
at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:44)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:96)
at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:52)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:83)
at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:54)
at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:74)
at org.gradle.internal.execution.steps.SkipEmptyWorkStep.lambda$execute$2(SkipEmptyWorkStep.java:88)
at java.base/java.util.Optional.orElseGet(Optional.java:362)
at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:88)
at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:34)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:46)
at org.gradle.internal.execution.steps.LoadExecutionStateStep.execute(LoadExecutionStateStep.java:34)
at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:43)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution$3.withWorkspace(ExecuteActionsTaskExecuter.java:286)
at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:43)
at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:33)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:40)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:30)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:54)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:40)
at org.gradle.internal.execution.impl.DefaultExecutionEngine.execute(DefaultExecutionEngine.java:41)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:183)
at java.base/java.util.Optional.orElseGet(Optional.java:362)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:183)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:173)
at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:109)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:62)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:41)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:411)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:398)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:391)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:377)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
at java.base/java.lang.Thread.run(Thread.java:832)
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':linkDebugFrameworkIosArm64'.
> Compilation finished with errors
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 11s
2 actionable tasks: 2 executed
IntelliJ IDEA Plugin
Endpoints View: Endpoints no longer populate in KTOR project
I recently updated my KTOR project to 1.6 and my KTOR plugin to version 1.5.6, and after restarting IntelliJ, the Endpoints View is now completely blank, even though IntelliJ correctly identifies the route
calls as endpoints.
This definitely used to work, and was super helpful.
Lots of Run Configurations Created for Ktor Project with the Similar Names
KtorTestIntentionImpl doesn't have description
-
It's highlighted in code
-
It throws exceptions on each search:
java.lang.RuntimeException: Intention Description Dir URL is null: Create test; KtorTestIntentionImpl; while looking for description.html at com.intellij.codeInsight.intention.impl.config.IntentionActionMetaData.getResourceLocation(IntentionActionMetaData.java:66) at com.intellij.codeInsight.intention.impl.config.BeforeAfterActionMetaData.getDescription(BeforeAfterActionMetaData.java:125) at com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings.processMetaData(IntentionManagerSettings.java:187)
Import `kotlin.test` is not resolved in generated project
Unable to run new Ktor project
Environment details:
IntelliJ IDEA 2021.1 (Ultimate Edition)
Build #IU-211.6693.111, built on April 6, 2021
Runtime version: 11.0.10+9-b1341.35 aarch64
VM: Dynamic Code Evolution 64-Bit Server VM by JetBrains s.r.o.
macOS 11.2.3
GC: G1 Young Generation, G1 Old Generation
Memory: 2048M
Cores: 8
Non-Bundled Plugins: mobi.hsz.idea.gitignore (4.1.0), com.github.beansoft.reatnative.idea.free (2020.2), intellij.ktor (1.5.3), Dart (211.7179), io.flutter (55.1.5), com.jetbrains.php (211.6693.120), org.jetbrains.plugins.go (211.6693.111)
Kotlin: 211-1.4.32-release-IJ6693.72
KTOR - 1.5.3
Created new project from KTor wizard.
Added
- Authentication JWT - this is adding automatically "Routing" & "Authentication"
- Authentication OAuth - this is adding automatically "Locations"
- Status Pages
- Compression
- CORS
- CallLogging
- Metrics
- Kotlinx.serialization - this is adding automatically "ContentNegotiation"
- RawSockets
Result :
At compilation stage :
Exception in thread "main" io.ktor.application.DuplicateApplicationFeatureException: Conflicting application feature is already installed with the same key as "Locations"
Removing :
install(Locations) {
}
from Routing.kt
creates error :
Exception in thread "main" io.ktor.application.MissingApplicationFeatureException: Application feature Locations is not installed
Research shared indexes for Ktor
Generated project with specific security and session features selected fails to compile / run
Created selecting a bunch of features. Security.kt imports Auth0 which is not referenced.
In addition
- makeJwtVerifier is not implemented
- MySession is not implemented
Wizard: Misleading comment in Static Feature
The sample code says:
// Static feature. Try to access `/static/ktor_logo.svg`
...but no ktor_logo.svg is included.
Server
Support X-Forwarded-Port header in XForwardedHeaderSupport plugin
It would be nice when the XForwardedHeaderSupport plugin recognized the X-Forwarded-Port
header. That's one of the default headers set by Amazon load balancers.
Currently call.request.origin.port
is only based on the scheme as parsed from various other X-Forwarded
headers. When the X-Forwarded-Port
header is present its value should be used as the port number instead.
configureBootstrap hook overwritten by Ktor settings
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1909
Subsystem
ktor-server-netty
Is your feature request related to a problem? Please describe.
I'm trying to add some GlobalTrafficShapingHandler to ktor, but I cannot do anything because Ktor overwrites what I am trying to do in the configureBootstrap
lambda. I cannot set the childHandler or the channelFactory , or even influence what they do in any way. I cannot touch childHandler nor channelFactory (it's set via the channel()
call).
ServerBootstrap().apply {
configuration.configureBootstrap(this)
group(connectionEventGroup, workerEventGroup)
channel(connectionEventGroup.channel.java)
childHandler(
NettyChannelInitializer(
pipeline, environment,
callEventGroup, engineDispatcherWithShutdown, dispatcherWithShutdown,
connector,
configuration.requestQueueLimit,
configuration.runningLimit,
configuration.responseWriteTimeoutSeconds,
configuration.requestReadTimeoutSeconds,
configuration.httpServerCodec
)
)
}
Describe the solution you'd like
I'd want a way to expose the underlying NettyPipeline, or at least have some way to meaninfully use the configureBootstrap
hook. I would also take a hook called in override fun initChannel(ch: SocketChannel) {
as that would let me do the configuration I want.
Motivation to include to ktor
Ktor already takes effort to expose this hook, but I've found the things I really want to do with this hook aren't allowed.
StatusPages doesn't catch FreeMarker exceptions
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1941
Ktor Version and Engine Used (client or server and name)
1.3.2, Netty server, FreeMarker and StatusPages
Describe the bug
If an exception is thrown while generating a html page using FreeMarker (ie. FreeMarker throws a TemplateException), the StatusPages feature does not consume it. I've tried catching Throwable and TemplateException:
exception<TemplateException> {
Rollbar.error("Error generating page: ", it)
call.respond(FreeMarkerContent(
"error.ftl",
mapOf("data" to ErrorData(
500,
"Oops! An error occurred while we were generating that page!",
"Hit the back button on your browser and try again."
))
))
}
exception<Throwable> {
Rollbar.error(null, it)
call.respond(FreeMarkerContent(
"error.ftl",
mapOf("data" to ErrorData(
500,
"Oops! Something went wrong on our end!",
it.localizedMessage
))
))
}
Adding application startup and hot-reloading time log
Unhandled get freezes with `CIO` server
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1962
Ktor Version and Engine Used (client or server and name)
Server CIO
, 1.3.2
Describe the bug
If we don't call call.respond
the server freezes and holds the connection with CIO
server(without any message)
The Netty
treats this sample as correct response and respond with status code Unauthorized
To Reproduce
Make the request with CIO
server:
get("unauthorized") {
call.response.status(HttpStatusCode.Unauthorized)
call.response.header(HttpHeaders.WWWAuthenticate, "Basic realm=\"TestServer\", charset=UTF-8")
}
Expected behavior
Respond with 5xx Internal server error
or a correct response in that case
JWK Public Key of type "EC"
Ktor supports the use of ES256, ES384, and ES512 as JWK algorithm, but the used library jwks-rsa-java doesn't support the key type EC.
This results in an exception InvalidPublicKeyException, when Ktor tries to create the public key. Is there an option/workaround to use EC encryption?
Enabled-by-default development mode breaks reflection by overriding classloader
I use reflection via Reflections for discovering implementations of services inside my module.
However, Ktor's development mode uses an OverridingClassLoader
, which seems to break things.
The following snippet...
val matchers = ref.getSubTypesOf(UrlMatcher::class.java).map { cl ->
val new = cl.getDeclaredConstructor().newInstance()
new
}
...now tries to cast a class across class loader boundaries, which is not allowed on the JVM:
Caused by: java.lang.ClassCastException: class io.sebi.urldecoder.urlmatcher.ArdMatcher cannot be cast to class io.sebi.urlmatcher.UrlMatcher (io.sebi.urldecoder.urlmatcher.ArdMatcher is in unnamed module of loader 'app'; io.sebi.urlmatcher.UrlMatcher is in unnamed module of loader io.ktor.server.engine.OverridingClassLoader$ChildURLClassLoader @37f1104d)
This was a super confusing issue for me. I ended up solving it by turning off the development mode in my application.conf
:
ktor {
development = false
}
Test Infrastructure
ktor-server-test-host depends on kotlin-test-junit and junit
This breaks any build using kotlin-test-junit5, requiring an exclude to get around:
> Error while evaluating property 'filteredArgumentsMap' of task ':compileTestKotlin'
> Could not resolve all files for configuration ':testCompileClasspath'.
> Could not resolve org.jetbrains.kotlin:kotlin-test-junit5:1.5.10.
Required by:
project :
> Module 'org.jetbrains.kotlin:kotlin-test-junit5' has been rejected:
Cannot select module with conflict on capability 'org.jetbrains.kotlin:kotlin-test-framework-impl:1.5.10' also provided by [org.jetbrains.kotlin:kotlin-test-junit:1.5.10(junitApi)]
> Could not resolve org.jetbrains.kotlin:kotlin-test-junit:1.5.10.
Required by:
project : > io.ktor:ktor-server-test-host:1.6.0
> Module 'org.jetbrains.kotlin:kotlin-test-junit' has been rejected:
Cannot select module with conflict on capability 'org.jetbrains.kotlin:kotlin-test-framework-impl:1.5.10' also provided by [org.jetbrains.kotlin:kotlin-test-junit5:1.5.10(junit5Api)]
https://github.com/ktorio/ktor/blob/main/ktor-server/ktor-server-test-host/build.gradle.kts#L34 and the line below it for junit should probably not be api
since it's not appropriate for ktor-server-test-host
to be choosing the test framework for the user.
Other
Double host header
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1867
Ktor Version and Engine Used (client or server and name)
io.ktor:ktor-server-cio:1.3.2
Describe the bug
Double Host header seems to be invalid and should fail
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() {
test(
"""
GET / HTTP/1.1
Host: www.example.com
Host: www.example2.com
""".trimIndent()
)
}
fun test(http: String) {
val request = runBlocking { parseRequest(ByteReadChannel(http)) }!!
println("method=${request.method};version=${request.version};uri={${request.uri}};headers=[${request.headers}]")
}
Expected behavior
An error expected - exception or null result.
Release 1.6.1
Publish Ktor to NPM
Improve diagnostics for exceptions inherited from IOException
Certain exceptions, like ConnectException
is caught by IOException
filter in logFailure
, and produce unhelpful "io failed" message. This should be improved to provide actual exception type and message.
developmentMode is on by default in tests
Hi,
First of all, thank you for creating and maintaining ktor, we enjoy using it a lot! The issue we notice that after v1.5.2, developmentMode value is set as true by default in tests (more precisely it is on when assertions are enabled) -> https://github.com/ktorio/ktor/blob/de742d1adfbbb994bedda198e6739c724e62a974/ktor-utils/jvm/src/io/ktor/util/PlatformUtils.kt#L15-L16
This issue causes our test cases to fail since, in developmentMode, the application is started in a different classloader which leads to some problems with our test setup. We had to override the `io.ktor.development` value to solve it for now.
We believe that this behavior is unintended and a bug. Could you please take a look and provide a better developmentMode resolving method?
Update CONTRIBUTING.md
In PullRequests process, it guides us to create a new PR with a request to merge to the master branch.
But the default branch name is changed to main.
Try to fix it.
Use kotlin.reflect.jvm.javaType instead of the type token pattern in io.ktor.util.reflect.typeInfo
Please consider using kotlin.reflect.javaType
instead of the type token-like approach in https://github.com/ktorio/ktor/blob/554b8223c5da0fbd0f3ecdd273e74459b8f009e7/ktor-utils/jvm/src/io/ktor/util/reflect/TypeInfoJvm.kt#L17.
The main benefit of using javaType
is that it doesn't produce an anonymous class for each call site.
Also, we'd like to get some feedback for javaType
before graduating it to stable in a future Kotlin release (we're planning to stabilize typeOf
in 1.6 (KT-45396), javaType
will likely follow in a subsequent release). That said, currently it doesn't support some corner cases which might be critical for Ktor, which I don't know. Those are listed in the kdoc or the link above.
Another aspect of it is that sometimes there are Kotlin compiler bugs that prevent correct reification of type parameters in anonymous objects (e.g. KT-46797), and typeOf
+ javaType
is supposed to be much more stable in this regard. The compiler bugs still need to be fixed, but if this code uses javaType
, at least Ktor users won't suffer from those bugs, like in KT-46797.
1.6.0
released 28th May 2021
Client
Ktor fails to deliver response with error: failed with exception: kotlinx.coroutines.JobCancellationException: Parent job is Completed;
Kotlin version 1.5.4
Issue: API Call failed with reason: failed with exception: kotlinx.coroutines.JobCancellationException: Parent job is Completed;
Hi there, this piece of code is causing an exception:
ktor/ktor-client/ktor-client-core/common/src/io/ktor/client/features/observer/ResponseObserver.kt
@Suppress("UNCHECKED_CAST")
(response.coroutineContext[Job] as CompletableJob).complete()
proceedWith(context.response)
Even though I am not a coroutine expert I suspect that when HttpClientCall -> receive method is called and more specifically the part below, the job is already completed and this is causing the exception to be thrown.
This is a quite major issue because it results in randomly losing API call responses.
val result = currentClient.responsePipeline.execute(this, subject).response
ref: https://github.com/ktorio/ktor/blob/764cad998700430f54f3e6c77edb53159d75d7c3/ktor-client/ktor-client-core/common/src/io/ktor/client/call/HttpClientCall.kt#L74
Is there a workaround or a fix?
HTTP-client auth with Bearer token
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1967
Subsystem
Client/Server, particular relevant ktor module(s) if applicable.
ktor HTTP-client, auth
Is your feature request related to a problem? Please describe.
Currently, Ktor HTTP-client supports only 2 types of providers: Basic and Digest. But in a real-life project is a standard to use Bearer token pair - access and refresh. So it will be great to add support of Bearer tokens to multiplatform Ktor's HTTP-client.
So the problem itself can be split into 4 sub-tasks:
- Persist token pair in a safe place
- Add them to auth headers
- In case when the request with access token returns 401,
3.1. pause all present requests
3.2 refresh it with refresh token.. and so on, regular flow.
3.3 restart interrupted requests from 3.1
Describe the solution you'd like
Create BearerAuthConfig, BearerAuthProvider like its Basic and Digest versions. Update Auth class to be able to save, load and refresh tokens. Something like this:
fun provide(): HttpClient = HttpClient {
install(Auth) {
bearer {
refresh = TODO("lambda function that get new access token and return a string"),
revoke = TODO("lambda function that removes tokens")
}
}
}
Motivation to include to ktor
Add support of Bearer tokens for clients.
Occasional "Channel has been cancelled" errors when using OkHttp
I haven't been able to repro this.
Ktor 1.4.0
OkHttp 4.8.0
Okio 2.7.0
java.util.concurrent.CancellationException: Channel has been cancelled
at io.ktor.utils.io.ByteBufferChannel.cancel(ByteBufferChannel.kt:152)
at io.ktor.utils.io.ByteReadChannelKt.cancel(ByteReadChannel.kt:225)
at io.ktor.utils.io.jvm.javaio.InputAdapter.close(Blocking.kt:66)
at okio.InputStreamSource.close(JvmOkio.kt:108)
at kotlin.io.CloseableKt.closeFinally(Closeable.kt:60)
at io.ktor.client.engine.okhttp.StreamRequestBody.writeTo(StreamRequestBody.kt:20)
at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.kt:59)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:517)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
java.util.concurrent.CancellationException: Channel has been cancelled
at java.lang.reflect.Constructor.newInstance0(Constructor.java)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at io.ktor.utils.io.ExceptionUtilsJvmKt$createConstructor$$inlined$safeCtor$3.invoke(ExceptionUtilsJvm.kt:105)
at io.ktor.utils.io.ExceptionUtilsJvmKt$createConstructor$$inlined$safeCtor$3.invoke
at io.ktor.utils.io.ExceptionUtilsJvmKt.tryCopyException(ExceptionUtilsJvm.kt:65)
at io.ktor.utils.io.ByteBufferChannelKt.rethrowClosed(ByteBufferChannel.kt:2594)
at io.ktor.utils.io.ByteBufferChannelKt.access$rethrowClosed(ByteBufferChannel.kt:1)
at io.ktor.utils.io.ByteBufferChannel.setupStateForRead(ByteBufferChannel.kt:268)
at io.ktor.utils.io.ByteBufferChannel.access$setupStateForRead(ByteBufferChannel.kt:23)
at io.ktor.utils.io.ByteBufferChannel.readAsMuchAsPossible(ByteBufferChannel.kt:2667)
at io.ktor.utils.io.ByteBufferChannel.readAvailable$suspendImpl(ByteBufferChannel.kt:605)
at io.ktor.utils.io.ByteBufferChannel.readAvailable
at io.ktor.utils.io.jvm.javaio.InputAdapter$loop$1.loop(Blocking.kt:33)
at io.ktor.utils.io.jvm.javaio.InputAdapter$loop$1$loop$1.invokeSuspend
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at io.ktor.utils.io.jvm.javaio.UnsafeBlockingTrampoline.dispatch(Blocking.kt:284)
at kotlinx.coroutines.DispatchedContinuation.resumeWith(DispatchedContinuation.kt:184)
at io.ktor.utils.io.jvm.javaio.BlockingAdapter.submitAndAwait(Blocking.kt:221)
at io.ktor.utils.io.jvm.javaio.BlockingAdapter.submitAndAwait(Blocking.kt:194)
at io.ktor.utils.io.jvm.javaio.InputAdapter.read(Blocking.kt:60)
at okio.InputStreamSource.read(JvmOkio.kt:90)
at okio.RealBufferedSink.writeAll(RealBufferedSink.kt:180)
at io.ktor.client.engine.okhttp.StreamRequestBody.writeTo(StreamRequestBody.kt:21)
at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.kt:59)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:517)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
Sporadic OkHttp errors after upgrading to ktor 1.3.1
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1708
Ktor Version and Engine Used (client or server and name)
implementation("io.ktor:ktor-client-core-jvm:1.3.1")
implementation("io.ktor:ktor-client-core:1.3.1")
implementation("io.ktor:ktor-client-jackson:1.3.1")
implementation("io.ktor:ktor-client-logging-jvm:1.3.1")
implementation("io.ktor:ktor-client-okhttp:1.3.1")
implementation("io.ktor:ktor-jackson:1.3.1")
implementation("io.ktor:ktor-metrics-micrometer:1.3.1")
implementation("io.ktor:ktor-metrics:1.3.1")
implementation("io.ktor:ktor-server-host-common:1.3.1")
implementation("io.ktor:ktor-server-netty:1.3.1")
Describe the bug
We've started seeing sporadic OkHttp exceptions in our tests when we upgraded from 1.3.0
to 1.3.1
. Upon downgrading back to 1.3.0
(with no other changes) the tests were fine again.
The exception is:
java.io.EOFException: \n not found: limit=0 content=…
at o.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:240)
at o.i.h.Http1ExchangeCodec.readHeaderLine(Http1ExchangeCodec.java:242)
at o.i.h.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.java:213)
... 20 common frames omitted
Wrapped by: java.io.IOException: unexpected end of stream on http://called-service/...
at o.i.h.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.java:236)
at o.i.c.Exchange.readResponseHeaders(Exchange.java:115)
at o.i.h.CallServerInterceptor.intercept(CallServerInterceptor.java:94)
at o.i.h.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at o.i.c.ConnectInterceptor.intercept(ConnectInterceptor.java:43)
at o.i.h.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at o.i.h.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at o.i.c.CacheInterceptor.intercept(CacheInterceptor.java:94)
at o.i.h.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at o.i.h.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at o.i.h.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at o.i.h.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at o.i.h.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
at o.i.h.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at o.i.h.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:221)
at o.RealCall$AsyncCall.execute(RealCall.java:172)
at o.i.NamedRunnable.run(NamedRunnable.java:32)
at j.u.c.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at j.u.c.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.lang.Thread.run(Thread.java:834)
It looks like the HTTP client receives an EOF while reading the response headers from calling http://called-service/... I don't think the culprit is the remote web server though, because this server is called from other services and it's only the one with ktor 1.3.1 that throws these errors. And this one is also fine with ktor 1.3.0.
This issue has already been raised before in the OkHttp repository, for example here.
From what I can see, both ktor 1.3.0
and 1.3.1
both depend on com.squareup.okhttp3:okhttp:3.14.2
, so maybe this is due to a change in the way ktor uses OkHttp?
To Reproduce
Not easy to reproduce, unfortunately, because the error only occurs in a small percentage of requests (~5%). I'm hoping someone else observes this issue and can come up with a reproducible test.
When the main thread executes runBlocking, using the iOS engine will cause a deadlock
When executing Unit Test, runBlocking must be executed to call suspend.
But IosClientEngine.kt
specifies the callback thread as the main thread when creating NSURLSession. Once the main thread is locked, this callback cannot return.
Should change
val session = NSURLSession.sessionWithConfiguration(
configuration,
responseReader.freeze(),
delegateQueue = NSOperationQueue.mainQueue()
)
to
val session = NSURLSession.sessionWithConfiguration(
configuration,
responseReader.freeze(),
delegateQueue = NSOperationQueue.currentQueue()
)
'%3D' inside query of redirect target location will be replaced to '='
When the server returns 302 with the URL has '%3D' (inside query part), the Ktor replaces '%3D' with '=' so some server returns invalid content (in this case, 403).
Test code:
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.runBlocking
fun main(): Unit = runBlocking {
val url = "https://dl.bintray.com/anatawa12/maven/com/anatawa12/forge/ForgeGradle/1.2-1.0.0/ForgeGradle-1.2-1.0.0-javadoc.jar"
// also in CIO
//val factory = io.ktor.client.engine.cio.CIO
val factory = io.ktor.client.engine.apache.Apache
val clientNoFollow = HttpClient(io.ktor.client.engine.cio.CIO) {
followRedirects = false
expectSuccess = false
}
val response = clientNoFollow.get<HttpResponse>(url)
println("status: " + response.status)
println("location: " + response.headers["Location"])
println("location has %3D: " + response.headers["Location"]!!.contains("%3D"))
check(response.headers["Location"]!!.contains("%3D"))
val clientWithFollow = HttpClient(io.ktor.client.engine.cio.CIO)
try {
val got = clientWithFollow.get<ByteArray>(url)
} catch (e: ResponseException) {
println("error: " + e)
println("error url: " + e.response.request.url)
println("error url has %3D: " + e.response.request.url.toString().contains("%3D"))
}
}
Log:
status: 302
location: https://d29vzk4ow07wi7.cloudfront.net/d133d0ab57f0e1a996fa1367eb3e5c7ebbd1860a5d505a69791d7746457acb0a?response-content-disposition=attachment%3Bfilename%3D%22ForgeGradle-1.2-1.0.0-javadoc.jar%22&Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHAqOi8vZDI5dnprNG93MDd3aTcuY2xvdWRmcm9udC5uZXQvZDEzM2QwYWI1N2YwZTFhOTk2ZmExMzY3ZWIzZTVjN2ViYmQxODYwYTVkNTA1YTY5NzkxZDc3NDY0NTdhY2IwYT9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPWF0dGFjaG1lbnQlM0JmaWxlbmFtZSUzRCUyMkZvcmdlR3JhZGxlLTEuMi0xLjAuMC1qYXZhZG9jLmphciUyMiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTYxMjkzNzUyOX0sIklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiIwLjAuMC4wLzAifX19XX0_&Signature=ch~WD7z9cBluu3KWX2pIsmCd7pfVQ~WnM2QEg6wLuDPFi5pr2e4DnHApdAX9A92dxJW68WYllbKB1pNRkJ6RjtW~3eip545DnUcSBYGT1vfiaxNzp80QYsg~EUo~l4eHFYVoywXXPNgEPRXPIZ2vHGv7H7aeGJ5fbhzyunSmdstRnfUpfKLL5XmsDjpvVY4RYy1NSHHG70BU3y6UP1nqdu1hEdzBhRFKwZ~0uj8~2S4OF6Daaj71jkRWF49TQS~aNpvrkaAZojUilWEwppXZNcCekXu7Bt95oJDESsNQf0bu0gDw5eNtAfy2ZzN~a~lXVTV3ORy3kVanNwiVJzcf~g__&Key-Pair-Id=APKAIFKFWOMXM2UMTSFA
location has %3D: true
error: io.ktor.client.features.ClientRequestException: Client request(https://d29vzk4ow07wi7.cloudfront.net/d133d0ab57f0e1a996fa1367eb3e5c7ebbd1860a5d505a69791d7746457acb0a?response-content-disposition=attachment%3Bfilename=%22ForgeGradle-1.2-1.0.0-javadoc.jar%22&Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHAqOi8vZDI5dnprNG93MDd3aTcuY2xvdWRmcm9udC5uZXQvZDEzM2QwYWI1N2YwZTFhOTk2ZmExMzY3ZWIzZTVjN2ViYmQxODYwYTVkNTA1YTY5NzkxZDc3NDY0NTdhY2IwYT9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPWF0dGFjaG1lbnQlM0JmaWxlbmFtZSUzRCUyMkZvcmdlR3JhZGxlLTEuMi0xLjAuMC1qYXZhZG9jLmphciUyMiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTYxMjkzNzUzMH0sIklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiIwLjAuMC4wLzAifX19XX0_&Signature=XwmQYLdJSkb~0fNr36ebj1vzn1KXmufRtaP9Mj9y9thRAtpAbyImJU-31VhtEhV56ijN2-tBtT5m0rNPSq-f6AEvngoEZURUUpE-vGVb1UFwiGnVnTuDKQJUvao3gZTj-fq9kRCLosJGYJiVbLG3GTXQKYoxL9fQh2ttwS3eFKAyFylcjyvsmSkEAekomfjB8OhUpgc~B6CE0Vg6aOm3ZBaKNAtfX4~cccnFrz4PB836WD68y8qX9jN5IQMCAiDy0881chyPIjGM7c30aycANw5c8StHSRy~gWo5WL7TGpq19X5q2pK-sT8Vq0I3AJJZ95XW9N3GWj0WR7IKqOQTAg__&Key-Pair-Id=APKAIFKFWOMXM2UMTSFA) invalid: 403 Forbidden. Text: "<?xml version="1.0" encoding="UTF-8"?><Error><Code>AccessDenied</Code><Message>Access denied</Message></Error>"
error url: https://d29vzk4ow07wi7.cloudfront.net/d133d0ab57f0e1a996fa1367eb3e5c7ebbd1860a5d505a69791d7746457acb0a?response-content-disposition=attachment%3Bfilename=%22ForgeGradle-1.2-1.0.0-javadoc.jar%22&Policy=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHAqOi8vZDI5dnprNG93MDd3aTcuY2xvdWRmcm9udC5uZXQvZDEzM2QwYWI1N2YwZTFhOTk2ZmExMzY3ZWIzZTVjN2ViYmQxODYwYTVkNTA1YTY5NzkxZDc3NDY0NTdhY2IwYT9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPWF0dGFjaG1lbnQlM0JmaWxlbmFtZSUzRCUyMkZvcmdlR3JhZGxlLTEuMi0xLjAuMC1qYXZhZG9jLmphciUyMiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTYxMjkzNzUzMH0sIklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiIwLjAuMC4wLzAifX19XX0_&Signature=XwmQYLdJSkb~0fNr36ebj1vzn1KXmufRtaP9Mj9y9thRAtpAbyImJU-31VhtEhV56ijN2-tBtT5m0rNPSq-f6AEvngoEZURUUpE-vGVb1UFwiGnVnTuDKQJUvao3gZTj-fq9kRCLosJGYJiVbLG3GTXQKYoxL9fQh2ttwS3eFKAyFylcjyvsmSkEAekomfjB8OhUpgc~B6CE0Vg6aOm3ZBaKNAtfX4~cccnFrz4PB836WD68y8qX9jN5IQMCAiDy0881chyPIjGM7c30aycANw5c8StHSRy~gWo5WL7TGpq19X5q2pK-sT8Vq0I3AJJZ95XW9N3GWj0WR7IKqOQTAg__&Key-Pair-Id=APKAIFKFWOMXM2UMTSFA
error url has %3D: false
Apache HTTP Client does not send Content-Length header if body is empty content
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1333
Ktor Version and Engine Used (client or server and name)
io.ktor:ktor-client-core:1.2.4
io.ktor:ktor-client-core-jvm:1.2.4
io.ktor:ktor-client-apache:1.2.4
Describe the bug
Apache HTTP Client does not send Content-Length header if body is empty content.
CIO send the header correctly.
To Reproduce
Steps to reproduce the behavior:
- Write the following
val apacheClient = HttpClient(Apache)
apacheClient.put<HttpResponseData>("http://example.com/")
apacheClient.post<HttpResponseData>("http://example.com/")
apacheClient.patch<HttpResponseData>("http://example.com/")
- Run on jvm
- The sent header does not contain Content-Length
{
"accept-charset": "UTF-8",
"accept": "*/*",
"user-agent": "Ktor client",
"host": "example.com",
"connection": "Keep-Alive"
}
Some endpoints that do not require body return 411 Length Required.
Expected behavior
Content-Length: 0
Should be sent.
Apache seems not to send Content-Length
if entity is null.
Following code sent the header correctly.
apacheClient.put<HttpResponseData>("http://example.com/"){
body = ""
}
The relevant part of the code in ktor:
https://github.com/ktorio/ktor/blob/f3858b2bd95dcb35aa7755451403e7cab1dd678c/ktor-client/ktor-client-apache/jvm/src/io/ktor/client/engine/apache/ApacheRequestProducer.kt#L122-L155
and org.apache.http.client.methods.RequestBuilder 's build() method.
Client upload/download progress observer/handler/interceptor
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1832
Subsystem
Client
Is your feature request related to a problem? Please describe.
Getting how much data been transferred always useful
Describe the solution you'd like
want to get eventlistener
with interface bytesSent/bytesTotal
Motivation to include to ktor
I think it already exists but I can't find it.
having code:
val retMessage = client.request<Message> {
method = HttpMethod.Patch
url("https://myhost/rest/v1/message/image-upload/aa")
body = MultiPartFormDataContent(
...
want to be able to add something like:
val retMessage = client.request<Message> {
eventListener = { event ->
println("${event.bytesSent/event.bytesTotal}% uploaded")
}
method = HttpMethod.Patch
url("https://myhost/rest/v1/message/image-upload/aa")
...
I assume currently it exists but I don't know how to do it, can somebody help me please :) ?
CIO: TLSConfigBuilder JVM allow null as password
Subsystem
CIO JVM TLSConfigBuilder
Motivation
It is legal (and possible with Apache HttpClientEngine) to use a keystore without a password. One valid and secure use-case is using a pki/smartcard middleware which is loaded as a keystore, and enforces itself the password/the smartcard pin.
Solution
Made password nullable https://github.com/ktorio/ktor/pull/2026
Core
UDPSocketTest.testBroadcastSuccessful[jvm] is failing
This became important since we merged 1.6.0 to the main branch.
How to track leaked buffers in ktor-io?
Prior ktor 1.5.3 it was possible to replace pool of created ByteReadPacket
by using something like this:
inline fun buildPacket(pool: ObjectPool<ChunkBuffer>, block: BytePacketBuilder.() -> Unit): ByteReadPacket {
val builder = BytePacketBuilder(0, pool)
try {
block(builder)
return builder.build()
} catch (t: Throwable) {
builder.release()
throw t
}
}
and then, use such pool
that will intercept calls to default ChunkBuffer.Pool
that is used for underlying storage:
object InUseTrackingPool : ObjectPool<ChunkBuffer> {
override val capacity: Int get() = ChunkBuffer.Pool.capacity
private val inUse = atomic(0)
override fun borrow(): ChunkBuffer {
inUse.incrementAndGet()
return ChunkBuffer.Pool.borrow()
}
override fun recycle(instance: ChunkBuffer) {
inUse.decrementAndGet()
ChunkBuffer.Pool.recycle(instance)
}
override fun dispose() {
ChunkBuffer.Pool.dispose()
}
fun resetInUse() {
inUse.lazySet(0)
}
fun assertNoInUse() {
val v = inUse.value
assertEquals(0, v, "Buffers in use")
}
}
But after fix for KTOR-960, now ChunkBuffer
s are linked to parentPool
in which they were created, and so, borrow
interception works, but recycle
will be never called, as ChunkBuffer.Pool.recycle
will be called directly.
Even now it's possible to do something like this but using internal
APIs like here: https://github.com/rsocket/rsocket-kotlin/blob/06ce789c6aeaf499f1dd43cdcf1c78e1b3825881/rsocket-test/src/commonMain/kotlin/io/rsocket/kotlin/test/InUseTrackingPool.kt - very very hacky.
The question is: Is it possible to have ability to track leaked buffers in another way? Or can you provide some easier (non-internal) way to create own Buffer Pools, or some other API
Expose TrailingSlashRouteSelector
Hey,
I think there is an unnecessary restriction on the RouteSelector
that limits TrailingSlashRouteSelector
as an internal object. I know that RouteSelection is an internal API, but it is very useful for route analysis, for instance I am starting to build out an OpenAPI spec generator, and would like to construct a route from the final route declaration, so something like this
@OptIn(InternalAPI::class)
fun Route.calculatePath(tail: String = ""): String = when (selector) {
is RootRouteSelector -> tail
is PathSegmentParameterRouteSelector -> parent?.calculatePath("/$selector$tail") ?: "/{$selector}$tail"
is PathSegmentConstantRouteSelector -> parent?.calculatePath("/$selector$tail") ?: "/$selector$tail"
else -> error("unknown selector type $selector")
}
However, I am running into a limitation placed seemingly only on TrailingSlashRouteSelector, which is declared internal object
. Are there any concerns around making this publicly available?
If so, what would be a proposed alternative for building out route patterns at runtime?
Docs
Wrong Tabs Name in Code Blocks
It should be Groovy
Kotlin
Maven
instead of Groovy
Kotlin
Kotlin
https://ktor.io/docs/testing.html#example-with-dependencies
Document usage of Bearer token in Http Client
In https://ktor.io/docs/features-auth.html:
We should add support for bearer token authorization; it's the most common one in APIs for apps
Duplicate server `Features` Section on the Documentation Website
Duplicate entry "Features" in Server docs
See Server -> Developing Applications
, there are two entries titled "Features": an article about features and a group of articles about the core features.
And they have the same URL https://ktor.io/docs/features.html.
Infrastructure
Update Dokka: Dokka tasks fails with old dokka version and Gradle 7
After updating to Gradle 7, the publish
/dokka
tasks fails, because the outdated dokka version (0.9.17) is not compatible with Gradle 7.
Solution:
Update Dokka to 1.4.32
./gradlew publishToMavenLocal
Redirecting repositories for buildSrc buildscript
> Configure project :
Using Kotlin compiler version: 1.5.0
> Task :dokka FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Some problems were found with the configuration of task ':dokka' (type 'DokkaTask').
- Type 'org.jetbrains.dokka.gradle.DokkaTask' property 'outputDirectory' is missing an input or output annotation.
Reason: A property without annotation isn't considered during up-to-date checking.
Possible solutions:
1. Add an input or output annotation.
2. Mark it as @Internal.
Please refer to https://docs.gradle.org/7.0.2/userguide/validation_problems.html#missing_annotation for more details about this problem.
- Type 'org.jetbrains.dokka.gradle.DokkaTask' property 'reportNotDocumented' is missing an input or output annotation.
Reason: A property without annotation isn't considered during up-to-date checking.
Possible solutions:
1. Add an input or output annotation.
2. Mark it as @Internal.
Please refer to https://docs.gradle.org/7.0.2/userguide/validation_problems.html#missing_annotation for more details about this problem.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/7.0.2/userguide/command_line_interface.html#sec:command_line_warnings
BUILD FAILED in 8s
1 actionable task: 1 executed
Migrate to Dokka 1.4.0
IntelliJ IDEA Plugin
Implement Switcher between embeddedServer and module
IDE plugin: SOE through KtorUrlReferenceContributor$Companion.getParentUrlPaths
User feedback:
After installing Intellij IDEA 2021.1 (on Linux Mint 20.1 with all updates installed) I was having a lot of problems working in a simple Ktor/Gradle project, newly created. I could build in gradle and run the application but in the IDE the editor was showing errors where there were none. Go to definition and other code navigation features weren't working. I got an error saying "Kotlin not enabled" but the plugin was installed and enabled. Eventually the IDE would lock up completely and I'd have to kill the process manually.
I tried uninstalling and reinstalling, removing all folders in places like .local where files were stored before reinstalling. I tried installing using JB toolbox and then removing that as well and installing from a .tar.gz file.
Finally I just removed it all and installed 2020.3, and it's working, but doesn't have some of the new features like Code With Me that I wanted to try.
IDEA 2021.1.1 Preview Build #IU-211.7142.13
Kotlin (211-1.4.32-release-IJ7142.3)
Ktor version: 1.5.3
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - IntelliJ IDEA 2021.1.1 Preview Build #IU-211.7142.13
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - JDK: 11.0.10; VM: Dynamic Code Evolution 64-Bit Server VM; Vendor: JetBrains s.r.o.
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - OS: Linux
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - Plugin to blame: Ktor version: 1.5.3
2021-04-22 09:09:59,895 [ 34465] ERROR - mpl.search.PsiSearchHelperImpl - Last Action: About
2021-04-22 09:09:59,895 [ 34465] ERROR - aemon.impl.PassExecutorService - null
java.lang.StackOverflowError
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.openapi.progress.util.ProgressWrapper.setText(ProgressWrapper.java:105)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processGlobalRequests(PsiSearchHelperImpl.java:837)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processGlobalRequestsOptimized(PsiSearchHelperImpl.java:830)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processRequests(PsiSearchHelperImpl.java:755)
at com.intellij.psi.search.SearchRequestQuery.processResults(SearchRequestQuery.java:24)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.delegateProcessResults(AbstractQuery.java:108)
at com.intellij.util.MergeQuery.processResults(MergeQuery.java:22)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.delegateProcessResults(AbstractQuery.java:108)
at com.intellij.util.UniqueResultsQuery.processResults(UniqueResultsQuery.java:39)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.forEach(AbstractQuery.java:83)
at com.intellij.util.AbstractQuery.findAll(AbstractQuery.java:28)
at com.intellij.util.Query.iterator(Query.java:129)
at com.intellij.util.AbstractQuery.iterator(AbstractQuery.java:39)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths(KtorUrlReferenceContributor.kt:370)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths$default(KtorUrlReferenceContributor.kt:120)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths(KtorUrlReferenceContributor.kt:191)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths$default(KtorUrlReferenceContributor.kt:120)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.calculateUrlPathContext(KtorUrlReferenceContributor.kt:85)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1$injector$1.invoke(KtorUrlReferenceContributor.kt:232)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1$injector$1.invoke(KtorUrlReferenceContributor.kt:22)
at com.intellij.microservices.url.references.UrlPathReferenceInjector$buildReferences$1.rootContext(UrlPathReferenceInjector.kt:128)
at com.intellij.microservices.url.references.UrlPathReferenceInjector$buildReferences$1.forPsiElement(UrlPathReferenceInjector.kt:136)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1.invoke(KtorUrlReferenceContributor.kt:238)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1.invoke(KtorUrlReferenceContributor.kt:22)
at com.intellij.psi.UastReferenceRegistrar$uastReferenceProvider$1.getReferencesByElement(UastReferenceRegistrar.kt:54)
at com.intellij.psi.UastReferenceProviderAdapter$getReferencesByElement$1.invoke(UastReferenceProviderAdapter.kt:13)
at com.intellij.psi.UastReferenceProviderAdapter$getReferencesByElement$1.invoke(UastReferenceProviderAdapter.kt:8)
at com.intellij.openapi.progress.impl.CancellationCheck.withCancellationCheck(CancellationCheck.kt:59)
at com.intellij.openapi.progress.impl.CancellationCheck$Companion.runWithCancellationCheck(CancellationCheck.kt:105)
at com.intellij.psi.UastReferenceProviderAdapter.getReferencesByElement(UastReferenceProviderAdapter.kt:13)
at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl.getReferences(ReferenceProvidersRegistryImpl.java:201)
at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl.mapNotEmptyReferencesFromProviders(ReferenceProvidersRegistryImpl.java:164)
at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl.doGetReferencesFromProviders(ReferenceProvidersRegistryImpl.java:143)
at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry.getReferencesFromProviders(ReferenceProvidersRegistry.java:43)
at com.intellij.psi.PsiReferenceServiceImpl.doGetReferences(PsiReferenceServiceImpl.java:34)
at com.intellij.psi.PsiReferenceServiceImpl.getReferences(PsiReferenceServiceImpl.java:26)
at com.intellij.psi.search.SingleTargetRequestResultProcessor.processTextOccurrence(SingleTargetRequestResultProcessor.java:32)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$5.lambda$execute$0(PsiSearchHelperImpl.java:942)
at com.intellij.psi.impl.search.LowLevelSearchUtil.processTreeUp(LowLevelSearchUtil.java:88)
at com.intellij.psi.impl.search.LowLevelSearchUtil.lambda$processElementsAtOffsets$0(LowLevelSearchUtil.java:179)
at com.intellij.psi.impl.search.LowLevelSearchUtil.processOffsets(LowLevelSearchUtil.java:205)
at com.intellij.psi.impl.search.LowLevelSearchUtil.processElementsAtOffsets(LowLevelSearchUtil.java:178)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$5.execute(PsiSearchHelperImpl.java:938)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$2.processInReadAction(PsiSearchHelperImpl.java:272)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$2.processInReadAction(PsiSearchHelperImpl.java:263)
at com.intellij.openapi.application.ReadActionProcessor.lambda$process$0(ReadActionProcessor.java:25)
at com.intellij.openapi.application.impl.ApplicationImpl.runReadAction(ApplicationImpl.java:844)
at com.intellij.openapi.application.ReadAction.compute(ReadAction.java:61)
at com.intellij.openapi.application.ReadActionProcessor.process(ReadActionProcessor.java:25)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processCandidates$17(PsiSearchHelperImpl.java:899)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processVirtualFile$10(PsiSearchHelperImpl.java:535)
at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1091)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processVirtualFile(PsiSearchHelperImpl.java:515)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processPsiFileRoots$7(PsiSearchHelperImpl.java:392)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processFilesConcurrentlyDespiteWriteActions$8(PsiSearchHelperImpl.java:465)
at com.intellij.openapi.application.impl.ReadMostlyRWLock.executeByImpatientReader(ReadMostlyRWLock.java:167)
at com.intellij.openapi.application.impl.ApplicationImpl.executeByImpatientReader(ApplicationImpl.java:178)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processFilesConcurrentlyDespiteWriteActions$9(PsiSearchHelperImpl.java:464)
at com.intellij.concurrency.JobLauncherImpl.lambda$processImmediatelyIfTooFew$2(JobLauncherImpl.java:142)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:688)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:634)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:64)
at com.intellij.concurrency.JobLauncherImpl.lambda$processImmediatelyIfTooFew$3(JobLauncherImpl.java:138)
at com.intellij.openapi.application.impl.ApplicationImpl.runReadAction(ApplicationImpl.java:814)
at com.intellij.concurrency.JobLauncherImpl.processImmediatelyIfTooFew(JobLauncherImpl.java:149)
at com.intellij.concurrency.JobLauncherImpl.invokeConcurrentlyUnderProgress(JobLauncherImpl.java:45)
at com.intellij.concurrency.JobLauncher.invokeConcurrentlyUnderProgress(JobLauncher.java:49)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processFilesConcurrentlyDespiteWriteActions(PsiSearchHelperImpl.java:481)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processPsiFileRoots(PsiSearchHelperImpl.java:389)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processCandidates(PsiSearchHelperImpl.java:894)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processGlobalRequests(PsiSearchHelperImpl.java:864)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processGlobalRequestsOptimized(PsiSearchHelperImpl.java:830)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processRequests(PsiSearchHelperImpl.java:755)
at com.intellij.psi.search.SearchRequestQuery.processResults(SearchRequestQuery.java:24)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.delegateProcessResults(AbstractQuery.java:108)
at com.intellij.util.MergeQuery.processResults(MergeQuery.java:22)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.delegateProcessResults(AbstractQuery.java:108)
at com.intellij.util.UniqueResultsQuery.processResults(UniqueResultsQuery.java:39)
at com.intellij.util.AbstractQuery.doProcessResults(AbstractQuery.java:91)
at com.intellij.util.AbstractQuery.forEach(AbstractQuery.java:83)
at com.intellij.util.AbstractQuery.findAll(AbstractQuery.java:28)
at com.intellij.util.Query.iterator(Query.java:129)
at com.intellij.util.AbstractQuery.iterator(AbstractQuery.java:39)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths(KtorUrlReferenceContributor.kt:370)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths$default(KtorUrlReferenceContributor.kt:120)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths(KtorUrlReferenceContributor.kt:191)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.getParentUrlPaths$default(KtorUrlReferenceContributor.kt:120)
at io.ktor.ide.KtorUrlReferenceContributor$Companion.calculateUrlPathContext(KtorUrlReferenceContributor.kt:85)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1$injector$1.invoke(KtorUrlReferenceContributor.kt:232)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1$injector$1.invoke(KtorUrlReferenceContributor.kt:22)
at com.intellij.microservices.url.references.UrlPathReferenceInjector$buildReferences$1.rootContext(UrlPathReferenceInjector.kt:128)
at com.intellij.microservices.url.references.UrlPathReferenceInjector$buildReferences$1.forPsiElement(UrlPathReferenceInjector.kt:136)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1.invoke(KtorUrlReferenceContributor.kt:238)
at io.ktor.ide.KtorUrlReferenceContributor$registerServerReferenceProvider$1.invoke(KtorUrlReferenceContributor.kt:22)
at com.intellij.psi.UastReferenceRegistrar$uastReferenceProvider$1.getReferencesByElement(UastReferenceRegistrar.kt:54)
[...]
Override flag to auto-suggest imports for Ktor modules
Add EP to Kotlin/Idea to prioritise a list of Ktor imports in case of ambiguity if the flag is enabled
Creating Ktor run configuration for module progress doesn't stop
After update from VCS the bg progress doesn't stop
Testing in IDE: Action to generate tests for given Ktor routes
Server
Support decompression for incoming request bodies
I'm not sure if you follow a template for these kinds of requests, so I'll keep it a little free-form.
I've been attempting to receive gzipped multipart data on a backend server I'm writing. The exact reasoning is that this application will be a small mock to act as Google Cloud Storage in a docker-compose setup (running my team's set of applications locally to avoid deploying to test environments as much), and where the client uses Google's own Java-library.
It appears that (at least when posting objects to a bucket) the client gzip-compresses multipart bodies, which makes call.receiveMultipart()
break as it doesn't decode the request body before parsing the body as multipart. I have figured out a little workaround here, though it borrows some scary internals I'm not sure I should really be playing with. I'm especially not fond of the way I create finishedRequest
below. I'm including everything relevant from this part of code to hopefully provide enough context.
package no.nav.flex.application.google
import io.ktor.application.call
import io.ktor.http.content.MultiPartData
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.request.ApplicationReceiveRequest
import io.ktor.request.receiveMultipart
import io.ktor.request.receiveStream
import io.ktor.response.respondText
import io.ktor.routing.Routing
import io.ktor.routing.post
import io.ktor.util.Identity.decode
import io.ktor.util.KtorExperimentalAPI
import io.ktor.utils.io.core.ExperimentalIoApi
import io.ktor.utils.io.jvm.javaio.toByteReadChannel
import no.nav.flex.Environment
import no.nav.flex.log
import kotlin.reflect.typeOf
@ExperimentalStdlibApi
@ExperimentalIoApi
@KtorExperimentalAPI
fun Routing.registerGoogleApi(env: Environment) {
post("/upload/storage/v1/b/${env.bucket}/o") {
val encoding = call.request.headers["Content-Encoding"] ?: "none"
val content = if (encoding == "gzip") {
val stream = call.receiveStream().toByteReadChannel()
val incomingContent = decode(stream)
val receiveRequest = ApplicationReceiveRequest(typeOf<MultiPartData>(), incomingContent)
val finishedRequest = call.request.pipeline.execute(call, receiveRequest)
finishedRequest.value as MultiPartData
} else {
call.receiveMultipart()
}
content.forEachPart {
when (it) {
is PartData.BinaryItem -> log.info("is binary item")
is PartData.FileItem -> log.info("is file item")
is PartData.FormItem -> log.info("is form item: ${it.value}")
}
}
call.respondText("hello")
}
}
So, I suppose I'm wondering if there is a better way to do this. I wouldn't be opposed to needing to create an extension function that mimics the <reified T : Any> ApplicationCall.receive(): T = receive(typeOf<T>())
-function (which could decode the input before sending it through the rest of the stack), though a number of things from that function are private (DoubleReceivePreventionTokenKey
, DoubleReceivePreventionToken
and CannotTransformContentToTypeException
) and I'm not sure how to circumvent that sort of stuff while still keeping everything nicely integrated. If there is some kind of middleware I can just install
to decode any incoming requests with Content-Encoding
set, then I've not been able to find such a thing.
Any tips or comments would be appreciated!
Netty: no way of configuring client certificate auth
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/595
The Netty engine config allows configuring the server's SSL parameters, but it doesn't expose the SslContextBuilder
. This makes it hard to create a ktor server that requires clients to supply a certificate verified against a custom CA. It would be great if the Netty engine config allowed specifying that client certificates are required, and a custom trust store for validating them.
Cannot receive content via jackson negotiator since 1.4.2
It seems like this commit broke the Jackson content negotiator, now when calling call.receive()
the following exception occurs:
java.lang.IllegalStateException: Using blocking primitives on this dispatcher is not allowed. Consider using async channel instead or use blocking primitives in withContext(Dispatchers.IO) instead.
at io.ktor.utils.io.jvm.javaio.BlockingKt.ensureParkingAllowed(Blocking.kt:302)
at io.ktor.utils.io.jvm.javaio.BlockingKt.access$ensureParkingAllowed(Blocking.kt:1)
at io.ktor.utils.io.jvm.javaio.InputAdapter.<init>(Blocking.kt:30)
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream(Blocking.kt:20)
at io.ktor.utils.io.jvm.javaio.BlockingKt.toInputStream$default(Blocking.kt:20)
at io.ktor.jackson.JacksonConverter.convertForReceive(JacksonConverter.kt:55)
at io.ktor.features.ContentNegotiation$Feature$install$3.invokeSuspend(ContentNegotiation.kt:175)
at io.ktor.features.ContentNegotiation$Feature$install$3.invoke(ContentNegotiation.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.request.ApplicationReceiveFunctionsKt.receive(ApplicationReceiveFunctions.kt:110)
at net.octyl.ourtwobe.api.ApiServerKt$module$7$2$1$1.invokeSuspend(ApiServer.kt:232)
at net.octyl.ourtwobe.api.ApiServerKt$module$7$2$1$1.invoke(ApiServer.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.routing.Routing.executeResult(Routing.kt:148)
at io.ktor.routing.Routing.interceptor(Routing.kt:35)
at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:100)
at io.ktor.routing.Routing$Feature$install$1.invoke(Routing.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:113)
at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:101)
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:100)
at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:140)
at io.ktor.features.StatusPages$Feature$install$2.invoke(StatusPages.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.features.CallLogging$Feature$install$2.invokeSuspend(CallLogging.kt:139)
at io.ktor.features.CallLogging$Feature$install$2.invoke(CallLogging.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:118)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:55)
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:111)
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:38)
at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:29)
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:215)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:832)
Netty: server freezes after start error
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/336
I runned the hello world example and it's ok. When I launch a second instance at the same time, I get java.net.BindException: Address already in use: bind. Perfect. But I expected the second instance to end with a failure. Instead I get a hanging netty application (a thread waiting in some netty routine).
Here is the code: https://github.com/jarekczek/ktorSimple/blob/master/src/main/java/WwwServer.kt
It never gets after the server.start(wait = false)
line.
I know a workaround. Catch the exception and System.exit()
.
What's also strange is that if I don't do server.stop
, the program never ends. I expect that when my application comes to the end of the main method, the program would stop. It works this way in finnagle. But not here.
So a final question: should netty prevent the application from terminating?
Occasionally empty response using Netty + Jackson
Ktor 1.5.0
When you use netty + jackson content negotiation, couple of times in 10k requests you will have 200 with empty response.
manual jackson mapping, or string response work properly. Also on jetty works properly(but you can see socket timeout instead of empty response)
gradle:
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.4.21'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation("com.squareup.okhttp3:okhttp:4.9.0")
implementation("io.ktor:ktor-server-netty:1.5.0")
implementation("io.ktor:ktor-jackson:1.5.0")
}
server
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.ContentNegotiation
import io.ktor.http.ContentType
import io.ktor.jackson.JacksonConverter
import io.ktor.response.respond
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import kotlinx.coroutines.delay
import java.util.UUID
import java.util.UUID.randomUUID
fun main() {
embeddedServer(Netty, 8081) {
install(ContentNegotiation) {
register(ContentType.Application.Json, JacksonConverter(objectmapper = jacksonObjectMapper()))
}
routing {
get("/json") {
delay(10)
call.respond(Response(randomUUID()))
}
}
}.start(wait = true)
}
data class Response(
val id: UUID
)
client
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.isActive
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.concurrent.atomic.AtomicLong
val counter: AtomicLong = AtomicLong()
val request: Request = Request.Builder()
.url("http://localhost:8081/json")
.build()
fun main(): Unit = runBlocking {
val clients = (1..10).map { it to OkHttpClient() }
withContext(IO) {
supervisorScope {
clients.map { (idx, client) ->
async {
while (isActive) {
val r = runCatching {
client.newCall(request).execute()
}
if ((r.getOrNull()?.body?.string() ?: "").isBlank()) {
println("$idx: I'm blank on ${counter.get()} try ${r.getOrNull()}")
}
if (counter.incrementAndGet() % 10000L == 0L) {
println("$idx: OK on ${counter.get()} try")
}
}
}
}
}
}
}
Example of log:
7: I'm blank on 8299 try Response{protocol=http/1.1, code=200, message=OK, url=http://localhost:8081/json}
2: OK on 10000 try
5: I'm blank on 10620 try Response{protocol=http/1.1, code=200, message=OK, url=http://localhost:8081/json}
6: I'm blank on 16593 try Response{protocol=http/1.1, code=200, message=OK, url=http://localhost:8081/json}
10: OK on 20000 try
8: I'm blank on 21634 try Response{protocol=http/1.1, code=200, message=OK, url=http://localhost:8081/json}
1: I'm blank on 22057 try Response{protocol=http/1.1, code=200, message=OK, url=http://localhost:8081/json}
7: I'm blank on 22574 try Response{protocol=http/1.1, code=200, message=OK, url=http://localhost:8081/json}
9: I'm blank on 27737 try Response{protocol=http/1.1, code=200, message=OK, url=http://localhost:8081/json}
5: I'm blank on 27759 try Response{protocol=http/1.1, code=200, message=OK, url=http://localhost:8081/json}
10: OK on 30000 try
...
call.respond() will not check or apply ContentNegotiation for some types
GET on "/" will return Content-Type: text/plain
even if the request was called with Accept: application/json
I'd expect it to return 406 Not Acceptable
The same happens even for respondText with a hardcoded type.
More than this it no longer checks accept header format.
install(ContentNegotiation) // <- adding a converter makes no difference
routing {
get("/") {
call.respond("some string")
}
}
I traced it down to Ktor adding a default transform (io.ktor.http.content.transformDefaultContent
) for the Render pipeline that transforms any String to an TextContent(): OutgoingContent
and the negotiation feature short circuits because of it.
New to Ktor features but adding a pre render phase inside the negotiation feature could let it apply its logic letting then the default transform short circuit instead.
Phases
ContentNegotiationPreRender
- existing negotiation renderer -> convert + error checks
Render
- default transform -> skip due to being passed OutgoingContent
Some caveats:
If the handler has mutating logic I don't see a way for the pipeline to ping back the handler to rollback as exceptions are converted to Outgoing responses.
My only fear is that moving the negotiation up the chain might affect users after it.
Partial solution:
One way to avoid this is to check the accepted vs available converters way earlier and never call the handlers in the first place but even so nobody stops users from calling respond methods with unserialisable objects, or unacceptable content types.
route("{...}") stopped matching root
STR:
route("{...}", HttpMethod.Get) {
accept(ContentType.Text.Html) {
call.respondBytes(..., ContentType.Text.Html.withCharset(Charsets.UTF_8))
}
}
AR: http://localhost:$PORT$ and http://localhost:$PORT$/ both return 404
ER: they return provided index bytes
Workaround that worked: add a separate route
section for "/"
:
route("/", HttpMethod.Get) {
accept(ContentType.Text.Html) {
call.respondBytes(..., ContentType.Text.Html.withCharset(Charsets.UTF_8))
}
}
route("{...}", HttpMethod.Get) {
accept(ContentType.Text.Html) {
call.respondBytes(..., ContentType.Text.Html.withCharset(Charsets.UTF_8))
}
}
Routing: Add PutTyped and PatchTyped Overload
Actually there exists a overloaded post
function in Routing.kt
, which allows you to write:
routing {
post<Foo> { foo ->
}
}
This overload is only available for POST
, but not for PATCH
or PUT
too.
Added these two overloads in PR:
Support for Compression Extensions for WebSocket (RFC 7692)
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/907
Origin Problem
It would be very useful if ktor-websockets gained support for Compression Extensions for WebSocket, as defined in RFC 7692. The WebSocket Subprotocol "ocpp2.0" (Open Charge Point Protocol version 2.0) requires this feature to be implemented at the server-side, specifically using the DEFLATE algorithm, which is included as the "permessage-deflate" extension in section 7 of the aforementioned RFC.
I guess there's probably a way to manually implement the handling of DEFLATE (de)compression according to this extension in application code on top of Ktor, but it would be much nicer if Ktor would gain integrated and transparent support for this extension in its WebSocket functionality, so this would work out of the box without requiring any application-specific programming.
Thank you in advance for considering this feature.
Proposed solution
We make the API for implementation and usage of the WebSocket extensions:
Using WebSocket extensions
Installation
To install and configure the extensions we provide 2 methods: extensions
and install
which can be used in the following way:
install(WebSockets) {
extensions { /* WebSocketExtensionConfig.() -> Unit */
install(MyWebSocketExtension) { /* MyWebSocketExtensionConfig.() -> Unit */
/* Optional extension configuration. */
}
}
}
The extensions are used in order of installation. This way looks familiar to existing features installation API and works for client and server in the same way.
Check if an extension is in use
All installed extensions go through the negotiation process and only negotiated extensions are used for the request.
We introduce WebSocketSession.extensions: : List<WebSocketExtension<*>>
property with list of all extensions used by for the current session.
There are 2 methods to check if the extension is in use: WebSocketSession.extension
and WebSocketSession.extensionOrNull
:
webSocket("/echo") {
val myExtension = extension(MyWebSocketException) // will throw if `MyWebSocketException` is not negotiated
// or
val myExtension = extensionOrNull(MyWebSocketException) ?: close() // will close the session if `MyWebSocketException` is not negotiated
}
This API is similar to the Ktor feature discovery with the feature(...)
method.
Implementing new extension
There are 2 interfaces for implementing new extension: WebSocketExtension<ConfigType: Any>
and WebSocketExtensionFactory<ConfigType : Any, ExtensionType : WebSocketExtension<ConfigType>>
. The new extension can be made by implementing all of them. The single implementation is working for client and server as well.
Here is the example of how the simple frame logging extension can be implemented:
class FrameLoggerExtension(val logger: Logger) : WebSocketExtension<FrameLogger.Config> {
The feature has 2 groups of fields and methods. The first group is for extension negotiation:
/** List of protocols will be send in client request for negotiation **/
override val protocols: List<WebSocketExtensionHeader> = emptyList()
/**
* This method will be called for the server and will process `requestedProtocols` from a client.
* In the result it will return a list of extensions that the server agrees to use.
*/
override fun serverNegotiation(requestedProtocols: List<WebSocketExtensionHeader>): List<WebSocketExtensionHeader> {
logger.log("Server negotiation")
return emptyList()
}
/**
* This method will be called on the client with a list of protocols, produced by `serverNegotiation`. It will decide if these extensions should be used.
*/
override fun clientNegotiation(negotiatedProtocols: List<WebSocketExtensionHeader>): Boolean {
logger.log("Client negotiation")
return true
}
The second group is the place for actual frame processing. Methods will take the frame and produce a new processed frame if necessary:
override fun processOutgoingFrame(frame: Frame): Frame {
logger.log("Process outgoing frame: $frame")
return frame
}
override fun processIncomingFrame(frame: Frame): Frame {
logger.log("Process incoming frame: $frame")
return frame
}
There are also some implementation details: the feature has Config
and reference to the origin `factory.
class Config {
lateinit var logger: Logger
}
/**
* Factory which can create current extension instance.
*/
override val factory: WebSocketExtensionFactory<Config, FrameLogger> = FrameLoggerExtension
The factory is usually implemented in companion object(similar to regular features):
companion object : WebSocketExtensionFactory<Config, FrameLogger> {
/* Key to discover installed extension instance */
override val key: AttributeKey<FrameLogger> = AttributeKey("frame-logger")
/** Occupied rsv bits.
* If the extension occupies a bit, it can't be used in other installed extensions. We use that bits to prevent feature conflicts(prevent installing multiple compression features). If you implementing a feature using some RFC, rsv occupied bits should be referenced there.
*/
override val rsv1: Boolean = true
/** Create feture instance. Will be called for each WebSocket session **/
override fun install(config: Config.() -> Unit): FrameLogger {
return FrameLogger(Config().apply(config).logger)
}
}
}
Status
This API is implemented in the ws-extensions branch.
It marked with @ExperimentalWebSocketExtensionsApi
annotation with the level error until we agree on the final design.
Other
Upgrading from 1.4.3 to 1.5.2 introduced a routing precedence
This is a cross-post from channel #ktor in Slack. I was asked by Rustam to file a issue here.
When I upgraded from 1.4.3 to 1.5.2, all of my routes serving GET requests yielded a HTTP error 404 on accessing them. To me it seems, that in 1.5.2 the order of registering routes has become significant, while in 1.4.3 it didn't matter.
In the example below, context "/groot" yields 404.
If staticResources() is called after groot(), route "/groot" becomes available as expected.
class SomeApp {
fun Application.main(testing: Boolean = false) {
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
routing {
underpants() // serves context "/underpants"
staticResources() // serves root context "/"
groot() // serves context "/groot"
}
}
}
// Route for static context
fun Routing.staticResources() {
// Serves frontend resources embedded in application jar
static("/") {
defaultResource("index.html", "web-resource")
resources("web-resource")
}
}
fun Routing.underpants(
) {
route("/underpants") {
get {
val map: HashMap<Int, String> = hashMapOf(1 to "Collect underpants.", 2 to "?", 3 to "Profit!")
call.respond(map)
}
}
}
fun Routing.groot(
) {
route("/groot") {
get {
val map: HashMap<Int, String> = hashMapOf(1 to "I", 2 to "am", 3 to "groot!")
call.respond(map)
}
}
}
Update Kotlin and Serialization Versions
Deprecate TestApplicationCall.requestHandled
This property has strange and hard to document behaviour. Users should not rely on it, but check response status/header/content
aSocket().bind() sometimes throws Already bound SocketException
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1049
Ktor Version
1.1.3
Ktor Engine Used
Netty, Socket
JVM Version, Operating System and Relevant Context
openjdk version "10.0.2" 2018-07-17, Ubuntu 18.04.2 LTS
Feedback
It's hard to reproduce, the problem appears and disappears without observable reasons. I have a server starting two Netty instances and one UDP socket
fun Application.main() {
embeddedServer(Netty, port = 18080) {
routing {
get("/") {
call.respondText("Hello World from what is supposed to be WWW server!", ContentType.Text.Plain)
}
}
}.start()
embeddedServer(Netty, port = 18090) {
routing {
get("/") {
call.respondText("Hello World from what is supposed to be RESt server!", ContentType.Text.Plain)
}
}
}.start()
runBlocking {
org.looksworking.ha.controller.Application.logger.info("Starting UDP")
val server = aSocket(ActorSelectorManager(Dispatchers.IO))
.udp()
.bind(InetSocketAddress("0.0.0.0", 18070))
while (true) {
println(server.incoming.receive().packet.readText())
}
}
}
Console output:
...
Starting UDP
Exception in thread "main" java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Static.call(CallerImpl.kt:106)
at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:106)
at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflect_api(KCallableImpl.kt:152)
at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:110)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.callFunctionWithInjection(ApplicationEngineEnvironmentReloading.kt:349)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.executeModuleFunction(ApplicationEngineEnvironmentReloading.kt:299)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:275)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:127)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:247)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:106)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:18)
at io.ktor.server.engine.ApplicationEngine$DefaultImpls.start$default(ApplicationEngine.kt:52)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:17)
Caused by: java.net.SocketException: Already bound
at java.base/sun.nio.ch.Net.translateToSocketException(Net.java:136)
at java.base/sun.nio.ch.DatagramSocketAdaptor.bind(DatagramSocketAdaptor.java:93)
at io.ktor.network.sockets.UDPSocketBuilder.bind(Builders.kt:150)
at io.ktor.network.sockets.UDPSocketBuilder.bind$default(Builders.kt:141)
at org.looksworking.ha.controller.AppKt$main$3.invokeSuspend(App.kt:44)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.kt:116)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:76)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:53)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:35)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at org.looksworking.ha.controller.AppKt.main(App.kt:38)
... 18 more
Caused by: java.nio.channels.AlreadyBoundException
at java.base/sun.nio.ch.DatagramChannelImpl.bind(DatagramChannelImpl.java:669)
at java.base/sun.nio.ch.DatagramSocketAdaptor.bind(DatagramSocketAdaptor.java:91)
... 30 more
I checked many times - no other instance is running, no ports are busy. At the same time, tcp ports are successfully taken, while this problem with udp socket sometimes persists even after reboot. And then just goes away without any changes.
Is it possible that like in most upvoted answer here, ktor sometimes creates an already bound socket?
Review Auth providers
sendWithoutRequest
is considered harmful- no option to update credentials after installation
Remove deprecated API usages
Add support for Velocity Tools
Subsystem
Server ktor-features/ktor-velocity
Motivation
Velocity templating is not very useful without being able to use the standard Velocity Tools.
Solution
This PR adds a VelocityTool feature. It is used as follow:
install(VelocityTools) {
engine {
// whatever engine configuration you need
setProperty("resource.loader", "string")
addProperty("resource.loader.string.name", "myRepo")
addProperty("resource.loader.string.class", StringResourceLoader::class.java.name)
addProperty("resource.loader.string.repository.name", "myRepo")
}
addDefaultTools() // add a default tool
tool("foo", MyCustomTool::class.java) // add a custom tool
}
Base name of micrometer metrics is not configurable
When Ktor service is running in the environment with services written in other frameworks like Spring Boot, having standardized metric names is important for monitoring. Static, non-configurable "ktor.http.server" base name is inconvenient and breaks existing monitoring setup or at least requires special handling for Ktor services.
Upload progress observer sometimes hangs
Add an option to disable URL Encoding
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1351
Subsystem
ktor-client, ktor-http/common
Is your feature request related to a problem? Please describe.
ktor-client cannot be used for URLs which are not supported by the URL Encoding format.
I failed to replace an HTTP client with ktor-client just because we needed to call a URL like /api?k1=v1;k1=v2
(non-encoded ;
instead of %3B
). As far as I checked, there's no way to bypass URL Encoding in ktor-client.
In other libraries, typically we can set parameters just like a simple Map<String, List<String>>
. I agree that it's better to always use a sanitized request object, but it would be too strict not to have an option to disable encoding.
Describe the solution you'd like
I opened a PR to add an option to bypass the URL Encoding when building a URL.
Motivation to include to ktor
I believe that users of ktor-client shouldn't be blocked by the implementation of a server URL because they might not have control over it.
(Please let me know if there's an option to resolve the above problem.)