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.
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
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
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
Client
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
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 :(
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)
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
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
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.
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
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.