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