diff --git a/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/KtorNSURLSessionDelegate.kt b/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/KtorNSURLSessionDelegate.kt index b963965bd02..006bc4fc9c3 100644 --- a/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/KtorNSURLSessionDelegate.kt +++ b/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/KtorNSURLSessionDelegate.kt @@ -11,7 +11,6 @@ import kotlinx.cinterop.UnsafeNumber import kotlinx.coroutines.CompletableDeferred import platform.Foundation.* import platform.darwin.NSObject -import kotlin.collections.set import kotlin.coroutines.CoroutineContext private const val HTTP_REQUESTS_INITIAL_CAPACITY = 32 @@ -37,6 +36,7 @@ public fun KtorNSURLSessionDelegate(): KtorNSURLSessionDelegate { * * For HTTP requests to work property, it's important that users call these functions: * * URLSession:dataTask:didReceiveData: + * * URLSession:task:didFinishCollectingMetrics: * * URLSession:task:didCompleteWithError: * * URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler: * @@ -65,6 +65,16 @@ public class KtorNSURLSessionDelegate( override fun URLSession(session: NSURLSession, taskIsWaitingForConnectivity: NSURLSessionTask) { } + override fun URLSession( + session: NSURLSession, + task: NSURLSessionTask, + didFinishCollectingMetrics: NSURLSessionTaskMetrics + ) { + val lastTransactionMetrics = didFinishCollectingMetrics.transactionMetrics.lastOrNull() + as? NSURLSessionTaskTransactionMetrics + lastTransactionMetrics?.let { taskHandlers[task]?.saveMetrics(it) } + } + override fun URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError: NSError?) { taskHandlers[task]?.let { it.complete(task, didCompleteWithError) diff --git a/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/internal/DarwinTaskHandler.kt b/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/internal/DarwinTaskHandler.kt index ead003b3240..5f345a68cbe 100644 --- a/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/internal/DarwinTaskHandler.kt +++ b/ktor-client/ktor-client-darwin/darwin/src/io/ktor/client/engine/darwin/internal/DarwinTaskHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package io.ktor.client.engine.darwin.internal @@ -9,12 +9,16 @@ import io.ktor.client.request.* import io.ktor.http.* import io.ktor.util.date.* import io.ktor.utils.io.* -import io.ktor.utils.io.CancellationException -import kotlinx.cinterop.* -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.* +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.UnsafeNumber +import kotlinx.cinterop.convert +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.consumeEach import platform.Foundation.* -import kotlin.coroutines.* +import kotlin.coroutines.CoroutineContext @OptIn(DelicateCoroutinesApi::class) internal class DarwinTaskHandler( @@ -29,6 +33,8 @@ internal class DarwinTaskHandler( private var pendingFailure: Throwable? = null get() = field?.also { field = null } + private var metrics: NSURLSessionTaskTransactionMetrics? = null + private val body: ByteReadChannel = GlobalScope.writer(callContext) { try { bodyChunks.consumeEach { @@ -42,15 +48,10 @@ internal class DarwinTaskHandler( }.channel fun receiveData(dataTask: NSURLSessionDataTask, data: NSData) { - if (!response.isCompleted) { - val result = dataTask.response as NSHTTPURLResponse - response.complete(result.toResponseData(requestData)) - } - val content = data.toByteArray() try { bodyChunks.trySend(content).isSuccess - } catch (cause: CancellationException) { + } catch (_: CancellationException) { dataTask.cancel() } } @@ -59,6 +60,10 @@ internal class DarwinTaskHandler( pendingFailure = cause } + fun saveMetrics(taskMetrics: NSURLSessionTaskTransactionMetrics) { + metrics = taskMetrics + } + fun complete(task: NSURLSessionTask, didCompleteWithError: NSError?) { if (didCompleteWithError != null) { val exception = pendingFailure ?: handleNSError(requestData, didCompleteWithError) @@ -87,9 +92,16 @@ internal class DarwinTaskHandler( status, requestTime, headers, - HttpProtocolVersion.HTTP_1_1, + protocolVersion(), responseBody, callContext ) } + + private fun protocolVersion(): HttpProtocolVersion = when (metrics?.networkProtocolName) { + "http/1.1" -> HttpProtocolVersion.HTTP_1_1 + "h2", "h2c" -> HttpProtocolVersion.HTTP_2_0 + "h3" -> HttpProtocolVersion.HTTP_3_0 + else -> HttpProtocolVersion.HTTP_1_1 + } } diff --git a/ktor-client/ktor-client-darwin/darwin/test/DarwinHttp2Test.kt b/ktor-client/ktor-client-darwin/darwin/test/DarwinHttp2Test.kt index 6b46039b7f8..e61daf2d427 100644 --- a/ktor-client/ktor-client-darwin/darwin/test/DarwinHttp2Test.kt +++ b/ktor-client/ktor-client-darwin/darwin/test/DarwinHttp2Test.kt @@ -7,16 +7,10 @@ package io.ktor.client.engine.darwin import io.ktor.client.engine.darwin.utils.* import io.ktor.client.tests.* import kotlinx.cinterop.UnsafeNumber -import kotlin.test.Ignore -import kotlin.test.Test class DarwinHttp2Test : Http2Test(Darwin, useH2c = false) { @OptIn(UnsafeNumber::class) override fun DarwinClientEngineConfig.disableCertificateValidation() { handleChallenge { _, _, challenge, completionHandler -> trustAnyCertificate(challenge, completionHandler) } } - - @Ignore // KTOR-9095 Darwin: HttpResponse.version always returns HTTP_1_1 - @Test - override fun `test protocol version is HTTP 2`() {} }