From cb49c660a1e86d72072321bf7a7e35549498f9d2 Mon Sep 17 00:00:00 2001 From: Oleksandr Zhurba Date: Mon, 13 Jul 2026 22:36:47 +0200 Subject: [PATCH] Add unix domain socket support for bind targets Adds a `.unixDomainSocket(path:)` bind target so the server can listen on a UNIX domain socket in addition to host and port. - Add `BindTarget.unixDomainSocket(path:)` and a matching `SocketAddress` case - Bind via `ServerBootstrap.bind(unixDomainSocketPath:)` for both plaintext and secure-upgrade channels, and report the bound path from `listeningAddresses` - Remove the socket file on shutdown; fail the bind if the path is already occupied so a stale socket is never silently reused - Support a `socketPath` key in swift-configuration, mutually exclusive with `host`/`port` Resolves swift-server/swift-http-server#69 --- .../NIOHTTPServer+SwiftConfiguration.swift | 26 +++++-- .../NIOHTTPServerConfiguration.swift | 15 +++++ .../NIOHTTPServerConfigurationError.swift | 4 ++ ...NIOHTTPServerSwiftConfigurationError.swift | 4 ++ .../NIOHTTPServer/NIOHTTPServer+HTTP1_1.swift | 27 +++++++- .../NIOHTTPServer+ListeningAddress.swift | 31 ++++++--- .../NIOHTTPServer+SecureUpgrade.swift | 24 ++++++- Sources/NIOHTTPServer/NIOHTTPServer.swift | 20 ++++++ Sources/NIOHTTPServer/SocketAddress.swift | 22 +++++- .../NIOHTTPServerTests/HTTPServerTests.swift | 67 +++++++++++++++++++ .../NIOHTTPServer+ServiceLifecycleTests.swift | 5 +- ...NIOHTTPServerSwiftConfigurationTests.swift | 32 +++++++++ .../NIOHTTPServerTests.swift | 4 +- Tests/NIOHTTPServerTests/TestError.swift | 1 + .../Utilities/NIOClient/NIOClient+HTTP1.swift | 11 ++- .../NIOClient/NIOClient+SecureUpgrade.swift | 13 +++- 16 files changed, 278 insertions(+), 28 deletions(-) diff --git a/Sources/NIOHTTPServer/Configuration/NIOHTTPServer+SwiftConfiguration.swift b/Sources/NIOHTTPServer/Configuration/NIOHTTPServer+SwiftConfiguration.swift index 807ef45..69f1263 100644 --- a/Sources/NIOHTTPServer/Configuration/NIOHTTPServer+SwiftConfiguration.swift +++ b/Sources/NIOHTTPServer/Configuration/NIOHTTPServer+SwiftConfiguration.swift @@ -93,7 +93,8 @@ extension NIOHTTPServerConfiguration { let bindTargetScope = snapshot.scoped(to: "bindTarget") let singularHost = bindTargetScope.string(forKey: "host") let singularPort = bindTargetScope.int(forKey: "port") - let hasSingular = singularHost != nil || singularPort != nil + let singularSocketPath = bindTargetScope.string(forKey: "socketPath") + let hasSingular = singularHost != nil || singularPort != nil || singularSocketPath != nil if hasSingular && hasPlural { throw NIOHTTPServerSwiftConfigurationError.singularAndPluralBindTargetsProvided @@ -117,17 +118,30 @@ extension NIOHTTPServerConfiguration.BindTarget { /// Initialize a bind target configuration from a config reader. /// /// ## Configuration keys: - /// - `host` (string, required): The hostname or IP address the server will bind to (e.g., "localhost", "0.0.0.0"). - /// - `port` (int, required): The port number the server will listen on (e.g., 8080, 443). + /// - `host` (string): The hostname or IP address to bind to. Required unless `socketPath` is given. + /// - `port` (int): The port to listen on. Required unless `socketPath` is given. + /// - `socketPath` (string): A unix domain socket path to bind to. Mutually exclusive with `host`/`port`. /// /// - Parameter config: The configuration reader. public init(config: ConfigSnapshotReader) throws { - self.init( - backing: .hostAndPort( + let host = config.string(forKey: "host") + let port = config.int(forKey: "port") + let socketPath = config.string(forKey: "socketPath") + + let backing: Backing + if let socketPath { + guard host == nil, port == nil else { + throw NIOHTTPServerSwiftConfigurationError.hostPortAndSocketPathProvided + } + backing = .unixDomainSocket(path: socketPath) + } else { + backing = .hostAndPort( host: try config.requiredString(forKey: "host"), port: try config.requiredInt(forKey: "port") ) - ) + } + + self.init(backing: backing) } } diff --git a/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfiguration.swift b/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfiguration.swift index 2f462e3..c97aed5 100644 --- a/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfiguration.swift +++ b/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfiguration.swift @@ -29,6 +29,7 @@ public struct NIOHTTPServerConfiguration: Sendable { public struct BindTarget: Sendable { enum Backing { case hostAndPort(host: String, port: Int) + case unixDomainSocket(path: String) } let backing: Backing @@ -47,8 +48,22 @@ public struct NIOHTTPServerConfiguration: Sendable { public static func hostAndPort(host: String, port: Int) -> Self { Self(backing: .hostAndPort(host: host, port: port)) } + + /// Creates a bind target for a unix domain socket. + /// + /// - Parameter path: The file system path to bind the unix domain socket to (e.g., "/tmp/server.sock") + /// - Returns: A configured `BindTarget` instance + /// + /// ## Example + /// ```swift + /// let target = BindTarget.unixDomainSocket(path: "/tmp/server.sock") + /// ``` + public static func unixDomainSocket(path: String) -> Self { + Self(backing: .unixDomainSocket(path: path)) + } } + /// Configuration for transport security settings. /// /// Provides options for running the server with or without TLS encryption. diff --git a/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfigurationError.swift b/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfigurationError.swift index 7f56f9c..4709d58 100644 --- a/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfigurationError.swift +++ b/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfigurationError.swift @@ -17,6 +17,7 @@ enum NIOHTTPServerConfigurationError: Error, CustomStringConvertible { case noSupportedHTTPVersionsSpecified case incompatibleTransportSecurity case noBindTargetsSpecified + case hostPortAndSocketPathProvided var description: String { switch self { @@ -28,6 +29,9 @@ enum NIOHTTPServerConfigurationError: Error, CustomStringConvertible { case .noBindTargetsSpecified: "Invalid configuration: at least one bind target must be specified." + + case .hostPortAndSocketPathProvided: + "Invalid configuration: a bind target has both 'host'/'port' and 'socketPath' set. Use either a host and port, or a unix domain socket path, not both." } } } diff --git a/Sources/NIOHTTPServer/Configuration/NIOHTTPServerSwiftConfigurationError.swift b/Sources/NIOHTTPServer/Configuration/NIOHTTPServerSwiftConfigurationError.swift index 6508d70..55a0673 100644 --- a/Sources/NIOHTTPServer/Configuration/NIOHTTPServerSwiftConfigurationError.swift +++ b/Sources/NIOHTTPServer/Configuration/NIOHTTPServerSwiftConfigurationError.swift @@ -21,6 +21,7 @@ enum NIOHTTPServerSwiftConfigurationError: Error, CustomStringConvertible { case trustRootsSourceAndVerificationCallbackMismatch case singularAndPluralBindTargetsProvided case bindTargetsHostsAndPortsLengthMismatch + case hostPortAndSocketPathProvided var description: String { switch self { @@ -38,6 +39,9 @@ enum NIOHTTPServerSwiftConfigurationError: Error, CustomStringConvertible { case .bindTargetsHostsAndPortsLengthMismatch: "Invalid configuration: 'bindTargets.hosts' and 'bindTargets.ports' must have the same number of elements." + + case .hostPortAndSocketPathProvided: + "Invalid configuration: a bind target has both 'host'/'port' and 'socketPath' set. Use either a host and port, or a unix domain socket path, not both." } } } diff --git a/Sources/NIOHTTPServer/NIOHTTPServer+HTTP1_1.swift b/Sources/NIOHTTPServer/NIOHTTPServer+HTTP1_1.swift index 3a419bf..468533d 100644 --- a/Sources/NIOHTTPServer/NIOHTTPServer+HTTP1_1.swift +++ b/Sources/NIOHTTPServer/NIOHTTPServer+HTTP1_1.swift @@ -84,10 +84,9 @@ extension NIOHTTPServer { do { for bindTarget in bindTargets { + let serverQuiescingHelper = ServerQuiescingHelper(group: self.eventLoopGroup) switch bindTarget.backing { case .hostAndPort(let host, let port): - let serverQuiescingHelper = ServerQuiescingHelper(group: self.eventLoopGroup) - let serverChannel = try await bootstrap.serverChannelInitializer { channel in channel.eventLoop.makeCompletedFuture { try channel.pipeline.syncOperations.addHandler( @@ -111,6 +110,30 @@ extension NIOHTTPServer { ) } serverChannels.append((serverChannel, serverQuiescingHelper)) + case .unixDomainSocket(let path): + let serverChannel = try await bootstrap.serverChannelInitializer { channel in + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandler( + serverQuiescingHelper.makeServerChannelHandler(channel: channel) + ) + + if let maxConnections = self.configuration.maxConnections { + try channel.pipeline.syncOperations.addHandler( + ConnectionLimitHandler(maxConnections: maxConnections) + ) + } + } + }.bind(unixDomainSocketPath: path) { channel in + self.setupHTTP1_1Connection( + channel: channel, + asyncChannelConfiguration: .init( + backPressureStrategy: .init(self.configuration.backpressureStrategy), + isOutboundHalfClosureEnabled: true + ), + isSecure: false + ) + } + serverChannels.append((serverChannel, serverQuiescingHelper)) } } } catch { diff --git a/Sources/NIOHTTPServer/NIOHTTPServer+ListeningAddress.swift b/Sources/NIOHTTPServer/NIOHTTPServer+ListeningAddress.swift index f3462c9..0fa9b91 100644 --- a/Sources/NIOHTTPServer/NIOHTTPServer+ListeningAddress.swift +++ b/Sources/NIOHTTPServer/NIOHTTPServer+ListeningAddress.swift @@ -20,6 +20,7 @@ enum ListeningAddressError: CustomStringConvertible, Error { case addressOrPortNotAvailable case unsupportedAddressType case serverClosed + case pathnameNotAvailable var description: String { switch self { @@ -31,6 +32,8 @@ enum ListeningAddressError: CustomStringConvertible, Error { return """ There is no listening address bound for this server: there may have been an error which caused the server to close, or it may have shut down. """ + case .pathnameNotAvailable: + return "Unable to retrieve the unix domain socket path from the underlying socket" } } } @@ -134,17 +137,27 @@ extension NIOHTTPServer { @available(anyAppleOS 26.0, *) extension NIOHTTPServer.SocketAddress { fileprivate init(_ address: NIOCore.SocketAddress?) throws(ListeningAddressError) { - guard let address, let port = address.port else { - throw ListeningAddressError.addressOrPortNotAvailable + guard let address else { + throw .addressOrPortNotAvailable } - switch address { - case .v4(let ipv4Address): - self.init(base: .ipv4(.init(host: ipv4Address.host, port: port))) - case .v6(let ipv6Address): - self.init(base: .ipv6(.init(host: ipv6Address.host, port: port))) - case .unixDomainSocket: - throw ListeningAddressError.unsupportedAddressType + let base: Base = switch (address, address.port, address.pathname) { + case (.v4(let ipv4Address), .some(let port), _): + .ipv4(.init(host: ipv4Address.host, port: port)) + + case (.v6(let ipv6Address), .some(let port), _): + .ipv6(.init(host: ipv6Address.host, port: port)) + + case (.unixDomainSocket, _, .some(let path)): + .unixDomainSocket(path: path) + + case (.v4, .none, _), (.v6, .none, _): + throw .addressOrPortNotAvailable + + case (.unixDomainSocket, _, .none): + throw .pathnameNotAvailable } + + self.init(base: base) } } diff --git a/Sources/NIOHTTPServer/NIOHTTPServer+SecureUpgrade.swift b/Sources/NIOHTTPServer/NIOHTTPServer+SecureUpgrade.swift index 6583fa7..b94c012 100644 --- a/Sources/NIOHTTPServer/NIOHTTPServer+SecureUpgrade.swift +++ b/Sources/NIOHTTPServer/NIOHTTPServer+SecureUpgrade.swift @@ -201,10 +201,9 @@ extension NIOHTTPServer { var serverChannels = [(NIOAsyncChannel, Never>, ServerQuiescingHelper)]() do { for bindTarget in bindTargets { + let serverQuiescingHelper = ServerQuiescingHelper(group: self.eventLoopGroup) switch bindTarget.backing { case .hostAndPort(let host, let port): - let serverQuiescingHelper = ServerQuiescingHelper(group: self.eventLoopGroup) - let serverChannel = try await bootstrap.serverChannelInitializer { channel in channel.eventLoop.makeCompletedFuture { try channel.pipeline.syncOperations.addHandler( @@ -225,6 +224,27 @@ extension NIOHTTPServer { ) } serverChannels.append((serverChannel, serverQuiescingHelper)) + case .unixDomainSocket(let path): + let serverChannel = try await bootstrap.serverChannelInitializer { channel in + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandler( + serverQuiescingHelper.makeServerChannelHandler(channel: channel) + ) + + if let maxConnections = self.configuration.maxConnections { + try channel.pipeline.syncOperations.addHandler( + ConnectionLimitHandler(maxConnections: maxConnections) + ) + } + } + }.bind(unixDomainSocketPath: path) { channel in + self.setupSecureUpgradeConnectionChildChannel( + channel: channel, + supportedHTTPVersions: supportedHTTPVersions, + sslContext: sslContext + ) + } + serverChannels.append((serverChannel, serverQuiescingHelper)) } } } catch { diff --git a/Sources/NIOHTTPServer/NIOHTTPServer.swift b/Sources/NIOHTTPServer/NIOHTTPServer.swift index 890c6d9..6d52d36 100644 --- a/Sources/NIOHTTPServer/NIOHTTPServer.swift +++ b/Sources/NIOHTTPServer/NIOHTTPServer.swift @@ -145,6 +145,10 @@ public struct NIOHTTPServer: HTTPServer { let serverChannels = try await self.makeServerChannels() + // Remove the socket files for any UDS bind targets so their paths are freed for the next run. + // Registered only after all binds succeeded, so every path is one we created. + defer { await self.removeUNIXDomainSocketFiles() } + return try await withTaskCancellationHandler { try await withGracefulShutdownHandler { try await self._serve(serverChannels: serverChannels, handler: handler) @@ -330,6 +334,22 @@ public struct NIOHTTPServer: HTTPServer { } } + /// Removes the socket files backing any unix-domain-socket bind targets. + private func removeUNIXDomainSocketFiles() async { + let fileIO = NonBlockingFileIO(threadPool: .singleton) + for bindTarget in self.configuration.bindTargets { + guard case .unixDomainSocket(let path) = bindTarget.backing else { continue } + do { + try await fileIO.unlink(path: path) + } catch { + self.logger.debug( + "Failed to remove unix domain socket file", + metadata: ["path": "\(path)", "error": "\(error)"] + ) + } + } + } + } @available(anyAppleOS 26.0, *) diff --git a/Sources/NIOHTTPServer/SocketAddress.swift b/Sources/NIOHTTPServer/SocketAddress.swift index 3e2384d..d75177a 100644 --- a/Sources/NIOHTTPServer/SocketAddress.swift +++ b/Sources/NIOHTTPServer/SocketAddress.swift @@ -55,6 +55,7 @@ extension NIOHTTPServer { enum Base: Hashable, Sendable { case ipv4(IPv4) case ipv6(IPv6) + case unixDomainSocket(path: String) } let base: Base @@ -88,23 +89,38 @@ extension NIOHTTPServer { } /// The ``SocketAddress``'s host. - public var host: String { + public var host: String? { switch self.base { case .ipv4(let ipv4): return ipv4.host case .ipv6(let ipv6): return ipv6.host + case .unixDomainSocket(_): + return nil } } /// The ``SocketAddress``'s port. - public var port: Int { + public var port: Int? { switch self.base { case .ipv4(let ipv4): return ipv4.port - case .ipv6(let ipv6): return ipv6.port + case .unixDomainSocket(_): + return nil + } + } + + /// The ``SocketAddress``'s unix domain socket path. + public var unixDomainSocketPath: String? { + switch self.base { + case .ipv4(_): + return nil + case .ipv6(_): + return nil + case .unixDomainSocket(let path): + return path } } } diff --git a/Tests/NIOHTTPServerTests/HTTPServerTests.swift b/Tests/NIOHTTPServerTests/HTTPServerTests.swift index 1939375..372e7db 100644 --- a/Tests/NIOHTTPServerTests/HTTPServerTests.swift +++ b/Tests/NIOHTTPServerTests/HTTPServerTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import BasicContainers +import Foundation import Logging import NIOHTTPServer import Testing @@ -60,4 +61,70 @@ struct HTTPServerTests { group.cancelAll() } } + + @Test("Unix domain socket file is removed on shutdown") + @available(anyAppleOS 26.0, *) + func testUnixDomainSocketFileRemovedOnShutdown() async throws { + // Keep the path short so it stays under the platform's `sun_path` limit (104 on Darwin, 108 on Linux), + // even on CI where the system temporary directory can be deep. + let socketPath = "/tmp/nio-http-server-uds-\(UUID().uuidString).sock" + // Guard against a leftover file from a previously crashed run, and clean up if this test fails early. + try? FileManager.default.removeItem(atPath: socketPath) + defer { try? FileManager.default.removeItem(atPath: socketPath) } + + let server = NIOHTTPServer( + logger: Logger(label: "Test"), + configuration: try .init( + bindTarget: .unixDomainSocket(path: socketPath), + supportedHTTPVersions: [.http1_1], + transportSecurity: .plaintext + ) + ) + + try await withThrowingTaskGroup { group in + group.addTask { + try await server.serve { request, context, reader, responseSender in + let responseWriter = try await responseSender.send(HTTPResponse(status: .ok)) + var buffer = UniqueArray() + try await responseWriter.finish(buffer: &buffer, finalElement: nil) + } + } + + // Wait until the server is bound; the socket file must exist while it is listening. + _ = try await server.listeningAddresses + #expect(FileManager.default.fileExists(atPath: socketPath)) + + // Shutting the server down should release the socket by removing its file. + group.cancelAll() + } + + // The task group only returns once `serve` has fully unwound, including its cleanup `defer`. + #expect(!FileManager.default.fileExists(atPath: socketPath)) + } + + @Test("Bind fails when the unix domain socket path is already occupied") + @available(anyAppleOS 26.0, *) + func testUnixDomainSocketBindFailsWhenPathExists() async throws { + let socketPath = "/tmp/nio-http-server-uds-\(UUID().uuidString).sock" + // Simulate a leftover/occupied socket by pre-creating a file at the path. + #expect(FileManager.default.createFile(atPath: socketPath, contents: nil)) + defer { try? FileManager.default.removeItem(atPath: socketPath) } + + let server = NIOHTTPServer( + logger: Logger(label: "Test"), + configuration: try .init( + bindTarget: .unixDomainSocket(path: socketPath), + supportedHTTPVersions: [.http1_1], + transportSecurity: .plaintext + ) + ) + + // Binding to an occupied path must fail rather than silently reusing or removing the file. + await #expect(throws: Error.self) { + try await server.serve { _, _, _, _ in } + } + + // The pre-existing file must be left untouched: cleanup only runs for sockets we bound ourselves. + #expect(FileManager.default.fileExists(atPath: socketPath)) + } } diff --git a/Tests/NIOHTTPServerTests/NIOHTTPServer+ServiceLifecycleTests.swift b/Tests/NIOHTTPServerTests/NIOHTTPServer+ServiceLifecycleTests.swift index 5812317..4760b7a 100644 --- a/Tests/NIOHTTPServerTests/NIOHTTPServer+ServiceLifecycleTests.swift +++ b/Tests/NIOHTTPServerTests/NIOHTTPServer+ServiceLifecycleTests.swift @@ -165,7 +165,10 @@ struct NIOHTTPServiceLifecycleTests { let (clientConnectionChannel, alpnResultFuture) = try await ClientBootstrap(group: .singletonMultiThreadedEventLoopGroup).connect( - to: try .init(ipAddress: serverAddress.host, port: serverAddress.port) + to: try .init( + ipAddress: try #require(serverAddress.host), + port: try #require(serverAddress.port) + ) ) { socketChannel in socketChannel.configureTestClientSSLPipeline(tlsConfig: tlsConfig).flatMap { socketChannel.configureTestSecureUpgradeClientPipeline().map { connectionChannel in diff --git a/Tests/NIOHTTPServerTests/NIOHTTPServerSwiftConfigurationTests.swift b/Tests/NIOHTTPServerTests/NIOHTTPServerSwiftConfigurationTests.swift index 904f3b1..d015c67 100644 --- a/Tests/NIOHTTPServerTests/NIOHTTPServerSwiftConfigurationTests.swift +++ b/Tests/NIOHTTPServerTests/NIOHTTPServerSwiftConfigurationTests.swift @@ -41,6 +41,8 @@ struct NIOHTTPServerSwiftConfigurationTests { case .hostAndPort(let host, let port): #expect(host == "localhost") #expect(port == 8080) + case .unixDomainSocket(let path): + Issue.record("Expected first bind target to be host/port, got unix domain socket path: \(path)") } } @@ -71,6 +73,36 @@ struct NIOHTTPServerSwiftConfigurationTests { #expect("Missing required config value for key: port." == "\(configError)") } + + @Test("Valid unix domain socket path") + @available(anyAppleOS 26.0, *) + func testValidUnixDomainSocketConfig() throws { + let provider = InMemoryProvider(values: ["socketPath": "/tmp/test.sock"]) + + let config = ConfigReader(provider: provider) + let snapshot = config.snapshot() + + let bindTarget = try NIOHTTPServerConfiguration.BindTarget(config: snapshot) + + switch bindTarget.backing { + case .unixDomainSocket(let path): + #expect(path == "/tmp/test.sock") + case .hostAndPort(let host, let port): + Issue.record("Expected a unix domain socket bind target, got host \(host) and port \(port) instead.") + } + } + + @Test("Init fails when both socketPath and host/port are provided") + @available(anyAppleOS 26.0, *) + func testSocketPathAndHostPortThrows() throws { + let provider = InMemoryProvider(values: ["socketPath": "/tmp/test.sock", "host": "localhost", "port": 8080]) + let config = ConfigReader(provider: provider) + let snapshot = config.snapshot() + + #expect(throws: NIOHTTPServerSwiftConfigurationError.hostPortAndSocketPathProvided) { + try NIOHTTPServerConfiguration.BindTarget(config: snapshot) + } + } } @Suite("Multiple bind targets via bindTargets") diff --git a/Tests/NIOHTTPServerTests/NIOHTTPServerTests.swift b/Tests/NIOHTTPServerTests/NIOHTTPServerTests.swift index 47a03fe..e13ac94 100644 --- a/Tests/NIOHTTPServerTests/NIOHTTPServerTests.swift +++ b/Tests/NIOHTTPServerTests/NIOHTTPServerTests.swift @@ -627,7 +627,9 @@ struct NIOHTTPServerTests { serverHandler: HTTPServerClosureRequestHandler { _, _, _, _ in }, body: { (addresses: [NIOHTTPServer.SocketAddress]) in #expect(addresses.count == 2) - #expect(addresses[0].port != addresses[1].port) + let firstPort = try #require(addresses[0].port) + let secondPort = try #require(addresses[1].port) + #expect(firstPort != secondPort) } ) } diff --git a/Tests/NIOHTTPServerTests/TestError.swift b/Tests/NIOHTTPServerTests/TestError.swift index 0874e0e..05286e9 100644 --- a/Tests/NIOHTTPServerTests/TestError.swift +++ b/Tests/NIOHTTPServerTests/TestError.swift @@ -16,4 +16,5 @@ enum TestError: Error { case errorWhileReading case errorWhileWriting + case unsupportedAddress } diff --git a/Tests/NIOHTTPServerTests/Utilities/NIOClient/NIOClient+HTTP1.swift b/Tests/NIOHTTPServerTests/Utilities/NIOClient/NIOClient+HTTP1.swift index a4dadf7..2ced0d4 100644 --- a/Tests/NIOHTTPServerTests/Utilities/NIOClient/NIOClient+HTTP1.swift +++ b/Tests/NIOHTTPServerTests/Utilities/NIOClient/NIOClient+HTTP1.swift @@ -56,7 +56,16 @@ extension ClientBootstrap { func connectToTestHTTP1Server( at serverAddress: NIOHTTPServer.SocketAddress ) async throws -> NIOAsyncChannel { - try await self.connect(to: try .init(ipAddress: serverAddress.host, port: serverAddress.port)) { channel in + let target: NIOCore.SocketAddress + if let path = serverAddress.unixDomainSocketPath { + target = try NIOCore.SocketAddress(unixDomainSocketPath: path) + } else if let host = serverAddress.host, let port = serverAddress.port { + target = try NIOCore.SocketAddress(ipAddress: host, port: port) + } else { + throw TestError.unsupportedAddress + } + + return try await self.connect(to: target) { channel in channel.configureTestHTTP1ClientPipeline() } } diff --git a/Tests/NIOHTTPServerTests/Utilities/NIOClient/NIOClient+SecureUpgrade.swift b/Tests/NIOHTTPServerTests/Utilities/NIOClient/NIOClient+SecureUpgrade.swift index 63c34a2..afe3d64 100644 --- a/Tests/NIOHTTPServerTests/Utilities/NIOClient/NIOClient+SecureUpgrade.swift +++ b/Tests/NIOHTTPServerTests/Utilities/NIOClient/NIOClient+SecureUpgrade.swift @@ -60,9 +60,16 @@ extension ClientBootstrap { at serverAddress: NIOHTTPServer.SocketAddress, tlsConfig: TLSConfiguration ) async throws -> NegotiatedClientConnection { - let clientNegotiatedChannel = try await self.connect( - to: try .init(ipAddress: serverAddress.host, port: serverAddress.port) - ) { channel in + let target: NIOCore.SocketAddress + if let path = serverAddress.unixDomainSocketPath { + target = try NIOCore.SocketAddress(unixDomainSocketPath: path) + } else if let host = serverAddress.host, let port = serverAddress.port { + target = try NIOCore.SocketAddress(ipAddress: host, port: port) + } else { + throw TestError.unsupportedAddress + } + + let clientNegotiatedChannel = try await self.connect(to: target) { channel in channel.configureTestClientSSLPipeline(tlsConfig: tlsConfig).flatMap { channel.configureTestSecureUpgradeClientPipeline() }