Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.26.0"),
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.24.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
.package(url: "https://github.com/apple/swift-distributed-tracing.git", from: "1.2.2"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),
],
Expand All @@ -71,6 +72,7 @@ let package = Package(
.product(name: "NIOSOCKS", package: "swift-nio-extras"),
.product(name: "NIOTransportServices", package: "swift-nio-transport-services"),
.product(name: "Logging", package: "swift-log"),
.product(name: "Tracing", package: "swift-distributed-tracing"),
.product(name: "Atomics", package: "swift-atomics"),
.product(name: "Algorithms", package: "swift-algorithms"),
],
Expand Down
38 changes: 32 additions & 6 deletions Sources/AsyncHTTPClient/AsyncAwait/HTTPClient+execute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,22 @@
//===----------------------------------------------------------------------===//

import Logging
import Tracing
import NIOCore
import NIOHTTP1

import struct Foundation.URL

private struct HTTPHeadersInjector: Injector, @unchecked Sendable {
static let shared: HTTPHeadersInjector = HTTPHeadersInjector()

private init() {}

func inject(_ value: String, forKey name: String, into headers: inout HTTPHeaders) {
headers.add(name: name, value: value)
}
}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension HTTPClient {
/// Execute arbitrary HTTP requests.
Expand All @@ -36,12 +47,27 @@ extension HTTPClient {
deadline: NIODeadline,
logger: Logger? = nil
) async throws -> HTTPClientResponse {
try await self.executeAndFollowRedirectsIfNeeded(
request,
deadline: deadline,
logger: logger ?? Self.loggingDisabled,
redirectState: RedirectState(self.configuration.redirectConfiguration.mode, initialURL: request.url)
)
func perform(request: HTTPClientRequest) async throws -> HTTPClientResponse {
try await self.executeAndFollowRedirectsIfNeeded(
request,
deadline: deadline,
logger: logger ?? Self.loggingDisabled,
redirectState: RedirectState(self.configuration.redirectConfiguration.mode, initialURL: request.url)
)
}
if let tracer = self.configuration.tracer {
return try await tracer.withSpan("HTTPClient.execute") { span in
var request = request
tracer.inject(
span.context,
into: &request.headers,
using: HTTPHeadersInjector.shared
)
return try await perform(request: request)
}
} else {
return try await perform(request: request)
}
}
}

Expand Down
63 changes: 51 additions & 12 deletions Sources/AsyncHTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import Atomics
import Foundation
import Logging
import Tracing
import NIOConcurrencyHelpers
import NIOCore
import NIOHTTP1
Expand Down Expand Up @@ -807,7 +808,7 @@ public final class HTTPClient: Sendable {
public struct Configuration {
/// TLS configuration, defaults to `TLSConfiguration.makeClientConfiguration()`.
public var tlsConfiguration: Optional<TLSConfiguration>

/// Sometimes it can be useful to connect to one host e.g. `x.example.com` but
/// request and validate the certificate chain as if we would connect to `y.example.com`.
/// ``dnsOverride`` allows to do just that by mapping host names which we will request and validate the certificate chain, to a different
Expand All @@ -817,7 +818,7 @@ public final class HTTPClient: Sendable {
/// `url` of `https://example.com/`, the ``HTTPClient`` will actually open a connection to `localhost` instead of `example.com`.
/// ``HTTPClient`` will still request certificates from the server for `example.com` and validate them as if we would connect to `example.com`.
public var dnsOverride: [String: String] = [:]

/// Enables following 3xx redirects automatically.
///
/// Following redirects are supported:
Expand All @@ -841,24 +842,24 @@ public final class HTTPClient: Sendable {
/// Ignore TLS unclean shutdown error, defaults to `false`.
@available(
*,
deprecated,
message:
deprecated,
message:
"AsyncHTTPClient now correctly supports handling unexpected SSL connection drops. This property is ignored"
)
public var ignoreUncleanSSLShutdown: Bool {
get { false }
set {}
}

/// What HTTP versions to use.
///
/// Set to ``HTTPVersion-swift.struct/automatic`` by default which will use HTTP/2 if run over https and the server supports it, otherwise HTTP/1
public var httpVersion: HTTPVersion

/// Whether ``HTTPClient`` will let Network.framework sit in the `.waiting` state awaiting new network changes, or fail immediately. Defaults to `true`,
/// which is the recommended setting. Only set this to `false` when attempting to trigger a particular error path.
public var networkFrameworkWaitForConnectivity: Bool

/// The maximum number of times each connection can be used before it is replaced with a new one. Use `nil` (the default)
/// if no limit should be applied to each connection.
///
Expand All @@ -870,20 +871,35 @@ public final class HTTPClient: Sendable {
}
}
}

/// Whether ``HTTPClient`` will use Multipath TCP or not
/// By default, don't use it
public var enableMultipath: Bool

/// A method with access to the HTTP/1 connection channel that is called when creating the connection.
public var http1_1ConnectionDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)?

/// A method with access to the HTTP/2 connection channel that is called when creating the connection.
public var http2ConnectionDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)?

/// A method with access to the HTTP/2 stream channel that is called when creating the stream.
public var http2StreamChannelDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)?


private var anyTracer: (any Sendable)?

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public var tracer: (any Tracer)? {
get {
guard let anyTracer else {
return nil
}
return anyTracer as! (any Tracer)?
}
set {
self.anyTracer = newValue
}
}

public init(
tlsConfiguration: TLSConfiguration? = nil,
redirectConfiguration: RedirectConfiguration? = nil,
Expand All @@ -903,6 +919,29 @@ public final class HTTPClient: Sendable {
self.networkFrameworkWaitForConnectivity = true
self.enableMultipath = false
}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public init(
tlsConfiguration: TLSConfiguration? = nil,
redirectConfiguration: RedirectConfiguration? = nil,
timeout: Timeout = Timeout(),
connectionPool: ConnectionPool = ConnectionPool(),
proxy: Proxy? = nil,
ignoreUncleanSSLShutdown: Bool = false,
decompression: Decompression = .disabled,
tracer: (any Tracer)? = InstrumentationSystem.tracer
) {
self.init(
tlsConfiguration: tlsConfiguration,
redirectConfiguration: redirectConfiguration,
timeout: timeout,
connectionPool: connectionPool,
proxy: proxy,
ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown,
decompression: decompression
)
self.anyTracer = tracer
}

public init(
tlsConfiguration: TLSConfiguration? = nil,
Expand Down
Loading