diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift index 1461a6620..3a0011d5e 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift @@ -322,6 +322,7 @@ extension HTTPConnectionPool.ConnectionFactory { if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { return tsBootstrap .channelOption(NIOTSChannelOptions.waitForActivity, value: self.clientConfiguration.networkFrameworkWaitForConnectivity) + .channelOption(NIOTSChannelOptions.multipathServiceType, value: self.clientConfiguration.enableMultipath ? .handover : .disabled) .connectTimeout(deadline - NIODeadline.now()) .channelInitializer { channel in do { @@ -338,6 +339,7 @@ extension HTTPConnectionPool.ConnectionFactory { if let nioBootstrap = ClientBootstrap(validatingGroup: eventLoop) { return nioBootstrap .connectTimeout(deadline - NIODeadline.now()) + .enableMPTCP(clientConfiguration.enableMultipath) } preconditionFailure("No matching bootstrap found") @@ -415,6 +417,7 @@ extension HTTPConnectionPool.ConnectionFactory { tsBootstrap .channelOption(NIOTSChannelOptions.waitForActivity, value: self.clientConfiguration.networkFrameworkWaitForConnectivity) + .channelOption(NIOTSChannelOptions.multipathServiceType, value: self.clientConfiguration.enableMultipath ? .handover : .disabled) .connectTimeout(deadline - NIODeadline.now()) .tlsOptions(options) .channelInitializer { channel in @@ -443,6 +446,7 @@ extension HTTPConnectionPool.ConnectionFactory { let bootstrap = ClientBootstrap(group: eventLoop) .connectTimeout(deadline - NIODeadline.now()) + .enableMPTCP(clientConfiguration.enableMultipath) .channelInitializer { channel in sslContextFuture.flatMap { sslContext -> EventLoopFuture in do { diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index c52263318..096fb9387 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -738,6 +738,10 @@ public class HTTPClient { } } + /// Whether ``HTTPClient`` will use Multipath TCP or not + /// By default, don't use it + public var enableMultipath: Bool + public init( tlsConfiguration: TLSConfiguration? = nil, redirectConfiguration: RedirectConfiguration? = nil, @@ -755,6 +759,7 @@ public class HTTPClient { self.decompression = decompression self.httpVersion = .automatic self.networkFrameworkWaitForConnectivity = true + self.enableMultipath = false } public init(tlsConfiguration: TLSConfiguration? = nil, diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index ac0aee068..0c259c1bd 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -3590,6 +3590,24 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertEqual(.ok, response.status) } + func testClientWithMultipath() throws { + do { + var conf = HTTPClient.Configuration() + conf.enableMultipath = true + let client = HTTPClient(configuration: conf) + defer { + XCTAssertNoThrow(try client.shutdown().wait()) + } + let response = try client.get(url: self.defaultHTTPBinURLPrefix + "get").wait() + XCTAssertEqual(.ok, response.status) + } catch let error as IOError where error.errnoCode == EINVAL || error.errnoCode == EPROTONOSUPPORT || error.errnoCode == ENOPROTOOPT { + // some old Linux kernels don't support MPTCP, skip this test in this case + // see https://www.mptcp.dev/implementation.html for details about each type + // of error + throw XCTSkip() + } + } + func testSingletonClientWorks() throws { let response = try HTTPClient.shared.get(url: self.defaultHTTPBinURLPrefix + "get").wait() XCTAssertEqual(.ok, response.status)