diff --git a/Sources/AHCHTTPClient/AHC+HTTPClient.swift b/Sources/AHCHTTPClient/AHC+HTTPClient.swift index 4469f5a..d37a4a3 100644 --- a/Sources/AHCHTTPClient/AHC+HTTPClient.swift +++ b/Sources/AHCHTTPClient/AHC+HTTPClient.swift @@ -18,6 +18,7 @@ import Foundation import HTTPTypes import NIOCore import NIOHTTP1 +public import NetworkTypes import Synchronization @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, *) @@ -25,8 +26,8 @@ extension AsyncHTTPClient.HTTPClient: HTTPAPIs.HTTPClient { public typealias RequestWriter = RequestBodyWriter public typealias ResponseConcludingReader = ResponseReader - public struct RequestOptions: HTTPClientCapability.RequestOptions { - + public struct RequestOptions: HTTPClientCapability.ServerTransportHint { + public var serverSupportedTransportsHint: Set = [] } public struct RequestBodyWriter: AsyncWriter, ~Copyable { diff --git a/Sources/HTTPAPIs/Client/HTTPClientCapability+ServerTransportHint.swift b/Sources/HTTPAPIs/Client/HTTPClientCapability+ServerTransportHint.swift new file mode 100644 index 0000000..76b1552 --- /dev/null +++ b/Sources/HTTPAPIs/Client/HTTPClientCapability+ServerTransportHint.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift HTTP API Proposal open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public import NetworkTypes + +@available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) +extension HTTPClientCapability { + /// A protocol for HTTP request options that hint at the transports a + /// server is known to support. + /// + /// Providing server transport information allows the client to optimize + /// connection establishment. For example, if a server is known to support + /// QUIC, the client can attempt an HTTP/3 connection directly instead of + /// falling back to TCP-based negotiation. + public protocol ServerTransportHint: RequestOptions { + /// The transports that the target server is known to support. + /// + /// An empty set indicates no prior knowledge of server capabilities, + /// and the client uses its default protocol negotiation behavior. + var serverSupportedTransportsHint: Set { get set } + } +} diff --git a/Sources/HTTPClient/DefaultHTTPClient.swift b/Sources/HTTPClient/DefaultHTTPClient.swift index 29bceec..063ac56 100644 --- a/Sources/HTTPClient/DefaultHTTPClient.swift +++ b/Sources/HTTPClient/DefaultHTTPClient.swift @@ -137,12 +137,12 @@ public final class DefaultHTTPClient: HTTPAPIs.HTTPClient { options: HTTPRequestOptions, responseHandler: (HTTPResponse, consuming ResponseConcludingReader) async throws -> Return ) async throws -> Return { - // TODO: translate request options - let options = self.client.defaultRequestOptions + var translatedOptions = self.client.defaultRequestOptions + translatedOptions.serverSupportedTransportsHint = options.serverSupportedTransportsHint let body = body.map { HTTPClientRequestBody(other: $0) { RequestWriter(actual: $0) } } - return try await self.client.perform(request: request, body: body, options: options) { response, body in + return try await self.client.perform(request: request, body: body, options: translatedOptions) { response, body in try await responseHandler(response, ResponseConcludingReader(actual: body)) } } diff --git a/Sources/HTTPClient/HTTPRequestOptions.swift b/Sources/HTTPClient/HTTPRequestOptions.swift index c87bdfa..5ca9cc8 100644 --- a/Sources/HTTPClient/HTTPRequestOptions.swift +++ b/Sources/HTTPClient/HTTPRequestOptions.swift @@ -10,9 +10,12 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// +public import NetworkTypes /// The options for the default HTTP client implementation. @available(macOS 26.2, iOS 26.2, watchOS 26.2, tvOS 26.2, visionOS 26.2, *) -public struct HTTPRequestOptions: HTTPClientCapability.RequestOptions { +public struct HTTPRequestOptions: HTTPClientCapability.ServerTransportHint { + public var serverSupportedTransportsHint: Set = [] + public init() {} } diff --git a/Sources/NetworkTypes/HTTPTransportVersion.swift b/Sources/NetworkTypes/HTTPTransportVersion.swift new file mode 100644 index 0000000..8233669 --- /dev/null +++ b/Sources/NetworkTypes/HTTPTransportVersion.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift HTTP API Proposal open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// An enumeration that represents a transport protocol used to carry HTTP +/// traffic. +/// +/// ``HTTPTransportVersion`` provides type-safe access to supported transport +/// protocols, allowing clients and servers to communicate transport +/// capabilities. New transports may be added in future releases, so client +/// code must handle unknown cases. +@nonexhaustive +public enum HTTPTransportVersion: Sendable, Hashable { + /// TCP transport. + /// + /// Whether TLS is layered on top is determined by the request's URL + /// scheme (`http` vs. `https`). + case tcp + + /// QUIC transport. + /// + /// QUIC is defined in RFC 9000 and is the transport used by HTTP/3 + /// (RFC 9114). + case quic +} diff --git a/Sources/URLSessionHTTPClient/URLSessionHTTPClient.swift b/Sources/URLSessionHTTPClient/URLSessionHTTPClient.swift index 499e462..bda4a70 100644 --- a/Sources/URLSessionHTTPClient/URLSessionHTTPClient.swift +++ b/Sources/URLSessionHTTPClient/URLSessionHTTPClient.swift @@ -279,7 +279,7 @@ public final class URLSessionHTTPClient: HTTPClient, IdleTimerEntryProvider { } request.allowsExpensiveNetworkAccess = options.allowsExpensiveNetworkAccess request.allowsConstrainedNetworkAccess = options.allowsConstrainedNetworkAccess - request.assumesHTTP3Capable = options.assumesHTTP3Capable + request.assumesHTTP3Capable = options.serverSupportedTransportsHint.contains(.quic) if let stallTimeout = options.stallTimeout { request.timeoutInterval = stallTimeout / .seconds(1) } else { diff --git a/Sources/URLSessionHTTPClient/URLSessionRequestOptions.swift b/Sources/URLSessionHTTPClient/URLSessionRequestOptions.swift index b263d88..c0305c9 100644 --- a/Sources/URLSessionHTTPClient/URLSessionRequestOptions.swift +++ b/Sources/URLSessionHTTPClient/URLSessionRequestOptions.swift @@ -19,7 +19,8 @@ public import NetworkTypes public struct URLSessionRequestOptions: HTTPClientCapability.RedirectionHandler, HTTPClientCapability.TLSSecurityHandler, - HTTPClientCapability.TLSVersionSelection + HTTPClientCapability.TLSVersionSelection, + HTTPClientCapability.ServerTransportHint { public var redirectionHandler: (any HTTPClientRedirectionHandler)? = nil @@ -30,7 +31,7 @@ public struct URLSessionRequestOptions: public var maximumTLSVersion: TLSVersion = .v1_3 public var allowsExpensiveNetworkAccess: Bool = true public var allowsConstrainedNetworkAccess: Bool = true - public var assumesHTTP3Capable: Bool = false + public var serverSupportedTransportsHint: Set = [.tcp] public var stallTimeout: Duration? = nil public init() {}