Changelog 2.1 version
2.1.3
released 28th October 2022
Client
Cookies: Invalid encoding of cookies' values since 1.4.0
Hello
I have issue on Ktor client 1.4.0 Android (iOS is ok).
I have response with
Content-Type: application/x-plist; charset=utf-8
Set-Cookie: JSESSIONID=jc1wDGgCjR8s72-xdZYYZsLywZdCsiIT86U7X5h7.front10; secure; HttpOnly
But for next the request my Cookie looks like
Cookie: JSESSIONID=jc1wDGgCjR8s72%2DxdZYYZsLywZdCsiIT86U7X5h7%2Efront10;
As I you see dot now encoded
In Ktor 1.3.2 for iOS and Android platforms was ok
Websockets: timeout doesn't cause closing of incoming and outgoing channels
I'm developing network App using Ktor 1.2.5 and Kotlin 1.3.61 in Android 8
When netty websocket server's wifi is turned off by manually (or Ethernet cable unplugged manually), any event or exception is not happened in opposite websocket client even something is sent. Which mean websocket client does not know session is lost.
On the contrary, when websocket client's wifi is turned off, webserver can catch it because closedException: ClosedSendChannelException is occured when something is sent.
Does the any way for websocket client to know "session is lost" when server's network is disconnect?
(When webserver's Ethernet cable is unplugged, websocket client cannot be even closed normally - In other words, final block cannot be called.-
Here is code
-----------------------------------------------------------------
SERVER
-----------------------------------------------------------------
embeddedServer(Netty, port = 8080) {
install(DefaultHeaders)
install(CallLogging)
install(WebSockets) {
pingPeriod = Duration.ofSeconds(10)
timeout = Duration.ofSeconds(5)
}
...
routing {
webSocket("/test") {
try {
incoming.consumeEach { frame ->
if(frame is Frame.Text) {
Log.d("test", frame.readText())
}
}
} finally {
val reason: CloseReason? = closeReason.await()
Log.d("test", reason.toString())
}
}
}
}
private suspend fun sendSomething(data:String) {
try {
clients.send(Frame.Binary(true, data))
}
catch (closedException: ClosedSendChannelException) {
Log.d("test", "Server ClosedSendChannelException is happened")
}
}
-----------------------------------------------------------------
CLIENT
-----------------------------------------------------------------
CoroutineScope(Dispatchers.IO).launch {
try {
client.ws(host = ip, port = 8080, path = "/test") {
pingIntervalMillis = 1000*10
timeoutMillis = 1000*5
...
try {
incoming.consumeEach { frame ->
if(frame is Frame.Text) {
Log.d("test", frame.readText())
}
}
} catch (e: ClosedReceiveChannelException) {
Log.d("test", "ClosedSendChannelException is happened")
} catch (e: Throwable) {
Log.d("test", "ClosedSendChannelException is happened")
} finally {
// This block cannot be called even websocket client run explicitly disconnect function, when webserver's Ethernet cable is unplugged.
val reason: CloseReason? = closeReason.await()
Log.d("test", reason.toString())
}
}
} catch (e: Exception) {
if (e is NoRouteToHostException || e is ConnectException) {
Log.d("test", "Exception")
} else if (e.javaClass.`package`?.name.toString().startsWith("java.net")) {
Log.d("test", "java.net")
} else {
Log.d("test", "unknown")
}
}
}
private suspend fun sendSomething(data:String) {
try {
clients.send(Frame.Binary(true, data))
}
catch (closedException: ClosedSendChannelException) {
Log.d("test", "Client ClosedSendChannelException is happened")
}
}
CIO: A request through a proxy server results in 403 from Cloudflare
To reproduce run the following code:
val client = HttpClient(CIO) {
engine {
proxy = ProxyBuilder.http("http://3.127.33.188:3128") // public proxy server
}
}
val response = client.get("http://eu.kith.com/products.json")
println(response.status)
As a result, the 403 status code is returned from the Cloudflare. The same code with the OkHttp
engine works as expected and a server replies with a 200 status code.
Core
RFC 3986 recommendation for encoding URI is NOT followed
According to https://tools.ietf.org/html/rfc3986#section-2.3
For consistency, percent-encoded octets in the ranges of ALPHA
(%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period (%2E),
underscore (%5F), or tilde (%7E) should not be created by URI
producers and, when found in a URI, should be decoded to their
corresponding unreserved characters by URI normalizers.
But currently, Ktor client percent-encodes the hyphen, period, underscore and tilde.
This issue causes other issues like this: https://youtrack.jetbrains.com/issue/KTOR-917
So that the String.encodeURLQueryComponent() and String.encodeURLPath()
should be updated accordinglly. (Perhaps there are other places)
JS: window.location.origin returns null when executed in iframe via srcdoc attribute
Interactive maps in https://github.com/JetBrains/lets-plot after updating ktor
from 1.6.8
to 2.1.1
stopped loading tiles via websocket in https://datalore.jetbrains.com/ in both Chrome and Firefox. Yet tiles work well in kaggle, colab, jupyter, deepnote, nextjournal.
The last line to be executed is:
https://github.com/JetBrains/lets-plot/blob/master/gis/src/commonMain/kotlin/jetbrains/gis/tileprotocol/socket/TileWebSocket.kt#L24
Code inside this receiver never runs in datalore. There are no errors, no logs with engine { this@HttpClient.developmentMode = true }
, even no connection in devTools/Network tab.
Sadly I can't provide any example and you can't test it by yourself.
"java.lang.IllegalArgumentException: Failed requirement." in SelectorManagerSupport
Some of my Android users are getting the following exception:
Fatal Exception: java.lang.IllegalArgumentException: Failed requirement.
at io.ktor.network.selector.SelectorManagerSupport.select(SelectorManagerSupport.java:34)
at io.ktor.network.sockets.DatagramSocketImpl.receiveSuspend(DatagramSocketImpl.java:77)
at io.ktor.network.sockets.DatagramSocketImpl.receiveImpl(DatagramSocketImpl.java:66)
at io.ktor.network.sockets.DatagramSocketImpl.access$receiveImpl(DatagramSocketImpl.java:16)
at io.ktor.network.sockets.DatagramSocketImpl$receiver$1.invokeSuspend(DatagramSocketImpl.java:40)
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)
There are just a few users who get this exception. I am not able to reproduce the issue myself so it would be a bit hard to create a sample project. I am using Ktor UDP sockets which seem to be the cause by looking at the stack trace.
My app uses a manually built version of Ktor based on the following commit: https://github.com/Thomas-Vos/ktor/tree/94fab7c9411414506c27ba3e120af64210535d5e
Any ideas why this could be happening? It would seem to me that this type of exception should never happen.
ByteReadChannel is unable to read files with long lines
We're using ktor and stores session data in redis. To avoid problems with the contents of the session we're base64'ing the data before storing it. This results in a one line file, 16kb big. This data is read back using ByteReadChannel, which apparently can not handle such long lines.
Tested on 1.4.21 and 1.4.32.
Test to trigger the exception:
@Test
fun decodeSessionData() {
val fileContent = ByteBufferChannelTest::class.java.getResource("/16kb-on-one-line.txt").readText()
val channel = ByteReadChannel(fileContent.toByteArray())
// ktor session storage is using channel.readUTF8Line() to read session data
runBlocking {
channel.readUTF8Line()
}
}
Stacktrace on 1.4.21:
io.ktor.utils.io.charsets.TooLongLineException: Line is longer than limit
at io.ktor.utils.io.ByteBufferChannel$readUTF8LineToUtf8Suspend$2.invokeSuspend(ByteBufferChannel.kt:2080)
at io.ktor.utils.io.ByteBufferChannel$readUTF8LineToUtf8Suspend$2.invoke(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend$suspendImpl(ByteBufferChannel.kt:1825)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteBufferChannel.readUTF8LineToUtf8Suspend(ByteBufferChannel.kt:2064)
at io.ktor.utils.io.ByteBufferChannel.readUTF8LineToAscii(ByteBufferChannel.kt:1999)
at io.ktor.utils.io.ByteBufferChannel.readUTF8LineTo$suspendImpl(ByteBufferChannel.kt:2099)
at io.ktor.utils.io.ByteBufferChannel.readUTF8LineTo(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteBufferChannel.readUTF8Line$suspendImpl(ByteBufferChannel.kt:2103)
at io.ktor.utils.io.ByteBufferChannel.readUTF8Line(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteReadChannelKt.readUTF8Line(ByteReadChannel.kt:223)
Stacktrace on 1.4.32:
io.ktor.utils.io.charsets.TooLongLineException: Line is longer than limit
at io.ktor.utils.io.ByteBufferChannel$readUTF8LineToUtf8Suspend$2.invokeSuspend(ByteBufferChannel.kt:2094)
at io.ktor.utils.io.ByteBufferChannel$readUTF8LineToUtf8Suspend$2.invoke(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend$suspendImpl(ByteBufferChannel.kt:1827)
at io.ktor.utils.io.ByteBufferChannel.lookAheadSuspend(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteBufferChannel.readUTF8LineToUtf8Suspend(ByteBufferChannel.kt:2076)
at io.ktor.utils.io.ByteBufferChannel.readUTF8LineToAscii(ByteBufferChannel.kt:2011)
at io.ktor.utils.io.ByteBufferChannel.readUTF8LineTo$suspendImpl(ByteBufferChannel.kt:2113)
at io.ktor.utils.io.ByteBufferChannel.readUTF8LineTo(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteBufferChannel.readUTF8Line$suspendImpl(ByteBufferChannel.kt:2117)
at io.ktor.utils.io.ByteBufferChannel.readUTF8Line(ByteBufferChannel.kt)
at io.ktor.utils.io.ByteReadChannelKt.readUTF8Line(ByteReadChannel.kt:223)
Docs
Fix the Kweet sample
On an attempt to run the Kweet sample, the following error occurred:
Exception in thread "main" java.lang.NoSuchMethodError: 'org.h2.engine.SessionInterface org.h2.jdbc.JdbcConnection.getSession()'
Highlight use of the dispose function for Multipart Uploads in ktor docs.
Currently in the ktor docs there is no mention of the dispose function for multi part uploads, but this function could handle the deletion of temporary files created when large bodies are uploaded as seen here.
From my perspective it would be useful to include that information in the docs as it currently could be easily overlooked. Even the github code sample doesn't include this.
Please let me know if I'm missing something obvious or it is already included somewhere else.
Server
Websockets timeout doesn't cause a close of a connection
When setting a timeout on a ktor server websocket, the connection does not get closed once the timeout happens, if the client does not respond. It works as intended, if the timout happens, because the client is too slow to repond to a ping frame. It however does not work as intended, if the timeeout happens because the client lost their internet connection (in my case simulated by unplugging the LAN cable on the client machine).
What I expect to happen: When I set a ping and timeout on a websocket connection and the client loses the connection to the server, the timeout occures and I can no longer send Frames on the outgoing channel and any receive on the incoming channel results in a ClosedReceiveChannelException
What is actually happening: Once the client loses a connection, the timeout occurs, but a receive call is blocking as if the connection was still open. Sending frames on the outgoing channel does not result in an exception either.
What I think is the reason for this behaviour: Once the timeout occurs, the server sends a Close frame to the client and waits for the clients close acknowledgement. Since the client lost its connection, the close is not acknowledged and the server keeps the connection open.
This issue can be reproduced with the following code (connect to the websocket and remove the network cable between the server and the client):
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(WebSockets) {
pingPeriod = Duration.ofSeconds(1)
timeout = Duration.ofSeconds(1)
}
routing {
webSocket("/socket") {
print("Connection")
while(true) {
withTimeoutOrNull(1000) {
println(incoming.receive())
}
outgoing.send(Frame.Text("test"))
}
}
}
}.start(wait = true)
}
I would expect that after the client lost the connection, either the incoming.receive()
or the outgoing.send()
should throw an exception, but neither happens.
HOCON: CLI parameters don't override custom properties since 2.1.0
Version 2.0.3 is OK.
I've tested it with HOCON application.conf
file.
application.conf
example:
ktor {
deployment {
port = 8085
}
}
some {
custom {
prop = ololo
}
}
Command example:
java -jar build/libs/app.jar -P:some.custom.prop=azaza
Expected bevaviour:
val n = config.config("some.custom").property("prop")
// n is "azaza" (from command line)
Actual behaviour:
val n = config.config("some.custom").property("prop")
// n is "ololo" (default value from file)
It wouldn't be so bad if there was a better way to pass parameters. But the alternative is either the whole file or the use of environment variables, both of which are much, much less convenient.
Autoreloading: "Flow invariant is violated" error since Ktor 2.0.3
When trying to upgrade from Ktor 2.0.2 to 2.0.3 (and all the way to 2.1.2), we started encountering strange Flow invariant violations. The stacktrace looks something like this:
java.lang.IllegalStateException: Flow invariant is violated:
Flow was collected in [io.sentry.kotlin.SentryContext@51f480a5, com.corp.app.ApplicationKt$module$2$1$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@4652be27, CoroutineId(6), \"coroutine#6\":ScopeCoroutine{Active}@4f97a3bd, io.ktor.server.engine.ClassLoaderAwareContinuationInterceptor@3b8c8a07],
but emission happened in [io.sentry.kotlin.SentryContext@51f480a5, com.corp.app.ApplicationKt$module$2$1$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@4652be27, CoroutineId(6), \"coroutine#6\":ScopeCoroutine{Active}@4f97a3bd, io.ktor.server.engine.ClassLoaderAwareContinuationInterceptor@3b8c8a07].
Please refer to 'flow' documentation or use 'flowOn' instead
at kotlinx.coroutines.flow.internal.SafeCollector_commonKt.checkContext(SafeCollector.common.kt:85)
at kotlinx.coroutines.flow.internal.SafeCollector.checkContext(SafeCollector.kt:106)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:83)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:66)
at com.corp.app.extensions.aws.SqsKt$getFlowFor$1.invokeSuspend(Sqs.kt:33)
at \u0008\u0008\u0008(Coroutine boundary.\u0008(\u0008)
at com.corp.app.ApplicationKt$module$2$1$1$1.invokeSuspend(Application.kt:172)
at \u0008\u0008\u0008(Coroutine creation stacktrace.\u0008(\u0008)
at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:122)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)
at com.corp.app.ApplicationKt$module$2$1$1.invokeSuspend(Application.kt:171)
at com.corp.app.ApplicationKt$module$2$1$1.invoke(Application.kt)
at com.corp.app.ApplicationKt$module$2$1$1.invoke(Application.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
at kotlinx.coroutines.SupervisorKt.supervisorScope(Supervisor.kt:61)
at com.corp.app.ApplicationKt$module$2.invokeSuspend(Application.kt:166)
[...]
After trying all kinda ways trying to fix coroutine contexts, I found KTOR-4164 in the changelog and tried setting ktor.development = false
in my config which significantly changes behavior on this (makes it much less reproducible on my side). I think this may be an unintended regression of the "fix" for the other issue?
Just in case it's an issue with my code, the method in question looks like this (uses the AWS Java SDK):
fun <T> SqsAsyncClient.getFlowFor(queueUrl: String, klass: Class<T>) = flow {
val queue = this@getFlowFor
val flow = this
while (currentCoroutineContext().isActive) {
val response = queue.receiveMessage(
ReceiveMessageRequest.builder()
.queueUrl(queueUrl)
.maxNumberOfMessages(10)
.waitTimeSeconds(20)
.build()
).await()
if (!response.hasMessages()) continue
val messages = response.messages()
messages
.map { json.readValue(it.body(), klass) }
.forEach { flow.emit(it) }
}
}
SensitivityWatchEventModifier - Move the reflection call of this modifier out from the Ktor Core
Due to the certain restrictions in Android, this class (or even a reference) is on Google's blacklist. Is it possible to consider moving it out from the framework code? Or maybe provide some configurable mechanism to avoid the explicitly mentioned class in a code?
Issue details:
Android Documentation: https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces
Source code: https://github.com/ktorio/ktor/blob/ef4e65cd48ca370528390d1a5a35278ba42b3bf3/ktor-server/ktor-server-host-common/jvm/src/io/ktor/server/engine/internal/AutoReloadUtils.kt#L68
The veridex test mentioned in the documentation doesn't pass successfully with the following message:
Reflection blacklist Lcom/sun/nio/file/SensitivityWatchEventModifier;->HIGH use(s):
Lio/ktor/server/engine/ApplicationEngineEnvironmentReloading;->get_com_sun_nio_file_SensitivityWatchEventModifier_HIGH()Ljava/nio/file/WatchEvent$Modifier;
This issue prevents Ktor Core to be fully compatible with Android and also breaks Google's requirements for building a custom Android-based device.
Thanks in advance!
DefaultHeaders: a header is duplicated in a StatusPages's handler
The HTTP headers configured via the DefaultHeaders plugin will be duplicated in the response if handled by a StatusPages Plugin status handler.
Example application:
package com.example
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.plugins.defaultheaders.DefaultHeaders
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
fun main(args: Array<String>): Unit =
io.ktor.server.cio.EngineMain.main(args)
fun Application.module() {
install(DefaultHeaders) {
header("Foo", "Bar")
}
install(StatusPages) {
status(HttpStatusCode.NotFound) { call, httpStatus ->
call.respond(httpStatus, "error")
}
}
routing {
get("/") {
call.respondText("Hello World!")
}
}
}
With a request to an invalid URL, like GET http://localhost:8080/invalid-url
you will receive the following response:
HTTP/1.1 404 Not Found
Foo: Bar
Date: Fri, 14 Oct 2022 09:03:55 GMT
Server: Ktor/2.1.2
Foo: Bar
Content-Length: 5
Content-Type: text/plain; charset=UTF-8
Connection: keep-alive
error
As you can see, the header Foo: Bar
is duplicated.
A request to the valid URL GET http://localhost:8080/
does not lead to the duplicated header:
HTTP/1.1 200 OK
Foo: Bar
Date: Fri, 14 Oct 2022 09:23:22 GMT
Server: Ktor/2.1.2
Content-Length: 12
Content-Type: text/plain; charset=UTF-8
Connection: keep-alive
Hello World!
Netty HTTP/2: response headers contain ":status" header and that leads to IllegalHeaderNameException in the ConditionalHeaders plugin
This causes an exception io.ktor.http.IllegalHeaderNameException: Header name ':status' contains illegal character ':' (code 58)
when processing ConditionalHeaders
.
Non-SSL connector is not affected.
Version 2.0.3 is not affected, 2.1.1 and 2.1.2 are.
I'm using the Netty engine.
Maven: ktor-server-test-host-jvm causes dependency error starting from Ktor 2.0.3
When trying to test my ktor application (using v2.1.1), maven produces an error:
Could not find artifact org.jetbrains.kotlinx:kotlinx-coroutines-bom:jar:1.6.3
Using that dependency directly, without using "ktor-server-test-host-jvm" works. But as soon as I depend on ktor-server-test-host-jvm:2.1.1 I get this error.
The same issue doesn't happen with version 2.0.0.
Autoreloading: ClassCastException when retrieving plugins in testApplication
Hello, while working on creating a Ktor plugin and setting up tests for it, I've encountered the following bug:
When creating a test that directly retrieves a plugin (either via the Application.plugin() function or when using custom extension functions over Application), the code crashes with a ClassCastException. While the class is the exact same, the instance of the class returned by the test Ktor application comes from another classloader, leading to the exceptions you can see below. It seems to be related to the development mode's class reloading feature interfering somewhere along the line.
Repro
- Here's a GitHub repository to repro this issue: https://github.com/utybo/cce_ktor_test
- Here's a GitHub Actions run with the failing tests: https://github.com/utybo/cce_ktor_test/runs/7724799083?check_suite_focus=true#step:5:34
Here's an example, let's take this plugin:
class MyCalculatorPlugin {
class Configuration
companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, MyCalculatorPlugin> {
override val key = AttributeKey<MyCalculatorPlugin>("MyCalculatorPlugin")
override fun install(
pipeline: ApplicationCallPipeline,
configure: Configuration.() -> Unit
): MyCalculatorPlugin {
return MyCalculatorPlugin()
}
}
fun add(x: Int, y: Int): Int {
return x + y
}
}
val Application.myCalculator get() = plugin(MyCalculatorPlugin)
The following tests will fail:
@Test
fun `Test simple sum via 'myCalculator' utility property`() = testApplication {
install(MyCalculatorPlugin)
application {
val result = myCalculator.add(1, 2) // <-- ClassCastException
assertEquals(3, result)
}
}
@Test
fun `Test simple sum via 'plugin' function`() = testApplication {
install(MyCalculatorPlugin)
application {
val result = plugin(MyCalculatorPlugin).add(1, 2) // <-- ClassCastException
assertEquals(3, result)
}
}
Stack trace for the first test:
class org.example.ccektortest.MyCalculatorPlugin cannot be cast to class org.example.ccektortest.MyCalculatorPlugin (org.example.ccektortest.MyCalculatorPlugin is in unnamed module of loader 'app'; org.example.ccektortest.MyCalculatorPlugin is in unnamed module of loader io.ktor.server.engine.OverridingClassLoader$ChildURLClassLoader @68ad99fe)
java.lang.ClassCastException: class org.example.ccektortest.MyCalculatorPlugin cannot be cast to class org.example.ccektortest.MyCalculatorPlugin (org.example.ccektortest.MyCalculatorPlugin is in unnamed module of loader 'app'; org.example.ccektortest.MyCalculatorPlugin is in unnamed module of loader io.ktor.server.engine.OverridingClassLoader$ChildURLClassLoader @68ad99fe)
at org.example.ccektortest.LibraryKt.getMyCalculator(Library.kt:28)
at org.example.ccektortest.LibraryTest$Test simple sum via 'myCalculator' utility property$1$1.invoke(LibraryTest.kt:17)
at org.example.ccektortest.LibraryTest$Test simple sum via 'myCalculator' utility property$1$1.invoke(LibraryTest.kt:16)
at io.ktor.server.engine.internal.CallableUtilsKt.executeModuleFunction(CallableUtils.kt:51)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:334)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:333)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:358)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.launchModuleByName(ApplicationEngineEnvironmentReloading.kt:333)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$launchModuleByName(ApplicationEngineEnvironmentReloading.kt:32)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:321)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:312)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:340)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:312)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:149)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:279)
at io.ktor.server.testing.TestApplicationEngine.start(TestApplicationEngine.kt:125)
at io.ktor.server.engine.ApplicationEngine$DefaultImpls.start$default(ApplicationEngine.kt:68)
at io.ktor.server.testing.TestApplicationKt.testApplication(TestApplication.kt:267)
at org.example.ccektortest.LibraryTest.Test simple sum via 'myCalculator' utility property(LibraryTest.kt:14)
[snip]
Stack trace for the second test:
class org.example.ccektortest.MyCalculatorPlugin cannot be cast to class org.example.ccektortest.MyCalculatorPlugin (org.example.ccektortest.MyCalculatorPlugin is in unnamed module of loader 'app'; org.example.ccektortest.MyCalculatorPlugin is in unnamed module of loader io.ktor.server.engine.OverridingClassLoader$ChildURLClassLoader @5b057c8c)
java.lang.ClassCastException: class org.example.ccektortest.MyCalculatorPlugin cannot be cast to class org.example.ccektortest.MyCalculatorPlugin (org.example.ccektortest.MyCalculatorPlugin is in unnamed module of loader 'app'; org.example.ccektortest.MyCalculatorPlugin is in unnamed module of loader io.ktor.server.engine.OverridingClassLoader$ChildURLClassLoader @5b057c8c)
at org.example.ccektortest.LibraryTest$Test simple sum via 'plugin' function$1$1.invoke(LibraryTest.kt:26)
at org.example.ccektortest.LibraryTest$Test simple sum via 'plugin' function$1$1.invoke(LibraryTest.kt:25)
at io.ktor.server.engine.internal.CallableUtilsKt.executeModuleFunction(CallableUtils.kt:51)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:334)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:333)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:358)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.launchModuleByName(ApplicationEngineEnvironmentReloading.kt:333)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$launchModuleByName(ApplicationEngineEnvironmentReloading.kt:32)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:321)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:312)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:340)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:312)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:149)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:279)
at io.ktor.server.testing.TestApplicationEngine.start(TestApplicationEngine.kt:125)
at io.ktor.server.engine.ApplicationEngine$DefaultImpls.start$default(ApplicationEngine.kt:68)
at io.ktor.server.testing.TestApplicationKt.testApplication(TestApplication.kt:267)
at org.example.ccektortest.LibraryTest.Test simple sum via 'plugin' function(LibraryTest.kt:23)
[snip]
Workaround
Disable development mode in tests by adding the following line inside the testApplication
block:
environment { developmentMode = false }
Versions
- OpenJDK 17 (also repro'd with Temurin JDK 17)
- Ktor 2.0.3
- Kotlin 1.7.10
- Gradle 7.5
Shared
WebSocketDeflateExtension configureProtocols always failed with stackOverflow
In this extension code looks weird. I guess, this was developed to save a previous manualConfiguration, but now it calls themselves infinitely.
internal var manualConfig: (MutableList<WebSocketExtensionHeader>) -> Unit = {}
/**
* Configure which protocols should send the client.
*/
public fun configureProtocols(block: (protocols: MutableList<WebSocketExtensionHeader>) -> Unit) {
manualConfig = {
manualConfig(it)
block(it)
}
}
There should be a list of manualConf
to do so.
internal val manualConfig: List<(MutableList<WebSocketExtensionHeader>) -> Unit> = mutableList()
/**
* Configure which protocols should send the client.
*/
public fun configureProtocols(block: (protocols: MutableList<WebSocketExtensionHeader>) -> Unit) {
manualConfig.add(block)
}
// Usage
internal fun build(): List<WebSocketExtensionHeader> {
val result = mutableListOf<WebSocketExtensionHeader>()
val parameters = mutableListOf<String>()
if (clientNoContextTakeOver) {
parameters += CLIENT_NO_CONTEXT_TAKEOVER
}
if (serverNoContextTakeOver) {
parameters += SERVER_NO_CONTEXT_TAKEOVER
}
result += WebSocketExtensionHeader(PERMESSAGE_DEFLATE, parameters)
manualConfig.forEach { configure ->
configure(result)
}
return result
}
Other
Update Kotlin to 1.7.20
CIO engine has wrong doc for request timeout
Request timeout handles the whole request time, not until body starts to download
2.1.2
released 30th September 2022
Build System Plugin
NoSuchMethodError: notCompatibleWithConfigurationCache when older version of Gradle is used
Hi,
when I tried to create a new project with the start.ktor.io link and tried to build it on the Intellij idea community, I faced this issue:
Could not create task ':jib'.
'void org.gradle.api.Task.notCompatibleWithConfigurationCache(java.lang.String)'
complete log:
> Task :prepareKotlinBuildScriptModel UP-TO-DATE
FAILURE: Build failed with an exception.
* What went wrong:
Could not create task ':jib'.
'void org.gradle.api.Task.notCompatibleWithConfigurationCache(java.lang.String)'
* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Exception is:
com.intellij.openapi.externalSystem.model.ExternalSystemException: Could not create task ':jib'.
'void org.gradle.api.Task.notCompatibleWithConfigurationCache(java.lang.String)'
at org.jetbrains.plugins.gradle.model.ProjectImportAction.addBuildModels(ProjectImportAction.java:346)
at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:127)
at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:42)
at org.gradle.tooling.internal.consumer.connection.InternalBuildActionAdapter.execute(InternalBuildActionAdapter.java:64)
at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.runAction(AbstractClientProvidedBuildActionRunner.java:132)
at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.apply(AbstractClientProvidedBuildActionRunner.java:119)
at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.apply(AbstractClientProvidedBuildActionRunner.java:96)
at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$fromBuildModel$2(DefaultBuildTreeLifecycleController.java:84)
at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$doBuild$4(DefaultBuildTreeLifecycleController.java:105)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.doBuild(DefaultBuildTreeLifecycleController.java:99)
at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.fromBuildModel(DefaultBuildTreeLifecycleController.java:70)
at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:58)
at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:52)
at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
at org.gradle.internal.buildtree.ProblemReportingBuildActionRunner.run(ProblemReportingBuildActionRunner.java:50)
at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:69)
at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:90)
at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
at org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.lambda$execute$0(RootBuildLifecycleBuildActionExecutor.java:40)
at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:128)
at org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.execute(RootBuildLifecycleBuildActionExecutor.java:40)
at org.gradle.internal.buildtree.DefaultBuildTreeContext.execute(DefaultBuildTreeContext.java:40)
at org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.lambda$execute$0(BuildTreeLifecycleBuildActionExecutor.java:40)
at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:53)
at org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.execute(BuildTreeLifecycleBuildActionExecutor.java:40)
at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$3.call(RunAsBuildOperationBuildActionExecutor.java:61)
at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$3.call(RunAsBuildOperationBuildActionExecutor.java:57)
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:79)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:79)
at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor.execute(RunAsBuildOperationBuildActionExecutor.java:57)
at org.gradle.tooling.internal.provider.ContinuousBuildActionExecutor.execute(ContinuousBuildActionExecutor.java:103)
at org.gradle.tooling.internal.provider.SubscribableBuildActionExecutor.execute(SubscribableBuildActionExecutor.java:64)
at org.gradle.internal.session.DefaultBuildSessionContext.execute(DefaultBuildSessionContext.java:46)
at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.lambda$execute$0(BuildSessionLifecycleBuildActionExecuter.java:55)
at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:69)
at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.execute(BuildSessionLifecycleBuildActionExecuter.java:54)
at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.execute(BuildSessionLifecycleBuildActionExecuter.java:36)
at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:63)
at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:58)
at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:42)
at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
at org.gradle.util.internal.Swapper.swap(Swapper.java:38)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreationException: Could not create task ':jib'.
at org.gradle.api.internal.tasks.DefaultTaskContainer.taskCreationException(DefaultTaskContainer.java:715)
at org.gradle.api.internal.tasks.DefaultTaskContainer.access$600(DefaultTaskContainer.java:76)
at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider.domainObjectCreationException(DefaultTaskContainer.java:707)
at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.tryCreate(DefaultNamedDomainObjectCollection.java:948)
at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider.access$1401(DefaultTaskContainer.java:654)
at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider$1.run(DefaultTaskContainer.java:680)
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:74)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:74)
at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider.tryCreate(DefaultTaskContainer.java:676)
at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.calculateOwnValue(DefaultNamedDomainObjectCollection.java:929)
at org.gradle.api.internal.provider.AbstractMinimalProvider.getOrNull(AbstractMinimalProvider.java:93)
at org.gradle.api.internal.DefaultNamedDomainObjectCollection.findByName(DefaultNamedDomainObjectCollection.java:295)
at org.gradle.api.internal.tasks.DefaultTaskContainer.findByName(DefaultTaskContainer.java:558)
at org.gradle.api.internal.tasks.DefaultTaskContainer.findByName(DefaultTaskContainer.java:75)
at org.gradle.plugins.ide.internal.tooling.GradleProjectBuilder.tasks(GradleProjectBuilder.java:104)
at org.gradle.plugins.ide.internal.tooling.GradleProjectBuilder.buildHierarchy(GradleProjectBuilder.java:80)
at org.gradle.plugins.ide.internal.tooling.GradleProjectBuilder.buildAll(GradleProjectBuilder.java:52)
at org.gradle.plugins.ide.internal.tooling.IdeaModelBuilder.buildAll(IdeaModelBuilder.java:77)
at org.gradle.plugins.ide.internal.tooling.IdeaModelBuilder.buildAll(IdeaModelBuilder.java:59)
at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$BuilderWithNoParameter.build(DefaultToolingModelBuilderRegistry.java:223)
at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$UserCodeAssigningBuilder.lambda$build$0(DefaultToolingModelBuilderRegistry.java:322)
at org.gradle.configuration.internal.DefaultUserCodeApplicationContext$CurrentApplication.reapply(DefaultUserCodeApplicationContext.java:98)
at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$UserCodeAssigningBuilder.build(DefaultToolingModelBuilderRegistry.java:322)
at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$LockSingleProjectBuilder.lambda$build$0(DefaultToolingModelBuilderRegistry.java:265)
at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$withProjectLock$3(DefaultProjectStateRegistry.java:340)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:340)
at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:321)
at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$LockSingleProjectBuilder.build(DefaultToolingModelBuilderRegistry.java:265)
at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$BuildOperationWrappingBuilder$1.call(DefaultToolingModelBuilderRegistry.java:300)
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:79)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:79)
at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$BuildOperationWrappingBuilder.build(DefaultToolingModelBuilderRegistry.java:297)
at org.gradle.tooling.internal.provider.runner.DefaultBuildController.getModel(DefaultBuildController.java:102)
at org.gradle.tooling.internal.consumer.connection.ParameterAwareBuildControllerAdapter.getModel(ParameterAwareBuildControllerAdapter.java:39)
at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.getModel(UnparameterizedBuildController.java:113)
at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.getModel(NestedActionAwareBuildControllerAdapter.java:31)
at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.findModel(UnparameterizedBuildController.java:97)
at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.findModel(NestedActionAwareBuildControllerAdapter.java:31)
at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.findModel(UnparameterizedBuildController.java:81)
at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.findModel(NestedActionAwareBuildControllerAdapter.java:31)
at org.jetbrains.plugins.gradle.model.ProjectImportAction$MyBuildController.findModel(ProjectImportAction.java:557)
at org.jetbrains.plugins.gradle.model.ProjectImportAction$MyBuildController.findModel(ProjectImportAction.java:578)
at org.jetbrains.plugins.gradle.model.ClassSetImportModelProvider.populateBuildModels(ClassSetImportModelProvider.java:27)
at org.jetbrains.plugins.gradle.model.ProjectImportAction.addBuildModels(ProjectImportAction.java:334)
at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:127)
at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:42)
at org.gradle.tooling.internal.consumer.connection.InternalBuildActionAdapter.execute(InternalBuildActionAdapter.java:64)
at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.runAction(AbstractClientProvidedBuildActionRunner.java:132)
at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.apply(AbstractClientProvidedBuildActionRunner.java:119)
at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.apply(AbstractClientProvidedBuildActionRunner.java:96)
at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$fromBuildModel$2(DefaultBuildTreeLifecycleController.java:84)
at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.lambda$doBuild$4(DefaultBuildTreeLifecycleController.java:105)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.doBuild(DefaultBuildTreeLifecycleController.java:99)
at org.gradle.internal.buildtree.DefaultBuildTreeLifecycleController.fromBuildModel(DefaultBuildTreeLifecycleController.java:70)
at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:58)
at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:52)
at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
at org.gradle.internal.buildtree.ProblemReportingBuildActionRunner.run(ProblemReportingBuildActionRunner.java:50)
at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:69)
at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:90)
at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
at org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.lambda$execute$0(RootBuildLifecycleBuildActionExecutor.java:40)
at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:128)
at org.gradle.launcher.exec.RootBuildLifecycleBuildActionExecutor.execute(RootBuildLifecycleBuildActionExecutor.java:40)
at org.gradle.internal.buildtree.DefaultBuildTreeContext.execute(DefaultBuildTreeContext.java:40)
at org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.lambda$execute$0(BuildTreeLifecycleBuildActionExecutor.java:40)
at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:53)
at org.gradle.launcher.exec.BuildTreeLifecycleBuildActionExecutor.execute(BuildTreeLifecycleBuildActionExecutor.java:40)
at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$3.call(RunAsBuildOperationBuildActionExecutor.java:61)
at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor$3.call(RunAsBuildOperationBuildActionExecutor.java:57)
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:79)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:79)
at org.gradle.launcher.exec.RunAsBuildOperationBuildActionExecutor.execute(RunAsBuildOperationBuildActionExecutor.java:57)
at org.gradle.tooling.internal.provider.ContinuousBuildActionExecutor.execute(ContinuousBuildActionExecutor.java:103)
at org.gradle.tooling.internal.provider.SubscribableBuildActionExecutor.execute(SubscribableBuildActionExecutor.java:64)
at org.gradle.internal.session.DefaultBuildSessionContext.execute(DefaultBuildSessionContext.java:46)
at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.lambda$execute$0(BuildSessionLifecycleBuildActionExecuter.java:55)
at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:69)
at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.execute(BuildSessionLifecycleBuildActionExecuter.java:54)
at org.gradle.tooling.internal.provider.BuildSessionLifecycleBuildActionExecuter.execute(BuildSessionLifecycleBuildActionExecuter.java:36)
at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:63)
at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:58)
at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:42)
at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
at org.gradle.util.internal.Swapper.swap(Swapper.java:38)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
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)
Caused by: java.lang.NoSuchMethodError: 'void org.gradle.api.Task.notCompatibleWithConfigurationCache(java.lang.String)'
at io.ktor.plugin.features.DockerKt.markJibTaskNotCompatible(Docker.kt:140)
at org.gradle.configuration.internal.DefaultUserCodeApplicationContext$CurrentApplication$1.execute(DefaultUserCodeApplicationContext.java:112)
at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction$1.run(DefaultCollectionCallbackActionDecorator.java:95)
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:74)
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:74)
at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction.execute(DefaultCollectionCallbackActionDecorator.java:92)
at org.gradle.api.internal.DefaultMutationGuard$2.execute(DefaultMutationGuard.java:44)
at org.gradle.api.internal.DefaultMutationGuard$2.execute(DefaultMutationGuard.java:44)
at org.gradle.api.internal.collections.CollectionFilter$1.execute(CollectionFilter.java:59)
at org.gradle.internal.ImmutableActionSet$SetWithManyActions.execute(ImmutableActionSet.java:329)
at org.gradle.api.internal.DefaultDomainObjectCollection.doAdd(DefaultDomainObjectCollection.java:264)
at org.gradle.api.internal.DefaultNamedDomainObjectCollection.doAdd(DefaultNamedDomainObjectCollection.java:113)
at org.gradle.api.internal.DefaultDomainObjectCollection.add(DefaultDomainObjectCollection.java:258)
at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.tryCreate(DefaultNamedDomainObjectCollection.java:944)
... 140 more
* Get more help at
https://help.gradle.org
BUILD FAILED in 1s
Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
See
https://docs.gradle.org/7.1/userguide/command_line_interface.html#sec:command_line_warnings
Versions:
ktor_version=2.1.0
kotlin_version=1.7.10
logback_version=1.2.11
kotlin.code.style=official
plugins {
application
kotlin("jvm") version "1.7.10"
id("io.ktor.plugin") version "2.1.0"
}
Client
HttpCacheEntry ignoring Request Cache-Control directives
We've found an issue with HttpCacheEntry
. The current implementation checks whether the cache entry has expired or not. If the entry has not expired, ValidateStatus.ShouldNotValidate
is returned immediately. Here, however, the cache control directives of the request would have to be checked beforehand. If the client sets no-cache
or max-age=0, must-revalidate, we would expect ValidateStatus.ShouldValidate
to be returned (see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control?retiredLocale=de#request_directives). Here is the current implementation supplemented with comments:
internal fun HttpCacheEntry.shouldValidate(request: HttpRequestBuilder): ValidateStatus {
val cacheControl = parseHeaderValue(responseHeaders[HttpHeaders.CacheControl])
val validMillis = expires.timestamp - getTimeMillis()
if (CacheControl.NO_CACHE in cacheControl) return ValidateStatus.ShouldValidate
// What if the REQUEST has a Cache-Control header of no-cache?
// The current implementation seems to ignore them in the following line.
if (validMillis > 0) return ValidateStatus.ShouldNotValidate
if (CacheControl.MUST_REVALIDATE in cacheControl) return ValidateStatus.ShouldValidate
val requestCacheControl = parseHeaderValue(request.headers[HttpHeaders.CacheControl])
val maxStale = requestCacheControl.firstOrNull { it.value.startsWith("max-stale=") }
?.value?.substring("max-stale=".length)
?.toIntOrNull() ?: 0
val maxStaleMillis = maxStale * 1000L
if (validMillis + maxStaleMillis > 0) return ValidateStatus.ShouldWarn
return ValidateStatus.ShouldValidate
}
Native: Wrong status code when requesting with DELETE method and body
Hi!
When i do DELETE request, each second request response 400 status, at first i thought that this is server issue but i didn't saw any logs with this code answer. Then i made sample ktor server with DELETE path, and tried made a request. To my surprise only first request was have successful answer, another answers have 404 error, but like the previous case i did't see any wrong request from client to my sample server.
Like i now macosx64 and linuxX64 targets use curl under the code
When i did curl request through terminal for this server path, i did not arise any errors
I tried user previous versions ktor-client-curl-macosx64 and another version of ktor client but its didn't help
Please Help
=(
Last config Dependencies for Kotlin Native Part (which I used)
sourceSets {
val ktorVersion="1.6.0-RC2"
val ktorSerializationVersion="1.6.7"
val kotlinxVersion="1.5.2"
val serializationVersion="1.3.0"
val nativeMain by getting {
dependencies {
api("io.ktor:ktor-client-core:$ktorVersion")
api("io.ktor:ktor-client-json:$ktorVersion")
api("io.ktor:ktor-client-serialization-macosx64:$ktorSerializationVersion")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxVersion-native-mt")
api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
api("io.ktor:ktor-client-curl-macosx64:1.6.7")
}
}
val nativeTest by getting
}
Execution logs with expectSuccess = false
> Task :runDebugExecutableNative
> start request
> response status -\> 200 OK
> start request
> response status -\> 404 Not Found
> start request
> response status -\> 404 Not Found
> start request
> response status -\> 404 Not Found
>
or Execution logs with expectSuccess = true
> Task :runDebugExecutableNative FAILED
start request
response status -> 200 OK
start request
Uncaught Kotlin exception: io.ktor.client.features.ClientRequestException: Client request(http://localhost:8080/samplepath) invalid: 404 Not Found. Text: ""
at 0 untitled.kexe 0x000000010d5972f9 kfun:kotlin.Throwable#<init>(kotlin.String?){} + 89 (/Users/teamcity1/teamcity_work/c75bfccfe067806/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:24:56)
at 1 untitled.kexe 0x000000010d590327 kfun:kotlin.Exception#<init>(kotlin.String?){} + 87 (/Users/teamcity1/teamcity_work/c75bfccfe067806/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:58)
at 2 untitled.kexe 0x000000010d5904b7 kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 87 (/Users/teamcity1/teamcity_work/c75bfccfe067806/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:58)
at 3 untitled.kexe 0x000000010d590747 kfun:kotlin.IllegalStateException#<init>(kotlin.String?){} + 87 (/Users/teamcity1/teamcity_work/c75bfccfe067806/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:70:58)
at 4 untitled.kexe 0x000000010d8c9613 kfun:io.ktor.client.features.ResponseException#<init>(io.ktor.client.statement.HttpResponse;kotlin.String){} + 675 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/features/DefaultResponseValidation.kt:65:5)
at 5 untitled.kexe 0x000000010d8c8acf kfun:io.ktor.client.features.ClientRequestException#<init>(io.ktor.client.statement.HttpResponse;kotlin.String){} + 415 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/features/DefaultResponseValidation.kt:110:5)
at 6 untitled.kexe 0x000000010d8ca796 kfun:io.ktor.client.features.$addDefaultResponseValidation$lambda-1$lambda-0COROUTINE$23.invokeSuspend#internal + 3622 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/features/DefaultResponseValidation.kt:47:38)
at 7 untitled.kexe 0x000000010d8caf7c kfun:io.ktor.client.features.$addDefaultResponseValidation$lambda-1$lambda-0COROUTINE$23.invoke#internal + 316 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/features/DefaultResponseValidation.kt:27:26)
at 8 untitled.kexe 0x000000010d8d530c kfun:io.ktor.client.features.HttpCallValidator.$validateResponseCOROUTINE$30.invokeSuspend#internal + 1484 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/features/HttpCallValidator.kt:54:38)
at 9 untitled.kexe 0x000000010d8d5837 kfun:io.ktor.client.features.HttpCallValidator.validateResponse#internal + 359 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/features/HttpCallValidator.kt:53:21)
at 10 untitled.kexe 0x000000010d8d9b67 kfun:io.ktor.client.features.HttpCallValidator.Companion.$install$lambda-3COROUTINE$29.invokeSuspend#internal + 679 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/features/HttpCallValidator.kt:133:25)
at 11 untitled.kexe 0x000000010d8da162 kfun:io.ktor.client.features.HttpCallValidator.Companion.$install$lambda-3COROUTINE$29.invoke#internal + 434 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/features/HttpCallValidator.kt:132:39)
at 12 untitled.kexe 0x000000010d8e8b42 kfun:io.ktor.client.features.HttpSend.Feature.$install$lambda-0COROUTINE$37.invokeSuspend#internal + 3682 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/features/HttpSend.kt:96:43)
at 13 untitled.kexe 0x000000010d5bbc89 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 761 (/Users/teamcity1/teamcity_work/c75bfccfe067806/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:30:39)
at 14 untitled.kexe 0x000000010d85531e kfun:io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith#internal + 942 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/SuspendFunctionGun.kt:191:18)
at 15 untitled.kexe 0x000000010d854eaf kfun:io.ktor.util.pipeline.SuspendFunctionGun.loop#internal + 2223 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/SuspendFunctionGun.kt:147:21)
at 16 untitled.kexe 0x000000010d857b85 kfun:io.ktor.util.pipeline.SuspendFunctionGun.object-1.resumeWith#internal + 373 (/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-utils/common/src/io/ktor/util/pipeline/SuspendFunctionGun.kt:93:13)
at 17 untitled.kexe 0x000000010d5bbfc4 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 1588 (/Users/teamcity1/teamcity_work/c75bfccfe067806/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:43:32)
at 18 untitled.kexe 0x000000010d71b614 kfun:kotlinx.coroutines.DispatchedTask#run(){} + 3412 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Debug.kt:17:2786)
at 19 untitled.kexe 0x000000010d6f182f kfun:kotlinx.coroutines.EventLoopImplBase#processNextEvent(){}kotlin.Long + 895 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/EventLoop.common.kt:277:22)
at 20 untitled.kexe 0x000000010d72e873 kfun:kotlinx.coroutines#runEventLoop(kotlinx.coroutines.EventLoop?;kotlin.Function0<kotlin.Boolean>){} + 979 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:80:40)
at 21 untitled.kexe 0x000000010d72df6e kfun:kotlinx.coroutines.BlockingCoroutine.joinBlocking#internal + 382 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:67:9)
at 22 untitled.kexe 0x000000010d72d258 kfun:kotlinx.coroutines#runBlocking(kotlin.coroutines.CoroutineContext;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.CoroutineScope,0:0>){0§<kotlin.Any?>}0:0 + 1304 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:50:22)
at 23 untitled.kexe 0x000000010d72d828 kfun:kotlinx.coroutines#runBlocking$default(kotlin.coroutines.CoroutineContext?;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.CoroutineScope,0:0>;kotlin.Int){0§<kotlin.Any?>}0:0 + 328 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:33:15)
at 24 untitled.kexe 0x000000010d9c42a1 kfun:#main(){} + 609 (/..../kotlin/Main.kt:46:5)
at 25 untitled.kexe 0x000000010d9c7a47 Konan_start + 135 (/.../src/nativeMain/kotlin/Main.kt:14:1)
at 26 untitled.kexe 0x000000010da0643e Init_and_run_start + 94
at 27 libdyld.dylib 0x00007fff204d7f3d start + 1
at 28 ??? 0x0000000000000001 0x0 + 1
Execution failed for task ':runDebugExecutableNative'.
simple Native Client
import io.ktor.client.HttpClient
import io.ktor.client.engine.curl.Curl
import io.ktor.client.features.HttpTimeout
import io.ktor.client.features.json.Json
import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.request.delete
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
fun main() {
val jsonBuilder: Json = Json {
encodeDefaults = true
ignoreUnknownKeys = true
isLenient = true
allowStructuredMapKeys = true
useArrayPolymorphism = true
}
val client: HttpClient = HttpClient(Curl)
{
expectSuccess = false
this.Json {
serializer = KotlinxSerializer(jsonBuilder)
}
install(HttpTimeout) {
val timeOut = 60_000L
connectTimeoutMillis = timeOut
requestTimeoutMillis = timeOut
socketTimeoutMillis = timeOut
}
}
suspend fun testRequest(): HttpResponse {
return client.delete("http://localhost:8080/samplepath") {
contentType(ContentType.Application.Json)
body = "{}"
}
}
runBlocking() {
repeat(4) {
println("start request")
val response = testRequest()
println("response status -> ${response.status} ")
}
}
Sample Server Side
fun main() {
embeddedServer(Netty, port = 8080, host = "127.0.0.1") {
routing {
post("/samplepath") { b ->
call.respond("{}")
}
}
}.start(wait = true)
}
Docs
Fix doc issues related to creating and using a Ktor run configuration
Handling requests: Missing documentation about reading bytes from request body
We miss a section in the documentation about receiving request body as ByteArray
and reading request body bytes from ByteReadChannel
.
Server
Routing: Wrong content-type results in 400 Bad Request instead of 415 Unsupported Media type
`Route.contentType` delegates to `Route.header`, but it always results in 400 error code on failure
package com.example
import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.application.log
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.server.response.respond
import io.ktor.server.routing.accept
import io.ktor.server.routing.contentType
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
fun main() {
embeddedServer(Netty, port = 3000) {
module()
}.apply {
start(wait = true)
}
}
fun Application.module(): Unit {
routing {
route("/some/plain/path", HttpMethod.Get) {
contentType(ContentType.Application.Json) {
handle {
call.respond("ok")
}
}
}
}
}
$ curl -i -XGET localhost:3000/some/plain/path -H'Content-Type: application/xml'
HTTP/1.1 400 Bad Request
Content-Length: 0
SameSite cookies are broken when Base64 cookie encoding is selected
When cookies are added to HTTP responses in Ktor Server, they can undergo additional encoding by Ktor. One of these encodings is CookieEncoding.BASE64_ENCODING
. This encoding is applied both to the cookie values and specified extra extensions. This can have unexpected consequences.
In particular, the SameSite
cookie parameter has to be set via an extension parameter. When we set this parameter to Strict
with Base64 cookie encoding enabled, Ktor sent the parameter as SameSite=U3RyaWN0
instead of SameSite=Strict
. This is not recognized by browsers; Firefox 103 seems to interpret this as SameSite=None
.
The issue can be triggered when creating cookies via ApplicationCall.response.cookies.append()
. I don't know if this can happen when cookies are manipulated via Sessions (https://ktor.io/docs/sessions.html#cookie).
The problem can be worked around easily in applications by encoding the cookie value via Base64 manually and letting Ktor only do CookieEncoding.URI_ENCODING
. However, this behaviour of Ktor was quite surprising to me.
Default host address 0.0.0.0 isn't reachable on Windows
Hello,
I have created a brand new ktor project from my Windows PC. When pressing on the giving URL in the console: 0.0.0.0:8080
It opens the browser but does not seems to reach the URL.
It works properly on MacOS devices. It does work when I change the URL to localhost:8080.
I was wondering if you could reproduce the same issue when creating a new ktor project.
Config of the project:
{width=70%}
My routing class:
{width=70%}
My application class:
{width=70%}
Cheers
Test Infrastructure
testApplication does not handle port and connectors
The following test failed. It has 2 problems:
- Route under
port
DSL ignored - Second
connector
is ignored byio.ktor.server.testing.TestApplicationEngine#resolvedConnectors
Test Example
import io.ktor.client.request.get
import io.ktor.client.request.host
import io.ktor.client.request.port
import io.ktor.client.utils.EmptyContent
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.engine.connector
import io.ktor.server.response.respond
import io.ktor.server.routing.get
import io.ktor.server.routing.port
import io.ktor.server.routing.routing
import io.ktor.server.testing.testApplication
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class ConnectorTest {
@Test
fun failed(): Unit = testApplication {
environment {
connector {
port = 8080
}
connector {
port = 8081
}
module {
route()
}
}
assertEquals(
HttpStatusCode.Forbidden,
client.get("/no-port") {
host = "0.0.0.0"
port = 8080
}.status
)
assertEquals(
HttpStatusCode.BadRequest,
client.get("/second") {
host = "0.0.0.0"
port = 8081
}.status
)
assertEquals(
HttpStatusCode.Unauthorized,
client.get("/first") {
host = "0.0.0.0"
port = 8080
}.status
)
}
private fun Application.route() = routing {
port(8080) {
get("/first") {
call.response.status(HttpStatusCode.Unauthorized)
call.respond(EmptyContent)
}
}
port(8081) {
get("/second") {
call.response.status(HttpStatusCode.BadRequest)
call.respond(EmptyContent)
}
}
get("/no-port") {
call.response.status(HttpStatusCode.Forbidden)
call.respond(EmptyContent)
}
}
}
But this configuration handles normally at MainApplication
TestApplicationEngine error handling is inconsistent with DefaultEnginePipeline, breaking clients
TestApplicationEngine
has the following error handler code:
private suspend fun PipelineContext<Unit, ApplicationCall>.handleTestFailure(cause: Throwable) {
tryRespondError(defaultExceptionStatusCode(cause) ?: throw cause)
}
Which differs from DefaultEnginePipeline
in the following way:
public suspend fun handleFailure(call: ApplicationCall, error: Throwable) {
logError(call, error)
tryRespondError(call, defaultExceptionStatusCode(error) ?: HttpStatusCode.InternalServerError)
}
The key is that TestApplicationEngine
will throw, which makes it impossible to verify custom service error handling via a test client.
One possible suggestion to resolve: make exception status code handling extensible.
Here's an example of that will break tests:
fun Application.configureStatusPages() {
install(StatusPages) {
exception<Throwable> { call, cause ->
when (cause) {
is UnauthorizedException -> call.respond(status = HttpStatusCode.Unauthorized, message = cause.localizedMessage)
is ForbiddenException -> call.respond(status = HttpStatusCode.Forbidden, message = cause.localizedMessage)
else -> run {
call.respond(status = HttpStatusCode.InternalServerError, message = cause.localizedMessage)
}
}
throw cause // This is the key; will cause re-entry and re-throw in TestApplicationEngine
}
}
}
testApplication {
application {
configureStatusPages()
this.routing {
route("/boom") {
throw UnauthorizedException()
}
}
}
val client = createClient {
expectSuccess = false
}
val response = client.get("/boom")
// service will blow up before we get here
assertEquals(HttpStatusCode.Unauthorized, response.status)
}
Works fine via the default engine, but against TestApplicationEngine
, this will cause re-entry to handleTestFailure
, which will bomb.
2.1.1
released 6th September 2022
Build System Plugin
Cannot configure custom port mapping for the runDocker task
Also, if I have a server port different from 8080 then the server isn't reachable because the plugin always maps 8080 to 8080.
The related discussion https://kotlinlang.slack.com/archives/C0A974TJ9/p1661705281529209
"Could not initialize class io.ktor.plugin.features.FatJarKt" error when ShadowJar is also installed
To reproduce install the Ktor build plugin and ShadowJar plugin and run ./gradlew buildFatJar
:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.7.10"
application
id("io.ktor.plugin") version "2.1.0-eap-26"
id("com.github.johnrengelman.shadow") version "7.1.2"
}
group = "me.user"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClass.set("MainKt")
}
As a result, the following error occurs:
FAILURE: Build failed with an exception.
* What went wrong:
Could not initialize class io.ktor.plugin.features.FatJarKt
Unable to build Ktor project with JDK 1.8
As far as I can understand the error I am facing, Gradle complains about Java version mismatch, as if Ktor is only compatible with Java 11, but I don't see Ktor dropping Java 8 support. I am providing you with the stacktrace and the build script.
Stacktrace
A problem occurred configuring root project 'wiki'.
> Could not resolve all files for configuration ':classpath'.
> Could not resolve io.ktor.plugin:plugin:2.1.0.
Required by:
project : > io.ktor.plugin:io.ktor.plugin.gradle.plugin:2.1.0
> No matching variant of io.ktor.plugin:plugin:2.1.0 was found. The consumer was configured to find a runtime of a library compatible with Java 8, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.4' but:
- Variant 'apiElements' capability io.ktor.plugin:plugin:2.1.0 declares a library, packaged as a jar, and its dependencies declared externally:
- Incompatible because this component declares an API of a component compatible with Java 11 and the consumer needed a runtime of a component compatible with Java 8
- Other compatible attribute:
- Doesn't say anything about org.gradle.plugin.api-version (required '7.4')
- Variant 'javadocElements' capability io.ktor.plugin:plugin:2.1.0 declares a runtime of a component, and its dependencies declared externally:
- Incompatible because this component declares documentation and the consumer needed a library
- Other compatible attributes:
- Doesn't say anything about its target Java version (required compatibility with Java 8)
- Doesn't say anything about its elements (required them packaged as a jar)
- Doesn't say anything about org.gradle.plugin.api-version (required '7.4')
- Variant 'runtimeElements' capability io.ktor.plugin:plugin:2.1.0 declares a runtime of a library, packaged as a jar, and its dependencies declared externally:
- Incompatible because this component declares a component compatible with Java 11 and the consumer needed a component compatible with Java 8
- Other compatible attribute:
- Doesn't say anything about org.gradle.plugin.api-version (required '7.4')
- Variant 'sourcesElements' capability io.ktor.plugin:plugin:2.1.0 declares a runtime of a component, and its dependencies declared externally:
- Incompatible because this component declares documentation and the consumer needed a library
- Other compatible attributes:
- Doesn't say anything about its target Java version (required compatibility with Java 8)
- Doesn't say anything about its elements (required them packaged as a jar)
- Doesn't say anything about org.gradle.plugin.api-version (required '7.4')
build.gradle.kts
val ktor_version: String by project
val kotlin_version: String by project
plugins {
application
kotlin("jvm") version "1.7.10"
id("io.ktor.plugin") version "2.1.0"
}
group = "suplub.io"
version = "0.0.1"
application {
mainClass.set("suplub.io.ApplicationKt")
}
repositories {
mavenCentral()
}
dependencies {
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
implementation("io.ktor:ktor-server-host-common-jvm:$ktor_version")
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
}
IU-222.3739.54, JRE 17.0.3+7-b469.37x64 JetBrains s.r.o., OS Mac OS X(aarch64) v12.4, screens 3456.0x2234.0; Retina
Client
Darwin: Symbol not found: _OBJC_CLASS_$_NSURLSessionWebSocketMessage on iOS 12
I use ktor in multiplatform project.
After 2.0.0 release, app crashes with IOS 12. Can i have a way to fix it?
dyld: Symbol not found: _OBJC_CLASS_$_NSURLSessionWebSocketMessage
Referenced from: /Users/my name/Library/Developer/CoreSimulator/Devices/0B611991-66BF-4BE7-9534-628D49250D1E/data/Containers/Bundle/Application/E6ED3D75-FBF2-427E-8B82-F6E9E0B09D33/App.app/Frameworks/shared.framework/shared
Expected in: /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation
in /Users/my user/Library/Developer/CoreSimulator/Devices/0B611991-66BF-4BE7-9534-628D49250D1E/data/Containers/Bundle/Application/E6ED3D75-FBF2-427E-8B82-F6E9E0B09D33/App.app/Frameworks/shared.framework/shared
dyld: launch, loading dependent libraries
DYLD_FRAMEWORK_PATH=/Users/denis.alexandrov/Library/Developer/Xcode/DerivedData/App-dljtddjulmigkpgpribvzgwhjcds/Build/Products/Debug-iphonesimulator
DYLD_FALLBACK_LIBRARY_PATH=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib
DYLD_ROOT_PATH=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot
DYLD_FALLBACK_FRAMEWORK_PATH=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks
DYLD_INSERT_LIBRARIES=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libBacktraceRecording.dylib:/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libMainThreadChecker.dylib:/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 12.4.simruntime/Contents/Resources/RuntimeRoot/Developer/Library
(lldb)
CIO: Connection reset by peer on MacOS
When try create 1000 concurrent request by ** CIO** ktor client had exception Connection reset by peer
on macOS(On Ubuntu 20.04 nor reproduced).
When use Apache ktor client not reproduced spend time 1.69s
When use OkHttp ktor client not reproduced but spend time 201s (Maybe here something wrong too)
Steps to reproduce issue:
- Run Netty server by
src/main/kotlin/NettyServer.kt
- Run
src/main/kotlin/CioClientApplication.kt
System info:
- JDK 11 (adopt-openjdk-11.0.10)
- macOS BigSur 11.2 (20D64)
- Intel Core i7
Missing Content-Type header in a request
The following test currently fails
private val client = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
})
}
}
@Test
fun put(): Unit = runBlocking {
val response = client.request<HttpResponse> {
url("https://sellmair.io")
method = HttpMethod.Put
contentType(ContentType.Application.Json)
body = SamplePayload(0)
}
assertTrue(response.request.headers.toMap().keys.contains("Content-Type"))
}
Sample project:
HttpCookies: no space between cookie pairs
There is no space between cookies sent by http-client. Normally, it should be a semicolon and a space to separate each cookie, which caused me to authenticate abnormally when requesting certain websites.
see this doc https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie
my ktor version is 1.5.1
WebSocketDeflateExtension not following RFC
Here is two mistakes. First, parameters in WebSocketDeflateExtension and WebSocketDeflateExtension.Config has inverted flags, so you should assign them also inverted.
Second, according to the WebSocket Extension Parameters RFC https://datatracker.ietf.org/doc/html/rfc7692#section-7.1, the client must always respect handshake response. The client MAY offer some parameters(e.g. server_no_context_takeover or client_no_context_takeover) and server MAY accept the offer. If the server accept the offer it respond that parameters back to the client. Also, server MAY respond the extension parameters without client offer and client MUST accept them or close connection. In two words: client MUST always respect server's response.
{width=70%}
{width=70%}
{width=70%}
That lead to the problem: https://youtrack.jetbrains.com/issue/KTOR-3005/Exception-when-using-WebSocketDeflateExtension(due to the inverted flags. Server send messages with context takeover, but ktor client reset context every time and will get java.util.zip.DataFormatException: invalid distance too far back). You can find reproduce here https://github.com/scrat98/ktor-2.0.3-ws-deflate-extension-problems
Workaround: thanks to another bug https://youtrack.jetbrains.com/issue/KTOR-3189/parseWebSocketExtensions-not-correct. We can configure flags like this
clientNoContextTakeOver = true
serverNoContextTakeOver = true
In that case wrong inverted logic become valid, but thanks to the bug mentioned above and that parameters cannot be parsed by server(because WebSecExtHeader has wrong format) and server will accept extension "permessage-deflate" without parameters.
{width=70%}
I would love if someone could look closer to the whole WebSocketExtension, not only to the mentioned bugs and cover that by tests. My example very simple, and I think very common. It's very frustrating that such basic use case doesn't work :(
The `OutgoingContent.toByteArray()` stalls when used in combination with a `OutgoingContent.WriteChannelContent`
Within the ktor-client-mock
module the OutgoingContent.toByteArray()
within the MockUtils.kt
file is broken when using it on a OutgoingContent.WriteChannelContent
. The issue is that the method stalls because the ByteChannel
, which is used to temporarily store the bytes in, isn't closed before calling the toByteArray
method.
I solved this issue myself by reimplementing the method as follows:
private suspend fun OutgoingContent.toByteArray() : ByteArray {
return when (this) {
is OutgoingContent.NoContent -> ByteArray(0)
is OutgoingContent.ByteArrayContent -> bytes()
is OutgoingContent.ReadChannelContent -> readFrom().toByteArray()
is OutgoingContent.WriteChannelContent -> {
val channel = ByteChannel()
try {
writeTo(channel)
channel.close()
channel.toByteArray()
} catch (e: Exception) {
channel.close(e)
throw e
}
}
else -> ByteArray(0)
}
}
An error occurs when there is a binary such as protobuf in the response body of error
This can be confirmed by adding a test to CallValidatorTest.
@Test
fun testResponseValidationThrowsResponseExceptionWithByteArray() = testWithEngine(MockEngine) {
val content = byteArrayOf(0x08.toByte(), 0x96.toByte(), 0x01.toByte())
config {
expectSuccess = true
engine {
addHandler {
val status = HttpStatusCode(900, "Awesome code")
respond(content, status)
}
}
}
test { client ->
try {
client.get<HttpResponse>()
fail("Should fail")
} catch (cause: ResponseException) {
assertEquals(900, cause.response.status.value)
assertEquals(content.contentHashCode(), cause.response.receive<ByteArray>().contentHashCode())
}
}
}
Input length = 1
io.ktor.utils.io.charsets.MalformedInputException: Input length = 1
at io.ktor.utils.io.charsets.CharsetJVMKt.throwExceptionWrapped(CharsetJVM.kt:323)
at io.ktor.utils.io.charsets.CharsetJVMKt.decode(CharsetJVM.kt:199)
at io.ktor.utils.io.charsets.EncodingKt.decode(Encoding.kt:103)
at io.ktor.utils.io.charsets.EncodingKt.decode$default(Encoding.kt:101)
at io.ktor.client.statement.HttpStatementKt.readText(HttpStatement.kt:173)
at io.ktor.client.statement.HttpStatementKt.readText$default(HttpStatement.kt:168)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invokeSuspend(DefaultResponseValidation.kt:36)
at io.ktor.client.features.DefaultResponseValidationKt$addDefaultResponseValidation$1$1.invoke(DefaultResponseValidation.kt)
at io.ktor.client.features.HttpCallValidator.validateResponse(HttpCallValidator.kt:54)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invokeSuspend(HttpCallValidator.kt:129)
at io.ktor.client.features.HttpCallValidator$Companion$install$3.invoke(HttpCallValidator.kt)
at io.ktor.client.features.HttpSend$Feature$install$1.invokeSuspend(HttpSend.kt:99)
at io.ktor.client.features.HttpSend$Feature$install$1.invoke(HttpSend.kt)
at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
at io.ktor.util.pipeline.DebugPipelineContext.proceedWith(DebugPipelineContext.kt:42)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:106)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invoke(HttpCallValidator.kt)
at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invokeSuspend(HttpRequestLifecycle.kt:37)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invoke(HttpRequestLifecycle.kt)
at io.ktor.util.pipeline.DebugPipelineContext.proceedLoop(DebugPipelineContext.kt:79)
at io.ktor.util.pipeline.DebugPipelineContext.proceed(DebugPipelineContext.kt:57)
at io.ktor.util.pipeline.DebugPipelineContext.execute(DebugPipelineContext.kt:63)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:79)
at io.ktor.client.HttpClient.execute(HttpClient.kt:187)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at io.ktor.client.statement.HttpStatement.execute(HttpStatement.kt:43)
at io.ktor.client.statement.HttpStatement.execute(HttpStatement.kt:58)
at io.ktor.client.tests.CallValidatorTest$testResponseValidationThrowsResponseExceptionWithByteArray$1$2.invokeSuspend(CallValidatorTest.kt:364)
at io.ktor.client.tests.CallValidatorTest$testResponseValidationThrowsResponseExceptionWithByteArray$1$2.invoke(CallValidatorTest.kt)
at io.ktor.client.tests.utils.CommonClientTestUtilsKt$testWithEngine$1$2.invokeSuspend(CommonClientTestUtils.kt:82)
at io.ktor.client.tests.utils.CommonClientTestUtilsKt$testWithEngine$1$2.invoke(CommonClientTestUtils.kt)
at io.ktor.client.tests.utils.CommonClientTestUtilsKt$concurrency$2$invokeSuspend$$inlined$List$lambda$1.invokeSuspend(CommonClientTestUtils.kt:102)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:86)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:61)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at io.ktor.test.dispatcher.TestJvmKt.testSuspend(TestJvm.kt:16)
at io.ktor.test.dispatcher.TestJvmKt.testSuspend$default(TestJvm.kt)
at io.ktor.client.tests.utils.CommonClientTestUtilsKt.testWithEngine(CommonClientTestUtils.kt:66)
at io.ktor.client.tests.utils.CommonClientTestUtilsKt.testWithEngine$default(CommonClientTestUtils.kt:64)
at io.ktor.client.tests.CallValidatorTest.testResponseValidationThrowsResponseExceptionWithByteArray(CallValidatorTest.kt:322)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:119)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
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:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
at java.base/java.lang.Thread.run(Thread.java:834)
Core
Netty HTTP/2 not working
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1261
The problem reproduces with http2-push from https://github.com/ktorio/ktor-samples
Run ./gradlew :http2-push:run
const val kotlin = "1.3.41"
const val ktor = "1.2.3"
give a exception
Exception in thread "main" java.lang.NoClassDefFoundError: io.netty.handler.ssl.OpenSsl (initialization failure)
at java.base/java.lang.J9VMInternals.initializationAlreadyFailed(J9VMInternals.java:143)
at io.netty.handler.ssl.SslContext.defaultProvider(SslContext.java:120)
at io.netty.handler.ssl.SslContext.defaultServerProvider(SslContext.java:107)
at io.netty.handler.ssl.SslContext.newServerContextInternal(SslContext.java:454)
at io.netty.handler.ssl.SslContextBuilder.build(SslContextBuilder.java:457)
at io.ktor.server.netty.NettyChannelInitializer.<init>(NettyChannelInitializer.kt:72)
at io.ktor.server.netty.NettyApplicationEngine.<init>(NettyApplicationEngine.kt:103)
at io.ktor.server.netty.Netty.create(Embedded.kt:14)
at io.ktor.server.netty.Netty.create(Embedded.kt:12)
at io.ktor.server.engine.EmbeddedServerKt.embeddedServer(EmbeddedServer.kt:79)
at io.ktor.server.engine.EmbeddedServerKt.embeddedServer$default(EmbeddedServer.kt:77)
at io.ktor.samples.http2push.MainKt.main(Main.kt:17)
Caused by: java.lang.NoSuchMethodError: io/netty/internal/tcnative/SSLContext.setCipherSuite(JLjava/lang/String;Z)Z (loaded from file:/Users/icode/.gradle/caches/modules-2/files-2.1/io.netty/netty-tcnative/2.0.7.Final/b9413ea71da2d14e6f5644aa33a5c4d966090540/netty-tcnative-2.0.7.Final.jar by jdk.internal.loader.ClassLoaders$AppClassLoader@760c26b8) called from class io.netty.handler.ssl.OpenSsl (loaded from file:/Users/icode/.gradle/caches/modules-2/files-2.1/io.netty/netty-handler/4.1.37.Final/a242f98517b4dc1a53a0eb7dad8ad30d4f4b9f2e/netty-handler-4.1.37.Final.jar by jdk.internal.loader.ClassLoaders$AppClassLoader@760c26b8).
at io.netty.handler.ssl.OpenSsl.<clinit>(OpenSsl.java:201)
at io.ktor.server.netty.NettyChannelInitializer$Companion.findAlpnProvider(NettyChannelInitializer.kt:158)
at io.ktor.server.netty.NettyChannelInitializer$Companion.access$findAlpnProvider(NettyChannelInitializer.kt:147)
at io.ktor.server.netty.NettyChannelInitializer$Companion$alpnProvider$2.invoke(NettyChannelInitializer.kt:148)
at io.ktor.server.netty.NettyChannelInitializer$Companion$alpnProvider$2.invoke(NettyChannelInitializer.kt:147)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at io.ktor.server.netty.NettyChannelInitializer$Companion.getAlpnProvider$ktor_server_netty(NettyChannelInitializer.kt)
at io.ktor.server.netty.NettyChannelInitializer.<init>(NettyChannelInitializer.kt:58)
... 6 more
I update tcnative_version
to 2.0.25.Final
, then start is ok
I get this exception when request
Navigate to https://localhost:8443/
2019-08-02 20:23:13.309 [nioEventLoopGroup-3-2] WARN i.n.h.s.ApplicationProtocolNegotiationHandler - [id: 0x00f04f96, L:/0:0:0:0:0:0:0:1:443 - R:/0:0:0:0:0:0:0:1:53289] TLS handshake failed:
javax.net.ssl.SSLHandshakeException: error:10000416:SSL routines:OPENSSL_internal:SSLV3_ALERT_CERTIFICATE_UNKNOWN
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.shutdownWithError(ReferenceCountedOpenSslEngine.java:965)
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.sslReadErrorResult(ReferenceCountedOpenSslEngine.java:1231)
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.unwrap(ReferenceCountedOpenSslEngine.java:1185)
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.unwrap(ReferenceCountedOpenSslEngine.java:1256)
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.unwrap(ReferenceCountedOpenSslEngine.java:1299)
at io.netty.handler.ssl.SslHandler$SslEngineType$1.unwrap(SslHandler.java:204)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1329)
at io.netty.handler.ssl.SslHandler.decodeNonJdkCompatible(SslHandler.java:1236)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1273)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:500)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:439)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1408)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:697)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:632)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:549)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:511)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:918)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
2019-08-02 20:23:13.310 [nioEventLoopGroup-3-2] WARN i.n.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: error:10000416:SSL routines:OPENSSL_internal:SSLV3_ALERT_CERTIFICATE_UNKNOWN
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:470)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1408)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:697)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:632)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:549)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:511)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:918)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
Caused by: javax.net.ssl.SSLHandshakeException: error:10000416:SSL routines:OPENSSL_internal:SSLV3_ALERT_CERTIFICATE_UNKNOWN
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.shutdownWithError(ReferenceCountedOpenSslEngine.java:965)
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.sslReadErrorResult(ReferenceCountedOpenSslEngine.java:1231)
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.unwrap(ReferenceCountedOpenSslEngine.java:1185)
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.unwrap(ReferenceCountedOpenSslEngine.java:1256)
at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.unwrap(ReferenceCountedOpenSslEngine.java:1299)
at io.netty.handler.ssl.SslHandler$SslEngineType$1.unwrap(SslHandler.java:204)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1329)
at io.netty.handler.ssl.SslHandler.decodeNonJdkCompatible(SslHandler.java:1236)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1273)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:500)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:439)
... 17 common frames omitted
The parseWebSocketExtensions function behaves incorrectly
According to spec (https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) list of extensions should be separated by comma, and parameters should be separated by semicolon.
See: (https://datatracker.ietf.org/doc/html/rfc7692#section-5.2, https://datatracker.ietf.org/doc/html/rfc6455#section-9.1)
Sec-WebSocket-Extensions = extension-list
extension-list = 1#extension
extension = extension-token *( ";" extension-param )
extension-token = registered-token
registered-token = token
extension-param = token [ "=" (token | quoted-string) ]
;When using the quoted-string syntax variant, the value
;after quoted-string unescaping MUST conform to the
;'token' ABNF.
Actual:
/**
* Parse `Sec-WebSocket-Accept` header.
*/
@ExperimentalWebSocketExtensionApi
public fun parseWebSocketExtensions(value: String): List<WebSocketExtensionHeader> = value
.split(";")
.map { it ->
val extension = it.split(",")
val name = extension.first().trim()
val parameters = extension.drop(1).map { it.trim() }
WebSocketExtensionHeader(name, parameters)
}
private fun parametersToString(): String =
if (parameters.isEmpty()) "" else ", ${parameters.joinToString(",")}"
Expected.
/**
* Parse `Sec-WebSocket-Extensions` header.
*/
@ExperimentalWebSocketExtensionApi
public fun parseWebSocketExtensions(value: String): List<WebSocketExtensionHeader> = value
.split(",")
.map { it ->
val extension = it.split(";")
val name = extension.first().trim()
val parameters = extension.drop(1).map { it.trim() }
WebSocketExtensionHeader(name, parameters)
}
private fun parametersToString(): String =
if (parameters.isEmpty()) "" else "; ${parameters.joinToString(";")}"
This mistake affect deflate extension (io.ktor.http.cio.websocket.WebSocketDeflateExtension#serverNegotiation).
val protocol = requestedProtocols.find { it.name == PERMESSAGE_DEFLATE } ?: return emptyList()
val parameters = mutableListOf<String>()
for ((key, value) in protocol.parseParameters()) {
protocol.parseParameters()
now always empty.
IllegalArgumentException is thrown when UnixSocketAddress.path is accessed on JVM (JDK 16+)
In Ktor 2.0.3, when doing:
val address = UnixSocketAddress("/path")
address.path // << fails with IllegalArgumentException
you get:
java.lang.IllegalArgumentException: object is not an instance of declaring class
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at io.ktor.network.sockets.UnixSocketAddress.getPath(SocketAddressJvm.kt:68)
...
This is a regression.
Crash when making requests from browser inside of web worker
window
is undefined inside of a web worker, and the check window !== undefined
in URLBuilder.Companion.origin
will crash because window
is not dynamic.
I have a pretty basic PR here: https://github.com/ktorio/ktor/pull/3116
Not sure if there is a better solution, perhaps by making the PlatformUtils check more granular?
Docs
Netty HTTP/2 not working
The http2-netty
cannot be built successfully for now unless modifying tcnative
version to 2.0.50.Final
or above.
After ran it through ./gradlew :http2-netty:run
, client cannot receive any data with GET then simply hang.
Server side log:
2022-07-26 17:08:24.473 [main] INFO Application - Autoreload is disabled because the development mode is off.
2022-07-26 17:08:25.795 [main] INFO Application - Application auto-reloaded in 1.321 seconds.
2022-07-26 17:08:30.264 [DefaultDispatcher-worker-1] INFO Application - Responding at http://0.0.0.0:8080
2022-07-26 17:08:30.264 [DefaultDispatcher-worker-1] INFO Application - Responding at https://0.0.0.0:8443
Client side log:
$ curl -v -k --http2 https://localhost:8443
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying ::1:8443...
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Connected to localhost (::1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: D:/Progams/Git/mingw64/ssl/certs/ca-bundle.crt
* CApath: none
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [19 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [1420 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [520 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: C=US; ST=Unspecified; L=Unspecified; O=ktor; OU=ktor; CN=localhost
* start date: Jul 7 11:18:25 2021 GMT
* expire date: Apr 9 11:18:25 2076 GMT
* issuer: C=US; ST=Unspecified; L=Unspecified; O=ktor; OU=ktor; CN=localhost
* SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
} [5 bytes data]
* Using Stream ID: 1 (easy handle 0x1e0920d3310)
} [5 bytes data]
> GET / HTTP/2
> Host: localhost:8443
> user-agent: curl/7.75.0
> accept: */*
>
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [214 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [214 bytes data]
* old SSL session ID is stale, removing
{ [5 bytes data]
* Connection state changed (MAX_CONCURRENT_STREAMS == 4294967295)!
} [5 bytes data]
0 0 0 0 0 0 0 0 --:--:-- 0:03:55 --:--:-- 0* OpenSSL SSL_read: Connection was reset, errno 10054
* Failed receiving HTTP2 data
} [5 bytes data]
* OpenSSL SSL_write: Connection was reset, errno 10054
* Failed sending HTTP2 data
0 0 0 0 0 0 0 0 --:--:-- 0:03:56 --:--:-- 0
I've tested it on Ubuntu 20.04 and Windows 10, the symptom is the same.
I've also tried HTTP/2 with Netty engine on my Android project as well. And the result is almost the same.
Android device log:
07-25 20:36:25.815 29917 30651 D i.n.h.s.SslHandler: [eventLoopGroupProxy-6-1] [id: 0x672ef200, L:/192.168.1.6:8081 - R:/192.168.1.8:54205] HANDSHAKEN: protocol:TLSv1.3 cipher suite:TLS_AES_256_GCM_SHA384
07-25 20:36:45.870 29917 30651 D i.n.c.DefaultChannelPipeline: [eventLoopGroupProxy-6-1] Discarded inbound message DefaultHttp2SettingsFrame(settings={ENABLE_PUSH=0, MAX_CONCURRENT_STREAMS=100, INITIAL_WINDOW_SIZE=33554432}) that reached at the tail of the pipeline. Please check your pipeline configuration.
07-25 20:36:45.883 29917 30651 D i.n.c.DefaultChannelPipeline: [eventLoopGroupProxy-6-1] Discarded message pipeline : [ssl, Http2MultiplexCodec#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x672ef200, L:/192.168.1.6:8081 - R:/192.168.1.8:54205].
07-25 20:37:23.489 29917 30651 D i.n.c.DefaultChannelPipeline: [eventLoopGroupProxy-6-1] Discarded inbound message DefaultHttp2SettingsAckFrame that reached at the tail of the pipeline. Please check your pipeline configuration.
07-25 20:37:23.506 29917 30651 D i.n.c.DefaultChannelPipeline: [eventLoopGroupProxy-6-1] Discarded message pipeline : [ssl, Http2MultiplexCodec#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x672ef200, L:/192.168.1.6:8081 - R:/192.168.1.8:54205].
Client side log:
$ curl -v -k --http2 https://192.168.1.6:8081
* Trying 192.168.1.6:8081...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Connected to 192.168.1.6 (192.168.1.6) port 8081 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: D:/Progams/Git/mingw64/ssl/certs/ca-bundle.crt
* CApath: none
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [15 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [1374 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [520 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject:
* start date:
* expire date:
* issuer:
* SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
} [5 bytes data]
* Using Stream ID: 1 (easy handle 0x22c96cc23a0)
} [5 bytes data]
> GET / HTTP/2
> Host: 192.168.1.6:8081
> user-agent: curl/7.75.0
> accept: */*
>
{ [5 bytes data]
* Connection state changed (MAX_CONCURRENT_STREAMS == 4294967295)!
} [5 bytes data]
0 0 0 0 0 0 0 0 --:--:-- 0:00:09 --:--:-- 0
Is HTTP/2 with Netty not supported by Ktor after 2.0.0?
OkHttp: Websockets pinging doesn't work
Scenario 1
val okHttpEngine = OkHttp.create()
val client = HttpClient(okHttpEngine) {
install(WebSockets) {
pingInterval = 20_000
}
}
client.webSocket("ws://localhost:8081") {
send(Frame.Text("Hello"))
incoming.consumeAsFlow().collect {
println("Frame Received: $it")
}
}
It's expected that pinging will work. However, it doesn't. It doesn't ping and can lead to silent failures.
Scenario 2
val okHttpEngine = OkHttp.create()
val client = HttpClient(okHttpEngine) {
install(WebSockets)
}
client.webSocket("ws://localhost:8081") {
pingIntervalMillis = 20_000 // WebSocketException("OkHttp doesn't support dynamic ping interval. You could switch it in the engine configuration." )
}
An exception is thrown.
It's understandable that it is an OkHttp limitation, and It's easy to see in the source code why this happens in Ktor.
However, I think both scenarios have similar set-ups and should have to have similar outcomes. At least a runtime warning when configuring the WebSocket in Scenario 1. But instead it can fail silently without any kind of feedback.
Clarify how to run an app packaged using the Gradle Application plugin
Server
Replace exception in InputStreamAdapter and OutputStreamAdapter constructors with warning message If parking
To reproduce make a POST /
request to the server with the following code:
embeddedServer(Netty, port = 3333) {
routing {
post("/") {
call.receiveStream()
}
}
}.start(wait = true)
As a result, the following exception is thrown:
java.lang.IllegalStateException: Acquiring blocking primitives on this dispatcher is not allowed. Consider using async channel or doing withContext(Dispatchers.IO) { call.receive<InputStream>().use { ... } } instead.
at io.ktor.server.engine.DefaultTransformJvmKt.checkSafeParking(DefaultTransformJvm.kt:57)
at io.ktor.server.engine.DefaultTransformJvmKt.receiveGuardedInputStream(DefaultTransformJvm.kt:52)
at io.ktor.server.engine.DefaultTransformJvmKt.defaultPlatformTransformations(DefaultTransformJvm.kt:28)
at io.ktor.server.engine.DefaultTransformKt$installDefaultTransformations$2.invokeSuspend(DefaultTransform.kt:63)
at io.ktor.server.engine.DefaultTransformKt$installDefaultTransformations$2.invoke(DefaultTransform.kt)
at io.ktor.server.engine.DefaultTransformKt$installDefaultTransformations$2.invoke(DefaultTransform.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:101)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.server.request.ApplicationReceiveFunctionsKt.receive(ApplicationReceiveFunctions.kt:88)
at ServerKt$main$1$1$1.invokeSuspend(server.kt:22)
at ServerKt$main$1$1$1.invoke(server.kt)
at ServerKt$main$1$1$1.invoke(server.kt)
at io.ktor.server.routing.Route$buildPipeline$1$1.invokeSuspend(Route.kt:116)
at io.ktor.server.routing.Route$buildPipeline$1$1.invoke(Route.kt)
at io.ktor.server.routing.Route$buildPipeline$1$1.invoke(Route.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:101)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.server.routing.Routing$executeResult$$inlined$execute$1.invokeSuspend(Pipeline.kt:478)
at io.ktor.server.routing.Routing$executeResult$$inlined$execute$1.invoke(Pipeline.kt)
at io.ktor.server.routing.Routing$executeResult$$inlined$execute$1.invoke(Pipeline.kt)
at io.ktor.util.debug.ContextUtilsKt.initContextInDebugMode(ContextUtils.kt:17)
at io.ktor.server.routing.Routing.executeResult(Routing.kt:174)
at io.ktor.server.routing.Routing.interceptor(Routing.kt:49)
at io.ktor.server.routing.Routing$Plugin$install$1.invokeSuspend(Routing.kt:124)
at io.ktor.server.routing.Routing$Plugin$install$1.invoke(Routing.kt)
at io.ktor.server.routing.Routing$Plugin$install$1.invoke(Routing.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
at io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1.invokeSuspend(BaseApplicationEngine.kt:122)
at io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1.invoke(BaseApplicationEngine.kt)
at io.ktor.server.engine.BaseApplicationEngineKt$installDefaultTransformationChecker$1.invoke(BaseApplicationEngine.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:101)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1$invokeSuspend$$inlined$execute$1.invokeSuspend(Pipeline.kt:478)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt)
at io.ktor.util.debug.ContextUtilsKt.initContextInDebugMode(ContextUtils.kt:17)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invokeSuspend(DefaultEnginePipeline.kt:118)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invoke(DefaultEnginePipeline.kt)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invoke(DefaultEnginePipeline.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:101)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1$invokeSuspend$$inlined$execute$1.invokeSuspend(Pipeline.kt:478)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1$invokeSuspend$$inlined$execute$1.invoke(Pipeline.kt)
at io.ktor.util.debug.ContextUtilsKt.initContextInDebugMode(ContextUtils.kt:17)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:119)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt)
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:126)
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:37)
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.runTask(AbstractEventExecutor.java:174)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:503)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:995)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.ktor.server.netty.EventLoopGroupProxy$Companion.create$lambda-1$lambda-0(NettyApplicationEngine.kt:260)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:829)
HTTP/2 push fails with Netty engine
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/350
The problem reproduces with http2-push
from https://github.com/ktorio/ktor-samples
- Run
./gradlew :http2-push:run
- Navigate to https://localhost:8443/
Resulting exception:
2018-03-07 14:47:07.089 [nettyWorkerPool-3-2] WARN i.n.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
java.lang.IllegalArgumentException: Stream no longer exists: 3
at io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.requireStream(DefaultHttp2ConnectionEncoder.java:343)
at io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.writePushPromise(DefaultHttp2ConnectionEncoder.java:279)
at io.ktor.server.netty.http2.NettyHttp2Handler.startHttp2PushPromise$ktor_server_netty(NettyHttp2Handler.kt:104)
at io.ktor.server.netty.http2.NettyHttp2ApplicationResponse$push$1.run(NettyHttp2ApplicationResponse.kt:49)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:886)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
2018-03-07 14:47:07.093 [nettyWorkerPool-3-2] WARN i.n.util.concurrent.DefaultPromise - An exception was thrown by io.ktor.server.netty.http2.NettyHttp2Handler$startHttp2PushPromise$1.operationComplete()
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Stream no longer exists: 3
at io.netty.util.concurrent.AbstractFuture.get(AbstractFuture.java:41)
at io.ktor.server.netty.http2.NettyHttp2Handler$startHttp2PushPromise$1.operationComplete(NettyHttp2Handler.kt:109)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:507)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:481)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:420)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:163)
at io.netty.channel.DefaultChannelPromise.addListener(DefaultChannelPromise.java:93)
at io.ktor.server.netty.http2.NettyHttp2Handler.startHttp2PushPromise$ktor_server_netty(NettyHttp2Handler.kt:108)
at io.ktor.server.netty.http2.NettyHttp2ApplicationResponse$push$1.run(NettyHttp2ApplicationResponse.kt:49)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:886)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalArgumentException: Stream no longer exists: 3
at io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.requireStream(DefaultHttp2ConnectionEncoder.java:343)
at io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.writePushPromise(DefaultHttp2ConnectionEncoder.java:279)
at io.ktor.server.netty.http2.NettyHttp2Handler.startHttp2PushPromise$ktor_server_netty(NettyHttp2Handler.kt:104)
... 7 common frames omitted
Json request failure with configured form authentication
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/926
Ktor Version
1.1.2
Ktor Engine Used(client or server and name)
Netty server
JVM Version, Operating System and Relevant Context
Java 1.8, Linux
Feedback
I have the following configuration:
fun Application.main() {
install(ContentNegotiation) {
jackson {
}
}
install(Authentication) {
form {
challenge = FormAuthChallenge.Unauthorized
validate { credentials ->
if (credentials.name == credentials.password) {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
}
routing {
authenticate {
post("/test") {
call.respondText("OK")
}
}
}
}
I should get "401 Unauthorized" response when making a simple POST request on "/test" endpoint. But when the content type of the request is "application/json", generated for instance with the following curl command:
curl -v -d '{}' -H "Content-Type: application/json" -X POST http://localhost:8080/test
ktor server fails with "500 Internal Server Error" and the exception:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of
`io.ktor.http.Parameters` (no Creators, like default construct, exist): abstract types either need to be
mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (InputStreamReader); line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1452)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1028)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3049)
at io.ktor.jackson.JacksonConverter.convertForReceive(JacksonConverter.kt:40)
at io.ktor.features.ContentNegotiation$Feature$install$3.invokeSuspend(ContentNegotiation.kt:104)
at io.ktor.features.ContentNegotiation$Feature$install$3.invoke(ContentNegotiation.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
at io.ktor.request.ApplicationReceiveFunctionsKt.receive(ApplicationReceiveFunctions.kt:69)
at io.ktor.request.ApplicationReceiveFunctionsKt.receiveOrNull(ApplicationReceiveFunctions.kt:85)
at io.ktor.auth.FormAuthKt$form$1.invokeSuspend(FormAuth.kt:93)
at io.ktor.auth.FormAuthKt$form$1.invoke(FormAuth.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
at io.ktor.auth.Authentication.processAuthentication(Authentication.kt:217)
at io.ktor.auth.Authentication$interceptPipeline$2.invokeSuspend(Authentication.kt:112)
at io.ktor.auth.Authentication$interceptPipeline$2.invoke(Authentication.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
at io.ktor.routing.Routing.executeResult(Routing.kt:148)
at io.ktor.routing.Routing.interceptor(Routing.kt:29)
at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:93)
at io.ktor.routing.Routing$Feature$install$1.invoke(Routing.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:63)
at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:106)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:31)
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:160)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:54)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:22)
at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:16)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:38)
at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:353)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
CIO: responses are received with a huge delay on JVM Windows (due to reverse DNS lookup internally)
Hi Team,
After migrating from Ktor V 1.6.8 to V 2.x.x, I observe a huge delay when making API call to Windows (server) only when accessing these APIs from another computer within my subnet, especially when I stream media content through
OutgoingContent.ReadChannelContent()
I believe this is a regression issue as it does not happening on any 1.6.X ktor version or when I access my APIs from the same computer (localhost/127.0.0.1).
After several hours of debugging across Ktor 1.6.8 and 2.0.2/2.1.0. I observe a weird behaviour in the CIOApplicationRequest.kt class.
I have attached screenshots of my findings with details below:
Ktor V 1.6.8
{width=70%}
Comments:
In CIOApplicationRequest.kt on v1.6.8, the localAddress is retrieve from a InetSocketAddress class which provide the correct local address of the machine: /192.168.1.4:5000
I assume this is the normal behaviour which execute with a fast/normal response speed. No issues here.
Ktor V 2.X.X
{width=70%}
Comments:
In CIOApplicationRequest.kt on v2.X.X, The localAddress is retrieve from a NetworkAddress.kt class which provide a weird address, in this use case: DESKTOP-1V7C8U4/192.168.152.1:5000
Is no longer pointing to the correct local IP(subnet address) but into another interface within my computer server. I believe this is the root cause of this huge delay as the JVM is trying to resolve DNS of the above address (DESKTOP-1V7C8U4....)
Please see the red arrows in my debugging view demonstrating the issue.
Please see the list of interfaces within my computer:
***Network connection configuration ***
{width=70%}
Comments:
I am actually using the ethernet interface Réseau 2, which is 192.168.1.4 (the correct address of my subnet router)
I again notice something interesting when disabling all other interfaces and leaving my Ethenet interface enable, calling APIs from another computer within my subnet are much quicker this time. I assume the system resolve faster the domain as there is only one interface. It is faster but not as fast compared to 1.6.X.
So this is definitely something to do with interfaces and resolving localAddress, which is a regression issue.
I could not reproduce this issue on MacOS devices using ktor v2.X.X.
Conclusion:
Ktor v1.6.8 -> v.2.x.x has an issue on Windows only devices for resolving DNS/ local address which causes Huge delay to compute responses
Kind regards
Netty ALPN provider detection not working
io.ktor.server.netty.NettyChannelInitializer.Companion#findAlpnProvider
is looking for a class named "sun.security.ssl.ALPNExtension", but the class contained in the jdk is called "sun.security.ssl.AlpnExtension", so the check always fails (unless you happen to have jetty-alpn on your boot classpath). Also io.netty.handler.ssl.SslProvider.isAlpnSupported(SslProvider.JDK)
reports that ALPN should be available.
Clearing Session Cookie in Chrome 80+ with SameSite and Secure
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1738
Ktor Server 1.3.2
I'm currently experience what is explained in this article. TLDR: Chrome 80+ won't delete SameSite
cookies unless the Secure
and SameSite
flags are both set. The current call.sessions.clear<MySession>()
does not set the Secure
flag or pass extensions through and thus a session will never be cleared for Chrome browsers.
To reproduce:
Setup an app with a Sessions
config that looks similar to the following, ensuring to set secure
and the SameSite
values:
install(Sessions) {
cookie<MySessionObject>("SESSION") {
cookie.path = "/"
cookie.secure = true
cookie.domain = "acme.test"
cookie.extensions["SameSite"] = "None"
cookie.maxAgeInSeconds = 2592000
}
}
Setup endpoints to set and clear the session.
Access the endpoint to create the session over HTTPS
Access the endpoint to clear the session over HTTPS
The endpoint to clear the session will return the following Header:
set-cookie: SESSION=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Domain=acme.test; Path=/; $x-enc=URI_ENCODING
As you can see the Secure
flag is not set. And you'll have to take my word for it, but Chrome, as explained in the article above, does not delete the cookie.
Expected behavior
Ideally, Ktor will pass the Secure
flag along when clearing the cookie through the call.sessions.clear
API.
CallLogging configured MDC entries are not passed to StatusPages exception handlers
Any MDC entries configured for the CallLogging plugin are not provided to the StatusPages exception handlers context. Assuming it's possible to do so, I think it's reasonable to expect that MDC entries be passed to this phase of the pipeline given the diagnostic nature of logs that are likely to be produced in exception handlers.
In the following example you can see that the MDC entry "hello=world" is missing from line 6 of the logs. Ideally it is present just as it is on line 5 of the logs.
package com.example
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.callid.*
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.slf4j.event.Level
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(CallLogging) {
level = Level.INFO
filter { call -> call.request.path().startsWith("/") }
callIdMdc("call-id")
mdc("hello") { "world" }
}
install(CallId) {
header(HttpHeaders.XRequestId)
verify { callId: String ->
callId.isNotEmpty()
}
}
install(StatusPages) {
exception<Throwable> { call, cause ->
call.application.log.error("Unhandled exception", cause)
call.respond(HttpStatusCode.InternalServerError)
}
}
routing {
get("/") {
call.respondText("Hello World!")
}
get("/error") {
call.application.log.error("Error")
throw RuntimeException("Example error")
}
}
}.start(wait = true)
}
Log output:
2022-04-20 10:12:04.021 [main] INFO ktor.application {} Autoreload is disabled because the development mode is off.
2022-04-20 10:12:04.055 [main] INFO ktor.application {} Application auto-reloaded in 0.033 seconds.
2022-04-20 10:12:04.056 [main] INFO ktor.application {} Application started: io.ktor.server.application.Application@7fd7a283
2022-04-20 10:12:04.149 [DefaultDispatcher-worker-1] INFO ktor.application {} Responding at http://0.0.0.0:8080
2022-04-20 10:12:09.111 [eventLoopGroupProxy-4-1] ERROR ktor.application {hello=world} Error
2022-04-20 10:12:09.114 [eventLoopGroupProxy-4-1] ERROR ktor.application {} Unhandled exception
java.lang.RuntimeException: Example error
at com.example.ApplicationKt$main$1$4$2.invokeSuspend(Application.kt:43)
at com.example.ApplicationKt$main$1$4$2.invoke(Application.kt)
at com.example.ApplicationKt$main$1$4$2.invoke(Application.kt)
...
2022-04-20 10:12:09.230 [eventLoopGroupProxy-4-1] INFO ktor.application {hello=world} 500 Internal Server Error: GET - /error
And if it helps, here's the logback config
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %X{call-id} %-5level %logger{36} {%mdc} %msg%n</pattern>
</encoder>
</appender>
<root level="trace">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.eclipse.jetty" level="INFO"/>
<logger name="io.netty" level="INFO"/>
</configuration>
LocalFileContent incorrectly relies on the last modification time of a file to check its existence
// https://github.com/ktorio/ktor/blob/main/ktor-server/ktor-server-core/jvm/src/io/ktor/server/http/content/LocalFileContent.kt#L27
init {
val lastModifiedVersion = file.lastModified()
if (lastModifiedVersion == 0L) {
throw IOException("No such file ${file.absolutePath}")
} else {
versions += LastModifiedVersion(lastModifiedVersion)
}
}
There are cases where a file can exist, but file.lastModified()
is precisely equal to 0L
-- i.e. if the last modified time is set to Jan 1, 1970
, which can be the case (for example) for files in docker images.
Sessions: WSS in combination with Secure cookies throws IllegalArgumentException
I'm running a Ktor behind reverse proxy that does TLS termination -> so the user goes to https://example.com
to the reverse proxy (in this case Traefik, but does not matter) and the proxy forwards traffic to the http://localhost:8080
. Now, because Ktor has support for this, it correctly takes the protocol (https
) from the correct header X-Forwarded-Proto
and thus it is possible to use Secure
cookies. Moreover, Ktor checks if that is possible here in ResponseCookies.kt.
if (item.secure && !secureTransport) {
throw IllegalArgumentException("You should set secure cookie only via secure transport (HTTPS)")
}
response.headers.append("Set-Cookie", renderSetCookieHeader(item))
Because secureTransport
is computed like this in BaseApplicationResponseJvm just by checking https
it leads to cases when wss
is not considered secure and the IllegalArgumentException
is thrown in cases, when the proxy properly inserts X-Forwarded-Proto: wss
(or other header properly indicating that this is wss
and not https
).
ResponseCookies(this, call.request.origin.scheme == "https")
This is also connected with KTOR-3159 - to allow setting Secure
cookies in any context. And also with KTOR-912 - because set cookies is done after every request even the websocket upgrades.
Minimal example should be the following code that is running behind some reverse proxy doing https
and setting header X-Forwarded-Proto: wss
when asked for wss://example.com/ws
.
fun main() {
data class MyCookie(val name: String)
// server exposed at http://localhost:8080
// behind reverse proxy that does TLS https://example.com and forwards
// to this localhost http://localhost:8080
embeddedServer(CIO) {
install(WebSockets)
// we run behind proxy, so we set cookies to Secure
install(Sessions) {
cookie<MyCookie>("c") {
cookie.secure = true
}
}
// takes protocol from X-Forwarded-Protocol, X-Forwarded-Proto
install(XForwardedHeaders)
// client first calls GET / to obtain cookie
// and then calls GET /wss to be upgraded to wss, but that never succeeds
routing {
// this sets cookie
get("/") {
call.sessions.set(MyCookie("hello-world"))
}
// code inside websocket is never reached, because of
// io.ktor.server.engine.BaseApplicationResponse line 33 in JVM version
// because server attempts to set secure cookie in wss context
webSocket("wss") {
println("This is never reached!")
}
}
}
}
Workaround, we used, is to force proxy to specify X-Forwarded-Proto: https
even in cases when it should be wss
.
I can think of following fixes (and actually expected behaviors):
- disable automatic cookie append as suggested in KTOR-912 this would fix this because code checking secure context won't be triggered
- allow setting secure cookies even in insecure context as suggested in KTOR-3159
- fixing checking for the secure context to
ResponseCookies(this, call.request.origin.scheme == "https" || call.request.origin.scheme == "wss")
- see https://github.com/ktorio/ktor/pull/3114
Other
Deprecate receiveOrNull because it's confusing
Can be replaced with tryCatching { receive() }
CallLogging MDC with sessions: Application feature Sessions is not installed
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1368
Ktor Version and Engine Used (client or server and name)
1.2.4, server, [cio, serialization, auth, locations, client-cio]
Describe the bug
Just tried out the CallLogging MDC feature, but as soon as I try to get a value from sessions, my server becomes unresponsive. The call just takes forever and doesn't return. No log shows up.
Then I tried the CallId
feature with the same logic and this time it throws:
io.ktor.application.MissingApplicationFeatureException: Application feature Sessions is not installed
.
Once I switched to netty, it shows the same exception when using the CallLogging
feature.
To Reproduce
Steps to reproduce the behavior:
- Write the following
class OAuthSession(val userId: Int)
fun main() {
embeddedServer(CIO, 5000) {
install(CallLogging) {
mdc("test") {
it.sessions.get<OAuthSession>()?.userId?.toString()
}
}
install(Sessions) {
cookie<OAuthSession>("oauthSampleSessionId") {
val secretSignKey = hex("000102030405060708090a0b0c0d0e0f")
transform(SessionTransportTransformerMessageAuthentication(secretSignKey))
}
}
install(Routing) {
get {
call.respond("Ok")
}
}
}.start(true)
}
- Start the server and call it via
localhost:5000
. - In CIO, no error is shown. With netty, exception is thrown.
Expected behavior
The session should be retrieved and used for mdc / call id.
Deprecate Public API with Atomicfu Declarations
Server ContentNegotiation Plugin doesn't check ignoredTypes for Request Body
Receive non-Nullable Type Throws NPE in Case of Failure
Fix Merging Date Headers on the Client
2.1.0
released 11th August 2022
Build System Plugin
GraalVM agent enabling does not work
After upgrading graalvm plugin to 0.9.12
, agent enabling stopped working. The issue seem to be in the new version of the plugin: https://github.com/graalvm/native-build-tools/issues/254
Proposed solution is to downgrade to 0.9.11
and reopen KTOR-4477.
`publishImage` throws 401 while credentials are correct
Can't run Ktor Gradle Plugin tasks using IDEA Gradle menu
You can't select the ktor-fatjar-sample
project in IDEA's Gradle menu and run a task namedrf buildFatJar
from it.
Seems like we can solve this issue by removing settings.gradle.kts
from subprojects. But when we remove it, we will not have a rootProject.name
set, which will cause the name of a fat jar in ktor-fatjar-sample
will have unnamed
in it.
Native image settings are not taken into account when running `buildNativeImage`
Add runFatJar task
Client
ContentEncoding: body<ByteArray>() receives truncated array
To reproduce run the following test:
@Test
fun test() = runBlocking {
val body = "*".repeat(500).toByteArray()
val encodedBody = with(GZip) {
encode(ByteReadChannel(body))
}.readRemaining().readBytes()
val client = HttpClient(MockEngine) {
install(ContentEncoding) {
gzip()
}
engine {
addHandler {
respond(encodedBody, headers = Headers.build {
append(HttpHeaders.ContentEncoding, "gzip")
append(HttpHeaders.ContentLength, encodedBody.size.toString())
})
}
}
}
val response = client.get("/")
assertEquals(body.size, response.body<ByteArray>().size)
}
The above test fails because the HttpClient.defaultTransformers
method reads the response body up to Content-Length
size. To this point, the body is already decompressed, so the resulting array is truncated to compressed data size.
OkHttp WebSocket hangs when computer goes to sleep on Linux
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1958
Ktor Version and Engine Used (client or server and name)
JVM Ktor 1.3.1 (which is latest stable ktor-client-websockets
version as of writing this), OkHttp engine on both server and client.
Client is on Linux system (not sure if other operating systems behave differently).
To Reproduce
Steps to reproduce the behavior:
- Connect to the websocket using following client code:
try {
client.ws("wss://my-websocket.com",) {
for (frame in incoming) {
LOGGER.debug("Received $frame")
}
val closeReason = closeReason.await()
LOGGER.debug("Close $closeReason")
}
} catch (e: Exception) {
LOGGER.error("Websocket Error", e)
}
- Send messages periodically from the server (for example one every couple of seconds)
- Verify that messages are being received (above code prints out "Received" log on every message)
- Put the computer that hosts the client in sleep mode
- Wait in sleep mode for about 15 minutes
- Turn computer back on
Expected behavior
Socket should probably disconnect or at least give some sort of indication that connection has been dropped.
Actual behavior
Socket just hangs. It does not receive any new messages from the server and does not close or error out (no log outputs from the above code).
Java engine: Allow configuring HTTP version
Currently Java engine has enforced an HTTP 1.1 protocol version, and it does not work out of the box with some services, e.g. https://jitpack.io
In fact, Java HTTP Client establishes handshake and could downgrade to HTTP 1.1 if the service does not support it:
The Java HTTP Client supports both HTTP/1.1 and HTTP/2. By default the client will send requests using HTTP/2. Requests sent to servers that do not yet support HTTP/2 will automatically be downgraded to HTTP/1.1
HTTP 1.1 enforcement: https://github.com/ktorio/ktor/blob/main/ktor-client/ktor-client-java/jvm/src/io/ktor/client/engine/java/JavaHttpEngine.kt#L71
Darwin: Allow setting custom NSURLSession
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1248
Ktor version
1.2.2
Ktor Engine used
IosClientEngine
Motivation and proposed Solution
We need to be able to implement URLSession delegate methods (used by IosClientEngine) in our custom code to achieve required functionality. Eg overriding didReceiveChallenge to deal with certificates stuff. Ideal solution would be to be able to set custom URLSession object or be able to override all URLSession delegate methods on top of some IosClientEngine delegate.
OAuth2: Allow sending extra parameters for authorization and access token requests
I'm using KTor and can sign in with my app using Google's OAuth2. This gives me a valid access token and works for authenticating my user. I am then using this access token to make calls to other Google APIs, and this works great... until the accessToken expires. I've printed out the Principal object that comes back, and while there is an expires value, the refreshToken value is null.
It seems like there needs to be an additional HTTP parameter on the authentication request. For Google it wants access_type=offline (https://developers.google.com/identity/protocols/oauth2/web-server#httprest_3) while it looks like Auth0 wants scope=offline_access as a parameter (https://auth0.com/docs/tokens/refresh-tokens/get-refresh-tokens). Maybe a solution would be to support extra parameters to the URL?
Below is my configuration with parameters hidden.
name = "google",
authorizeUrl = authorizeUrl,
accessTokenUrl = tokenUrl,
requestMethod = HttpMethod.Post,
clientId = clientId,
clientSecret = clientSecret,
defaultScopes = listOf("profile", // no email, but gives full name, picture, and id
"email", // email
"https://www.googleapis.com/auth/calendar.readonly" // google calendar
,"https://www.googleapis.com/auth/plus.login"
)
)
Client resources plugin miss builders for PATCH method
HttpCache plugin does not support max-stale directive
Darwin: configureRequest doesn't actually configure a NSMutableURLRequest when HTTP request is made
To reproduce run the following code on native target:
fun main() {
val job = GlobalScope.launch {
val client = HttpClient(Darwin) {
engine {
configureRequest {
setValue("my header value", forHTTPHeaderField = "X-Custom-Header")
}
}
}
val r = client.get("https://httpbin.org/get").bodyAsText()
println(r)
}
while (job.isActive) { }
}
As result, the X-Custom-Header
header isn't sent when Ktor 2.0.2 or 2.0.3 is used. The above code works as expected with Ktor 2.0.1 and earlier versions.
[OkHttp] StreamRequestBody should override isOneShot or allow multiple reads of request body
When Ktor maps request bodies from OutgoingContent
to OkHttp's RequestBody
, it uses a wrapper StreamRequestBody
(GitHub). In conjunction with OkHttp Interceptors which may try to read the request body, such as HttpLoggingInterceptor
(GitHub) or Chucker (GitHub Issue), the underlying stream may be read fully & closed before final transmission. As I understand it, request bodies will use an OutgoingContent
when transmitting binary data or multipart requests
Recommended solution:
StreamRequestBody
should override isOneShot()
to return true
so that such interceptors do not attempt to read the content. Both the interceptors mentioned above honor this method
Alternative solution:
StreamRequestBody
could update its implementation of writeTo(BufferedSink)
so that it can be invoked multiple times & write the content each time without error. Future interceptors would then be able to behave normally & read the content but may waste system resources depending on the size of the stream
OkHttp and iOS: request with only-if-cache directive in Cache-Control header fails with 504 when match is stale
Currently (Ktor 1.6.8 and Ktor 2.0.0-beta1) setting an only-if-cached
Cache-Control Header doesn't work as expected.
The expectation is (from https://developer.mozilla.org/en-US/docs/Web/API/Request/cache):
If there is a match, fresh or stale, it will be returned from the cache.
If there is no match, the browser will respond with a 504 Gateway timeout status.
RFC: https://datatracker.ietf.org/doc/html/rfc7234#section-5.2.1.7
Ktor always fails with a 504 even if the there is a matching response in the cache.
Core
File.readChannel might leak open file handle outside scope
While investigating why my application kept a File handle open longer than what seems reasonable I encountered this code in Ktor. I dont think this exact issue is the cause of it but it seems prudent to get fixed regardless.
Problems:
- The function opens a file handle outside the coroutine with out guaranteed call to close (
RandomAccessFile(this@readChannel, "r")
). - The function then proceeds to call
.length()
on it outside of.use {
which may cause another exception when producing the error message for therequire
check. This is doubly weird sincefile.length()
is not part of therequire
condition. - Technically the GC will clean up for us but it is super annoying during development to have ktor lock files that are part of the project build outputs since it will break the compilation process when it attempts to delete it (on windows at least)
@OptIn(ExperimentalIoApi::class)
public fun File.readChannel(
start: Long = 0,
endInclusive: Long = -1,
coroutineContext: CoroutineContext = Dispatchers.IO
): ByteReadChannel {
val fileLength = length()
val file = RandomAccessFile(this@readChannel, "r")
return CoroutineScope(coroutineContext).writer(CoroutineName("file-reader") + coroutineContext, autoFlush = false) {
require(start >= 0L) { "start position shouldn't be negative but it is $start" }
require(endInclusive <= fileLength - 1) {
"endInclusive points to the position out of the file: " +
"file size = ${file.length()}, endInclusive = $endInclusive"
}
file.use {
val fileChannel: FileChannel = file.channel
if (start > 0) {
fileChannel.position(start)
}
I will submit a pull request to fix the issue.
Path parameter doesn't get encoded in type safe requests
Example:
Expected :/_matrix/client/v3/room_keys/keys/%21room%3Aexample%2Eorg/%2Bess%2FionId1?version=1
Actual :/_matrix/client/v3/room_keys/keys/!room:example.org/+ess/ionId1?version=1
The awaitSuspend method wakes up early in closed ByteChannelSequential
Parameters of cloned UrlBuilder affect parameters of an original builder
When I use function
URLBuilder.clone()
then I get new URLBuilder object.
But when I assign values to new object 'parameters' then 'parameters' in old object seems to be updated as well.
Docs
Provide a method to deserialize Websockets frames without receiving them
The existing WebSocketServerSession.receiveDeserialized
method doesn't allow to receive frames from the incoming
channel via the for
loop construction.
https://kotlinlang.slack.com/archives/C0A974TJ9/p1643325004741489
https://kotlinlang.slack.com/archives/C0A974TJ9/p1651820348768689
https://kotlinlang.slack.com/archives/C0A974TJ9/p1654823399432959
Document FarJar generation for Ktor plugin
Ticket KTOR-3573 (Gradle Plugin in general) and KTOR-3862 (FatJar generation)
Update the topic's structure to reflect that CIO is supported on Native targets
Describe in the documentation how to install curl library dependencies
Simple project with ktor cannot be compiled on linux with curl ktor client
fun main() = runBlocking {
println("Hello, Kotlin/Native on Linux!")
launch(context = Dispatchers.Default) {
val curlClient = HttpClient(Curl) {
engine {
sslVerify = false
}
}
val response = curlClient.get("https://ktor.io")
println("Response status: ${response.status}")
curlClient.close()
}
}
Ktor version: 2.0.2
Kotlin version: 1.7.0
Ubuntu Ubuntu 20.04.4 LTS 64-bit
Curl is installed and present in system:
curl --version
curl 7.68.0 (x86_64-pc-linux-gnu) libcurl/7.68.0 OpenSSL/1.1.1f zlib/1.2.11 brotli/1.0.7 libidn2/2.2.0 libpsl/0.21.0 (+libidn2/2.2.0) libssh/0.9.3/openssl/zlib nghttp2/1.40.0 librtmp/2.3
Release-Date: 2020-01-08
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS brotli GSS-API HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets
Compilation stacktrace:
> Task :linkDebugExecutableNativeLinux FAILED
e: /home/user/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/bin/ld.gold invocation reported errors
The /home/user/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/bin/ld.gold command returned non-zero exit code: 1.
output:
/home/user/.konan/dependencies/x86_64-unknown-linux-gnu-gcc-8.3.0-glibc-2.19-kernel-4.9-2/x86_64-unknown-linux-gnu/bin/ld.gold: error: cannot find -lcurl
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_global_init_wrapper23: error: undefined reference to 'curl_global_init'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_slist_append_wrapper27: error: undefined reference to 'curl_slist_append'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_slist_free_all_wrapper28: error: undefined reference to 'curl_slist_free_all'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_easy_strerror_wrapper33: error: undefined reference to 'curl_easy_strerror'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_easy_pause_wrapper35: error: undefined reference to 'curl_easy_pause'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_easy_init_wrapper36: error: undefined reference to 'curl_easy_init'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_easy_cleanup_wrapper38: error: undefined reference to 'curl_easy_cleanup'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_multi_init_wrapper44: error: undefined reference to 'curl_multi_init'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_multi_add_handle_wrapper45: error: undefined reference to 'curl_multi_add_handle'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_multi_remove_handle_wrapper46: error: undefined reference to 'curl_multi_remove_handle'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_multi_poll_wrapper49: error: undefined reference to 'curl_multi_poll'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_multi_wakeup_wrapper50: error: undefined reference to 'curl_multi_wakeup'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_multi_perform_wrapper51: error: undefined reference to 'curl_multi_perform'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_multi_cleanup_wrapper52: error: undefined reference to 'curl_multi_cleanup'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_multi_info_read_wrapper53: error: undefined reference to 'curl_multi_info_read'
/tmp/konan_temp1002284678272429140/result.o:out:function libcurl_curl_multi_strerror_wrapper54: error: undefined reference to 'curl_multi_strerror'
/tmp/konan_temp1002284678272429140/result.o:out:knifunptr_libcurl39_curl_easy_setopt: error: undefined reference to 'curl_easy_setopt'
/tmp/konan_temp1002284678272429140/result.o:out:knifunptr_libcurl42_curl_easy_getinfo: error: undefined reference to 'curl_easy_getinfo'
FAILURE: Build failed with an exception.
Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':linkDebugExecutableNativeLinux'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:145)
at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:143)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:131)
at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
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:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:402)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:389)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:382)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:368)
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 org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:61)
Caused by: org.jetbrains.kotlin.backend.konan.KonanCompilationException: Compilation finished with errors
at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:424)
at org.jetbrains.kotlin.cli.bc.K2Native$Companion$mainNoExitWithGradleRenderer$1.invoke(K2Native.kt:422)
at org.jetbrains.kotlin.util.UtilKt.profileIf(Util.kt:22)
at org.jetbrains.kotlin.util.UtilKt.profile(Util.kt:16)
at org.jetbrains.kotlin.cli.bc.K2Native$Companion.mainNoExitWithGradleRenderer(K2Native.kt:422)
at org.jetbrains.kotlin.cli.bc.K2NativeKt.mainNoExitWithGradleRenderer(K2Native.kt:677)
at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1$1.invoke(main.kt:70)
at org.jetbrains.kotlin.cli.utilities.MainKt$daemonMain$1$1.invoke(main.kt:70)
at org.jetbrains.kotlin.cli.utilities.MainKt.mainImpl(main.kt:19)
at org.jetbrains.kotlin.cli.utilities.MainKt.daemonMain(main.kt:70)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.runInProcess(KotlinToolRunner.kt:140)
at org.jetbrains.kotlin.compilerRunner.KotlinToolRunner.run(KotlinToolRunner.kt:85)
at org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink.compile(KotlinNativeTasks.kt:660)
Generator
Get io.ktor.server.application.DuplicatePluginException after project creation
I get an exception when try to run project after creation.
Exception in thread "main" io.ktor.server.application.DuplicatePluginException: Please make sure that you use unique name for the plugin and don't install it twice. Conflicting application plugin is already installed with the same key as `AuthenticationHolder`
at io.ktor.server.application.ApplicationPluginKt.install(ApplicationPlugin.kt:112)
at com.niki.plugins.SecurityKt.configureSecurity(Security.kt:37)
at com.niki.ApplicationKt$main$1.invoke(Application.kt:15)
at com.niki.ApplicationKt$main$1.invoke(Application.kt:8)
at io.ktor.server.engine.internal.CallableUtilsKt.executeModuleFunction(CallableUtils.kt:51)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:329)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$launchModuleByName$1.invoke(ApplicationEngineEnvironmentReloading.kt:328)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartupFor(ApplicationEngineEnvironmentReloading.kt:353)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.launchModuleByName(ApplicationEngineEnvironmentReloading.kt:328)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.access$launchModuleByName(ApplicationEngineEnvironmentReloading.kt:32)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:316)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading$instantiateAndConfigureApplication$1.invoke(ApplicationEngineEnvironmentReloading.kt:307)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.avoidingDoubleStartup(ApplicationEngineEnvironmentReloading.kt:335)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:307)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:144)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:274)
at io.ktor.server.cio.CIOApplicationEngine$initServerJob$1$2.invokeSuspend(CIOApplicationEngine.kt:157)
at io.ktor.server.cio.CIOApplicationEngine$initServerJob$1$2.invoke(CIOApplicationEngine.kt)
at io.ktor.server.cio.CIOApplicationEngine$initServerJob$1$2.invoke(CIOApplicationEngine.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:166)
at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
at io.ktor.server.cio.CIOApplicationEngine$initServerJob$1.invokeSuspend(CIOApplicationEngine.kt:156)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
fun main() {
embeddedServer(CIO, port = 8080, host = "0.0.0.0") {
configureAdministration()
configureRouting()
configureSockets()
configureSerialization()
configureTemplating()
configureMonitoring()
configureSecurity()
}.start(wait = true)
}
ktor_version=2.0.2
kotlin_version=1.7.0
IntelliJ IDEA 2022.1.2 (Ultimate Edition)
Build #IU-221.5787.30, built on May 31, 2022
After my project creation my Security.kt.
Suggest safer version of Gradle wrapper to a user
Can't Import Project Generated with start.ktor.io or Wizard with build.gradle.kts file
id "io.ktor.plugin" version "2.1.0"
Generator creates two routes under the same auth methods in `Authentication Basic` template
routing {
authenticate("myauth1") {
get("/protected/route/basic") {
val principal = call.principal<UserIdPrincipal>()!!
call.respondText("Hello ${principal.name}")
}
}
authenticate("myauth1") { // should be myauth2 here
get("/protected/route/form") {
val principal = call.principal<UserIdPrincipal>()!!
call.respondText("Hello ${principal.name}")
}
}
}
Upgrade logback to 1.2.11
The current version which is 1.2.3
is vulnerable, see Vulnerabilities
on Maven Central. Version 1.2.11
is a bit less vulnerable.
As a bonus in IDEA we will have no intention to upgrade to a newer version after creating a new Ktor project.
Use Gradle Ktor plugin in new projects in Generator
Authentication-feature-realated code and Routing-related-code are swapped in a project with these two features
Create a project with Authentication
and Routing
features, you'll see that Authentication.kt
has configureRouting
function and Routing.kt
has configureSecurity
.
These things are not in production yet, only in the main branch.
Ktor generator is unavailable due to SRE-A-111 and jackson dependency
Post mortem:
We encountered 4 types of problems:
- After fixing KTOR-4414 jackson dependency was promoted and is now incompatible at runtime with FUS due to (probably) kotlin compiler issues
Fixed - Deploy script must be updated and rewritten with new ingress version https://youtrack.jetbrains.com/articles/SRE-A-111/Migrate-to-k8s-v122
Fixed - After unsuccessful redeploy (with retries) if some specific stage has passed, one POD (instance, replica) still replaces one random old one. After 3 unsuccessful redeploys we have completely out-of-service.
Fixed - Deploy used to have readiness checks of 30 seconds which with avalanche requests (after getting server working again) don't pass due to the highload.
Fixed - We (maybe) have some issues in clients with retries that may populate multiple queries at the same time (probably in versions update every 24 hours -- in theory, it may be because some clients face 503 response at the same time, and then they retry at the same time next day. This may lead to the problem of insufficient number of replicas (3).
It is currently hard to prove this theory. We will continue monitoring nearest days and try to analyze the request frequencies throughout the day.
MissingApplicationPluginException in generated project in runtime due to a wrong order of modules installation
I have
build.gradl.kts
implementation("io.ktor:ktor-server-websockets-jvm:$ktor_version") // ktor_version=2.0.2
Application.kt
fun Application.module() {
configureKoin()
configureSecurity()
configureRouting()
configureHTTP()
httpsRedirect()
configureTemplating()
configureMonitoring()
configureSerialization()
configureSockets()
}
Sockets.kt
import io.ktor.server.application.*
import io.ktor.server.websocket.*
import java.time.Duration
fun Application.configureSockets() {
install(WebSockets) {
pingPeriod = Duration.ofSeconds(15)
timeout = Duration.ofSeconds(15)
maxFrameSize = Long.MAX_VALUE
masking = false
}
Routing.kt
fun Application.configureRouting() {
val chatService: ChatService by inject()
routing {
chatWebSocket(chatService)
}
}
}
ChatRoutes
fun Route.chatWebSocket(chatService: ChatService) {
authenticate {
webSocket("/api/websocket") { // websocketSession
incoming.consumeEach { frame ->
when (frame) {
is Frame.Binary -> TODO()
is Frame.Text -> TODO()
is Frame.Close -> TODO()
is Frame.Ping -> TODO()
is Frame.Pong -> TODO()
}
}
}
}
}
I get the Error
Exception in thread "main" io.ktor.server.application.MissingApplicationPluginException: Application plugin WebSockets is not installed
`routing` import is missing in WebSocket feature template
For example, in a new 2.x.x Ktor project the WebSockets feature code here is missing routing import:
package com.example.plugins
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import java.time.Duration
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.request.*
fun Application.configureRouting() {
install(WebSockets) {
pingPeriod = Duration.ofSeconds(15)
timeout = Duration.ofSeconds(15)
maxFrameSize = Long.MAX_VALUE
masking = false
}
routing {
webSocket("/ws") { // websocketSession
for (frame in incoming) {
if (frame is Frame.Text) {
val text = frame.readText()
outgoing.send(Frame.Text("YOU SAID: $text"))
if (text.equals("bye", ignoreCase = true)) {
close(CloseReason(CloseReason.Codes.NORMAL, "Client said BYE"))
}
}
}
}
}
}
Gradle Plugin
Ktor Gradle Plugin Downloads not Required Dependencies
Download https://plugins.gradle.org/m2/org/junit/jupiter/junit-jupiter-api/5.8.1/junit-jupiter-api-5.8.1.jar, took 164 ms (193.5 kB)
IntelliJ IDEA Plugin
Add the 'YAML file' option for the 'Configuration in' field in the generator
With v2.1.0, the YAML format is supported for a configuration file (KTOR-3572). YAML support requires the ktor-server-config-yaml
.
Plugin description disappears in IDEA plugin
To reproduce the issue, try to specify/clear a plugin name in a search box.
IntelliJ IDEA 2022.1 Beta (Ultimate Edition)
Build #IU-221.5080.56, built on March 23, 2022
Endpoints View: Can't find routes inside custom intercept
I'm using Ktor (server) for building an API, and some routes need a custom authentication system. So, I'm using a custom authenticated
function like this to retrieve easily the session from any authenticated routes :
fun Route.authenticated(build: Route.() -> Unit): Route {
val pipelinePhase = PipelinePhase("Validate")
val route = createChild(AuthenticatedSelector())
route.insertPhaseAfter(ApplicationCallPipeline.Plugins, pipelinePhase)
route.intercept(pipelinePhase) {
val token = call.request.header(HttpHeaders.Authorization) ?: return@intercept call.respondText(
"missing authentication token",
status = HttpStatusCode.BadRequest
)
val session = sessionsServices.session(token)
val user = transaction { usersDAO.get(session.userID) }
call.attributes.put(sessionKey, session)
call.attributes.put(userKey, user)
}
route.build()
return route
}
And I use it like this :
authenticated {
post("/logout") {
sessionsServices.revokeSession(call.session.token)
call.ok()
}
}
The problem is that routes inside the authenticated
function are not listed in the IntelliJ Endpoints View.
Is this a bug, or the authenticated
function is not supposed to be written like that?
Upgrading ktor from IDE sets version to 2.0.0-eap
I select "Upgrade project" when prompted about a new ktor update. The deps in the gradle build files get set to the version indicated in the subject. This has happened twice, now (2.0.0 and 2.0.1).
It might be worth noting that, both times, I had left the Maven ktor eap link in the build.gradle.kts files.
IU-213.6777.52, JRE 11.0.13+7-b1751.25x64 JetBrains s.r.o., OS Windows 10(amd64) v10.0 , screens 1920.0x1080.0
The 'Migrate Ktor to the latest version' intention doesn't add a JSON serialization dependency
- Create a new Ktor project with the
kotlinx.serialization
plugin (version 1.6.8). - Migrate it to 2.0.0 beta.
=> Noktor-serialization-kotlinx-json
dependency is added in a build script.
The 'HTTP Client' tab in the Endpoints tool window is inactive for a newly created Ktor project
IntelliJ IDEA 2022.1 Beta (Ultimate Edition)
Build #IU-221.5080.56, built on March 23, 2022
Add migrations for CORS plugin
This includes:
method(HttpMethod)
->allowMethod(HttpMethod)
header(Header)
->allowHeader(Header)
host(Host)
->allowHost(Host)
- Replace
maxAgeDuration
with fqn because it was moved fromio.ktor.features
toio.ktor.server.plugins.cors
Can't create ktor project
New project dialog for ktor is empty, next button without function.
In log:Â
2022-07-20 15:20:54,302 [ 987905] INFO - #i.k.i.a.KtorGeneratorWebAPIImpl - IOException loading JSON response from https://ktor-plugin.europe-north1-gke.intellij.net//project/settingsÂ
com.intellij.util.io.HttpRequests$HttpStatusException: <html>Â
<head><title>503 Service Temporarily Unavailable</title></head>Â
<body>Â
<center><h1>503 Service Temporarily Unavailable</h1></center>Â
<hr><center>nginx</center>Â
</body>Â
</html>
EAP repository is added in newly created project with Ktor 2.0.3
EAP repository is added in newly created project with Ktor 2.0.3
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/ktor/eap") }
shouldn't be added for the stable versions of Ktor
External links don't work in IDEA plugin
To reproduce, create a new project and try to visit any link on the Plugins page.
Velocity sample in docs will not compile
I observe the following:
install(Velocity) {
setProperty("resource.loader", "classpath")
setProperty("classpath.resource.loader.class", ClasspathResourceLoader::class.java.name)
}
}
Completion for Ktor plugins incorrectly generates code inside the previous plugin config
- Place the caret right after the plugin configuration block.
- Start typing the plugin name and press Enter.
=> IDEA inserts code inside the previous plugin config.
P.S. If an extra line is added after the previous plugin config, all works fine.
The Ktor plugin sometimes causes the 'java.lang.Throwable' when hovering over a URL and pressing Cmd
Build #IU-222.2270.31, built on May 18, 2022
java.lang.Throwable: Must be executed under progress indicator: com.intellij.openapi.progress.EmptyProgressIndicator@5c22341d. Please see e.g. ProgressManager.runProcess()
at com.intellij.openapi.diagnostic.Logger.error(Logger.java:182)
at com.intellij.openapi.progress.impl.CoreProgressManager.assertUnderProgress(CoreProgressManager.java:990)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processFilesConcurrentlyDespiteWriteActions(PsiSearchHelperImpl.java:434)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processPsiFileRoots$8(PsiSearchHelperImpl.java:402)
at com.intellij.psi.impl.PsiManagerImpl.runInBatchFilesMode(PsiManagerImpl.java:451)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processPsiFileRoots(PsiSearchHelperImpl.java:395)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processElementsWithTextInGlobalScope(PsiSearchHelperImpl.java:316)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.bulkProcessElementsWithWord(PsiSearchHelperImpl.java:225)
at com.intellij.psi.impl.search.Layer.processSingleRequest(helper.kt:260)
at com.intellij.psi.impl.search.Layer.processGlobalRequests(helper.kt:218)
at com.intellij.psi.impl.search.Layer.processWordRequests(helper.kt:172)
at com.intellij.psi.impl.search.Layer.runLayer(helper.kt:151)
at com.intellij.psi.impl.search.HelperKt.runSearch(helper.kt:59)
at com.intellij.model.search.impl.SearchWordQuery.processResults(SearchWordQuery.kt:19)
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 io.ktor.ide.KtorUastSearchUtilsKt.searchRoutingCalls(KtorUastSearchUtils.kt:109)
at io.ktor.ide.KtorUrlResolverKt.getAllUrlMappings$lambda-2(KtorUrlResolver.kt:64)
at com.intellij.psi.impl.PsiCachedValueImpl.doCompute(PsiCachedValueImpl.java:39)
at com.intellij.util.CachedValueBase.lambda$getValueWithLock$3(CachedValueBase.java:227)
at com.intellij.util.CachedValueBase.computeData(CachedValueBase.java:42)
at com.intellij.util.CachedValueBase.lambda$getValueWithLock$4(CachedValueBase.java:227)
at com.intellij.openapi.util.RecursionManager$1.computePreventingRecursion(RecursionManager.java:114)
at com.intellij.openapi.util.RecursionGuard.doPreventingRecursion(RecursionGuard.java:44)
at com.intellij.openapi.util.RecursionManager.doPreventingRecursion(RecursionManager.java:68)
at com.intellij.util.CachedValueBase.getValueWithLock(CachedValueBase.java:228)
at com.intellij.psi.impl.PsiCachedValueImpl.getValue(PsiCachedValueImpl.java:28)
at com.intellij.util.CachedValuesManagerImpl.getCachedValue(CachedValuesManagerImpl.java:72)
at com.intellij.psi.util.CachedValuesManager.getCachedValue(CachedValuesManager.java:111)
at io.ktor.ide.KtorUrlResolverKt.getAllUrlMappings(KtorUrlResolver.kt:61)
at io.ktor.ide.KtorUrlResolverKt.access$getAllUrlMappings(KtorUrlResolver.kt:1)
at io.ktor.ide.KtorUrlResolver$resolve$2.invoke(KtorUrlResolver.kt:30)
at io.ktor.ide.KtorUrlResolver$resolve$2.invoke(KtorUrlResolver.kt:29)
at kotlin.sequences.FlatteningSequence$iterator$1.ensureItemIterator(Sequences.kt:315)
at kotlin.sequences.FlatteningSequence$iterator$1.hasNext(Sequences.kt:303)
at kotlin.sequences.TransformingSequence$iterator$1.hasNext(Sequences.kt:214)
at kotlin.sequences.FlatteningSequence$iterator$1.ensureItemIterator(Sequences.kt:316)
at kotlin.sequences.FlatteningSequence$iterator$1.hasNext(Sequences.kt:303)
at kotlin.sequences.FlatteningSequence$iterator$1.ensureItemIterator(Sequences.kt:316)
at kotlin.sequences.FlatteningSequence$iterator$1.hasNext(Sequences.kt:303)
at com.intellij.microservices.url.UrlPathModelKt.filterBestUrlPathMatches(UrlPathModel.kt:310)
at com.intellij.microservices.url.references.UrlPathReferenceUnifiedPomTarget$$special$$inlined$lazySynchronousResolve$1.invoke(ReferenceResolveUtils.kt:33)
at kotlin.SafePublicationLazyImpl.getValue(LazyJVM.kt:107)
at com.intellij.microservices.url.references.UrlPathReferenceUnifiedPomTarget.getResolvedTargets(UrlPathReferenceUnifiedPomTarget.kt)
at com.intellij.microservices.url.references.UrlPathReferenceUnifiedPomTarget.getNavigatablePsiElement(UrlPathReferenceUnifiedPomTarget.kt:51)
at com.intellij.microservices.url.references.UrlPathReferenceUnifiedPomTarget.canNavigateToSource(UrlPathReferenceUnifiedPomTarget.kt:77)
at com.intellij.psi.impl.PomTargetPsiElementImpl.canNavigateToSource(PomTargetPsiElementImpl.java:152)
at com.intellij.navigation.impl.NavigationServiceImpl.rawNavigationRequest(NavigationServiceImpl.kt:25)
at com.intellij.pom.Navigatable.navigationRequest(Navigatable.java:32)
at com.intellij.psi.impl.PsiElementBase.navigationRequest(PsiElementBase.java:186)
at com.intellij.codeInsight.navigation.impl.PsiElementNavigationTarget.navigationRequest(PsiElementNavigationTarget.kt:20)
at com.intellij.codeInsight.navigation.impl.TargetGTDActionData.result(gtd.kt:83)
at com.intellij.codeInsight.navigation.impl.GtduKt.toGTDUActionData(gtdu.kt:70)
at com.intellij.codeInsight.navigation.impl.GtduKt.fromTargetData(gtdu.kt:64)
at com.intellij.codeInsight.navigation.impl.GtduKt.gotoDeclarationOrUsagesInner(gtdu.kt:59)
at com.intellij.codeInsight.navigation.impl.GtduKt.access$gotoDeclarationOrUsagesInner(gtdu.kt:1)
at com.intellij.codeInsight.navigation.impl.GtduKt$gotoDeclarationOrUsages$1.invoke(gtdu.kt:20)
at com.intellij.codeInsight.navigation.impl.GtduKt$gotoDeclarationOrUsages$1.invoke(gtdu.kt)
at com.intellij.codeInsight.navigation.impl.CommonKt.processInjectionThenHost(common.kt:14)
at com.intellij.codeInsight.navigation.impl.GtduKt.gotoDeclarationOrUsages(gtdu.kt:20)
at com.intellij.codeInsight.navigation.actions.GotoDeclarationOrUsageHandler2.gotoDeclarationOrUsages(GotoDeclarationOrUsageHandler2.kt:31)
at com.intellij.codeInsight.navigation.actions.GotoDeclarationOrUsageHandler2.getCtrlMouseData(GotoDeclarationOrUsageHandler2.kt:43)
at com.intellij.codeInsight.navigation.actions.GotoDeclarationAction.getCtrlMouseData(GotoDeclarationAction.java:96)
at com.intellij.codeInsight.navigation.CtrlMouseHandler2$computeInReadAction$1.invoke(CtrlMouseHandler.kt:235)
at com.intellij.codeInsight.navigation.CtrlMouseHandler2$computeInReadAction$1.invoke(CtrlMouseHandler.kt:77)
at com.intellij.lang.documentation.ide.impl.DocumentationTargetHoverInfoKt.injectedThenHost(DocumentationTargetHoverInfo.kt:76)
at com.intellij.codeInsight.navigation.CtrlMouseHandler2.computeInReadAction(CtrlMouseHandler.kt:234)
at com.intellij.codeInsight.navigation.CtrlMouseHandler2.access$computeInReadAction(CtrlMouseHandler.kt:77)
at com.intellij.codeInsight.navigation.CtrlMouseHandler2$compute$2$1.invoke(CtrlMouseHandler.kt:222)
at com.intellij.codeInsight.navigation.CtrlMouseHandler2$compute$2$1.invoke(CtrlMouseHandler.kt:77)
at com.intellij.openapi.application.rw.InternalReadAction.insideReadAction(InternalReadAction.kt:96)
at com.intellij.openapi.application.rw.InternalReadAction.access$insideReadAction(InternalReadAction.kt:13)
at com.intellij.openapi.application.rw.InternalReadAction$tryReadCancellable$1.invoke(InternalReadAction.kt:81)
at com.intellij.openapi.application.rw.InternalReadAction$tryReadCancellable$1.invoke(InternalReadAction.kt:13)
at com.intellij.openapi.progress.CancellationKt$sam$com_intellij_openapi_util_ThrowableComputable$0.compute(cancellation.kt)
at com.intellij.openapi.progress.Cancellation.withJob(Cancellation.java:60)
at com.intellij.openapi.progress.CancellationKt.withJob(cancellation.kt:14)
at com.intellij.openapi.progress.CancellationKt.executeWithJobAndCompleteIt(cancellation.kt:111)
at com.intellij.openapi.application.rw.CancellableReadActionKt$cancellableReadActionInternal$2$1.run(cancellableReadAction.kt:34)
at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1154)
at com.intellij.openapi.application.rw.CancellableReadActionKt$cancellableReadActionInternal$2.run(cancellableReadAction.kt:32)
at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runActionAndCancelBeforeWrite(ProgressIndicatorUtils.java:158)
at com.intellij.openapi.application.rw.CancellableReadActionKt.cancellableReadActionInternal(cancellableReadAction.kt:30)
at com.intellij.openapi.application.rw.InternalReadAction.tryReadCancellable(InternalReadAction.kt:80)
at com.intellij.openapi.application.rw.InternalReadAction.access$tryReadCancellable(InternalReadAction.kt:13)
at com.intellij.openapi.application.rw.InternalReadAction$tryReadAction$2.invoke(InternalReadAction.kt:66)
at com.intellij.openapi.application.rw.InternalReadAction$tryReadAction$2.invoke(InternalReadAction.kt:13)
at com.intellij.openapi.progress.CancellationKt$sam$com_intellij_openapi_util_ThrowableComputable$0.compute(cancellation.kt)
at com.intellij.openapi.progress.Cancellation.withJob(Cancellation.java:60)
at com.intellij.openapi.progress.CancellationKt.withJob(cancellation.kt:14)
at com.intellij.openapi.progress.CoroutinesKt.blockingContext(coroutines.kt:138)
at com.intellij.openapi.application.rw.InternalReadAction.tryReadAction(InternalReadAction.kt:61)
at com.intellij.openapi.application.rw.InternalReadAction.readLoop(InternalReadAction.kt:53)
at com.intellij.openapi.application.rw.InternalReadAction.access$readLoop(InternalReadAction.kt:13)
at com.intellij.openapi.application.rw.InternalReadAction$runReadAction$4.invokeSuspend(InternalReadAction.kt:33)
at com.intellij.openapi.application.rw.InternalReadAction$runReadAction$4.invoke(InternalReadAction.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
at com.intellij.openapi.application.rw.InternalReadAction.runReadAction(InternalReadAction.kt:32)
at com.intellij.openapi.application.rw.PlatformReadActionSupport.executeReadAction(PlatformReadActionSupport.kt:24)
at com.intellij.openapi.application.CoroutinesKt.constrainedReadAction(coroutines.kt:50)
at com.intellij.codeInsight.navigation.CtrlMouseHandler2$compute$2.invokeSuspend(CtrlMouseHandler.kt:221)
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)
Server
Client does not support sending or receiving json null value
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/588
How does one send and receive the json null
value ?
I had a look at implementing the change myself but it seems the non-nullability of the various content properties are quite pervasive so I am not sure how such a change would be most desirable to implement. I get the feeling that making them all nullable isnt appropriate.
According to https://tools.ietf.org/html/rfc8259#section-2 it seems to be valid.
2. JSON Grammar
A JSON text is a sequence of tokens. The set of tokens includes six
structural characters, strings, numbers, and three literal names.
A JSON text is a serialized value. Note that certain previous
specifications of JSON constrained a JSON text to be an object or an
array. Implementations that generate only objects or arrays where a
JSON text is called for will be interoperable in the sense that all
implementations will accept these as conforming JSON texts.
Support configuring Netty codec limits via application config
In our company we had to increase netty limits slightly. We are using EngineMain and application.conf.
Unfortionally, this configuration approach does not support netty configuration.
We should create an embeded version . . . It is not conviniant . . .
Reified type causes ApplicationCall.receive() throw UnsupportedOperationException
The following code (Kotlin 1.6.10, Ktor 1.6.7, Java 17.0.1, jvmTarget = "1.8") reproduces the error:
@Serializable
data class Age(val value: Int)
@Serializable
data class Update<T>(val old: T, val new: T)
fun main() {
embeddedServer(Netty, port = 8080) {
install(ContentNegotiation) {
json()
}
routing {
update<Age>()
}
}.start(wait = true)
}
inline fun <reified T> Route.update() {
post("/") {
val update = call.receive<Update<T>>()
application.log.info("update=$update")
call.respond(HttpStatusCode.OK, "OK")
}
}
Sending the following request:
$ curl -v -H "Content-Type: application/json" -POST http://localhost:8080/ -d '{"old":{"value":1},"new":{"value":2}}'
makes server throw:
java.lang.UnsupportedOperationException: This function has a reified type parameter and thus can only be inlined at compilation time, not called directly.
at kotlin.jvm.internal.Intrinsics.throwUndefinedForReified(Intrinsics.java:207)
at kotlin.jvm.internal.Intrinsics.throwUndefinedForReified(Intrinsics.java:201)
at kotlin.jvm.internal.Intrinsics.reifiedOperationMarker(Intrinsics.java:211)
at ServerKt$update$1.invokeSuspend(server.kt:34)
at ServerKt$update$1.invoke(server.kt)
at ServerKt$update$1.invoke(server.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:248)
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:78)
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.routing.Routing$Feature$install$1.invoke(Routing.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:248)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:116)
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:145)
at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:248)
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:78)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:127)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt)
at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:248)
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:78)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:123)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt)
at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:55)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.startCoroutineImpl(Builders.common.kt:194)
at kotlinx.coroutines.BuildersKt.startCoroutineImpl(Unknown Source)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:134)
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(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:469)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.ktor.server.netty.EventLoopGroupProxy$Companion.create$lambda-1$lambda-0(NettyApplicationEngine.kt:251)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:831)
I found similar problem KTOR-582 that was identified as Kotlin compiler’s issue and eventually resolved as Fixed.
Support setting caching options on call
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1688
Subsystem
CachingHeaders feature 1.3.1 and its docs
Is your feature request related to a problem?
I was trying to set up a cache for an endpoint, but the request has to be conditionally cached depending on whether the user is logged in. i.e.
get<Cinemas.Routes.List> {
if (call.hasUser) {
// this cannot be cached, because the user can change cinema's state (favorite it)
call.respond(repository.getCinemasAuth(userID = call.userId))
} else {
// no logged in user, safe to cache for days/weeks since the cinema list barely ever changes
call.cache(CachingOptions(/* parameters specifically for this response path */))
call.respond(repository.getActiveCinemas())
}
}
Describe the solution you'd like
The implementation of ApplicationCall.cache()
was really tricky. The documentation only shows how to cache centrally based on content type. No mention of caching by route or code path. Reading the code and debugging for hours I found that the following setup works:
embeddedServer(Netty, port) { // this: Application
install(CachingHeaders) {
// default is `options { it.caching }`, see ktorio/ktor#1687
}
routing {
get ... // as above
}
}
fun ApplicationCall.cache(caching: CachingOption) {
this.response.pipeline.intercept(ApplicationSendPipeline.Render) {
// by definition of Render phase, it can be only OutgoingContent here
// this will be read by the default CachingHeaders.options lambda
// and put it among the response headers
(subject as OutgoingContent).caching = caching
}
}
Motivation to include to ktor
A function like call.cache(...)
above or a property call.caching = ...
would be beneficial to share via in-built extensions, so that users who write Ktor apps can set caching up easily.
It is possible that what's missing is a lambda-bearing overload of respond(Any)
similar to respondText
, where OutgoingContent
can be amended.
CORS: Pattern matching for origin
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1989
Subsystem
Server
Is your feature request related to a problem? Please describe.
I stumbled upon a problem where I need to allow origin by wildcard or pattern, ex. http://*.example.com so any subdomain is allowed.
Describe the solution you'd like
I want to be able to specify a host with a pattern matching like regex or glob style or give us a way to set a custom callback for checking the origin
Motivation to include to ktor
There is no way to solve this with the current CORS feature.
Make Content-Length header validation optional
How should we handle the newly thrown IOException when there's a Content-Length mismatch?
Relevant PR (released in 2.0.3): https://github.com/ktorio/ktor/pull/3069
We recently upgraded to KTOR 2.0.3 and were caught out by the new exception-throwing behavior introduced by the above bug-fix. We receive a small handful of incomplete requests per day due to clients with flaky networks. Before the upgrade, the incomplete request bodies would arrive at our server as empty or incomplete JSON, and we were using ContentNegotiation
to catch any JSON parse errors (which would include these requests) and respond to the client accordingly (with a 400 status code, and no noise in our logs).
The newly thrown IOException happens at an earlier point than this, and by default results in an unhandled exception (causing log noise) and just closes the channel which takes away our opportunity to craft a response to the client. IMO this isn't the ideal default behavior here. In order to override this ourselves, we're left with a few options:
-
Add to our existing
StatusPages
config to catch the new error and downgrade to a 400 like before. This doesn't feel great - we'd have to rely on the exception message which would be quite brittle (without it, we'd just be generically downgrading all IOExceptions, which we definitely don't want to do!) -
Introduce an intermediate helper function wrapping
call.receive
so we can catch IOExceptions at this point and re-throw as something for StatusPages to deal with. Also not great, as now our engineers have to remember to use this special version in order to get the error handling logic. -
Attempt to intercept the correct pipeline phase (
ApplicationReceivePipeline.Phases.Before
, perhaps?) to generically catch IOExceptions at the point of receiving request content, and re-throw as something for StatusPages to deal with.
Another thing that makes this awkward is that the change is specific to the NettyApplicationEngine
- there's no corresponding change in the TestApplicationEngine
for me to write a unit test against. So to test my fix I'm having to resort to a more full-on API test fired at a real backend instance. FWIW, I gave option 3) a go and it kinda worked (we can catch and wrap the IOException), but out attempt to respond with a 400
doesn't work because the channel has been closed (so in reality, my test client just hangs and doesn't get a response).
I thought it was worth raising an issue as I'm not sure what downstream impact was anticipated from this change - what's the intended way for us to be handling this? It feels to me like something KTOR could take care of by default, automatically returning a 400: BadRequest
on the call instead of throwing an exception and closing the channel.
Incoming request body validation
This issue was imported from GitHub issue: https://github.com/ktorio/ktor/issues/1524
Subsystem
Server
Is your feature request related to a problem? Please describe.
The feature is related to the incoming request validation problem.
Describe the solution you'd like
You can find the implementation here: https://github.com/viartemev/ktor-validation-feature
Motivation to include to ktor
This is a core feature of all HTTP servers, most of the incoming requests must be validated. This feature provides a simple way for incoming request validation.
If you think that the proposed option suits for Ktor I can create PR and add tests for it.
Add YAML Configuration Format Support
Right now we're supporting automatically only hocon
configuration format(with application.conf
file located in the resources folder). Unfortunately, this format is supported only with using external plugins so it's hard to provide any navigation features.
As the solution, we want to have the YAML support(same as HOCON) with a brief review of the API on how to fetch properties.
Allow overriding HSTS settings per host
Jetty: Content Length exception when body size is greater than 4096 bytes
When updated to ktor 2.0.3 the following issue can occur:
Client provided less bytes than content length. Expected 7636 but got 4096.
This happens when we post body greater than 4096. Should be related to KTOR-4379
I'm using Jetty as a container. See the sample app in the attach
OverridingClassLoader fails to delegate to parent for resources
I have submitted a test and fix in PR https://github.com/ktorio/ktor/pull/2893
It should be pretty obvious that this is a critical flaw. I hope we can get it backported to the 1.x versions also.
Allow Pebble to use Accepted Language header for built-in i18n support
Adding support for improving the ergonomics of using pebbles built in I18N support. It essentially makes it so you do not need to match Accept Headers to Locales in every route manually.
This feature should not break any existing usage of the library with this change.
ApplicationConfig lacks the ability to export a part of the config to a third-party library
Let's say I want to use some library which has some configure(Map)
or configure(Properties)
method. There is no way to gather its configuration in a someLibrary { ... }
section of my application.conf
without having to extract manually one by one all its configuration value from the ApplicationConfig
object.
I suggest adding an ApplicationConfig.toMap(): Map<String, Any?>
method for such a use case.
Test Infrastructure
Test engine can't handle concurrent requests
Backstory
I have a big application in Ktor1 with a lot of tests. Tests are being run in parallel. Ktor test engine is being reused. There are also tests that test some concurrent scenarios.
When I tried to migrate to Ktor2 everything just hangs and tests can't even exit properly, they just hang forever.
Repro
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import kotlin.test.assertEquals
import kotlinx.coroutines.IO_PARALLELISM_PROPERTY_NAME
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.junit.jupiter.api.Test
class KtorTest {
init {
// uncomment me to make test work
// System.setProperty(IO_PARALLELISM_PROPERTY_NAME, "128")
}
@Test
fun `ktor test engine should handle concurrent requests properly`() = testApplication {
routing {
get("/") {
call.respondText("Hello World!")
}
}
coroutineScope {
val jobs = (1..100).map {
async {
client.get("/").apply {
assertEquals("Hello World!", bodyAsText())
}
}
}
jobs.forEach { it.join() }
}
}
}
Workaround
- I can increase IO_PARALLELISM_PROPERTY_NAME to an obnoxious number and pray that it will be enough
- Don't migrate. Ktor1 is perfectly fine with similar test
Probable root cause
Ktor2 test engine is BLOCKING the thread on each HttpClient Request
That non-suspended call to handleRequest
is being called from suspended function here
Because these calls are being scheduled to Dispatchers.IO which has an unscalable number of threads to run (defaulted to 64) whenever threads run out, it blocks forever.
But why does it block forever and the engine doesn't respond?
PROBABLY because testApplication
is written in such a way, that test is being executed against ApplicationTestBuilder
(fully run to the end) and only AFTER that engine is started
val builder = ApplicationTestBuilder()
.apply { runBlocking { block() } }
val testApplication = TestApplication(builder)
testApplication.engine.start()
testApplication.stop()
where block()
is the whole test body.
Other
Revert default behavior of string encoding for ContentNegotiation and JsonPlugin
Changing the mechanism of String encoding is a major regression. The new behavior should be enabled separately with:
removeIgnoredType<String>()