Skip to content

Add unix domain socket support for bind targets - #96

Open
mob-connection wants to merge 12 commits into
swift-server:mainfrom
mob-connection:uds-bind-target
Open

mob-connection wants to merge 12 commits into
swift-server:mainfrom
mob-connection:uds-bind-target

Conversation

@mob-connection

@mob-connection mob-connection commented Jul 13, 2026 •

Copy link
Copy Markdown

Motivation

NIOHTTPServer could only bind to a host and port, but serving over a UNIX domain socket is a common requirement (e.g. a reverse proxy over a local socket). Resolves #69

Modifications

  • Added BindTarget.unixDomainSocket(path:) and a matching SocketAddress case, bound through the existing listener path so HTTP/1.1 and HTTP/2 need no UDS-specific handling
  • SocketAddress.host / .port are now optional (source-breaking): a UDS address has neither
  • An occupied path fails the bind; closing the socket frees it
  • Added a socketPath key to swift-configuration and a configuration error for UDS with HTTP/3
  • Added tests, including request-response over a UDS across HTTP/1.1 and HTTP/2

Result

NIOHTTPServer can listen on a UNIX domain socket over HTTP/1.1 and HTTP/2, and reports the bound path from listeningAddresses

/// ```swift
/// let target = BindTarget.unixDomainSocket(path: "/tmp/server.sock")
/// ```
public static func unixDomainSocket(path: String) -> Self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably use the new FilePath type once it becomes available

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably use the new FilePath type once it becomes available

In the UDS bind support the socket path has two different types depending on direction: on input we take a FilePath (BindTarget.unixDomainSocket(path:)), but on output we expose it as String (SocketAddress.unixDomainSocketPath, mirroring NIOCore.SocketAddress.pathname). If we commit to FilePath, I'd lean toward using it on output too for consistency — but keeping String also makes sense since that's just what NIO reports back. Which way should we go?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think ignore what NIO does and keep the API what looks the best in Swift, in this case FilePaths for all types that represent instead of a String

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#69
Motivation:

The UDS bind path was typed as a plain String. A System.FilePath gives
it a strongly-typed, path-aware API at the configuration boundary.

Modifications:

- BindTarget.unixDomainSocket(path:) and its backing now take a FilePath
- Convert to String only at the NIO edge (bind(unixDomainSocketPath:)
  and fileIO.unlink), where NIO still requires a String
- public import System where FilePath appears in public API; plain
  import elsewhere
- Update UDS tests to pass a FilePath

Result:

The UDS bind path is strongly typed; string literals still work via
ExpressibleByStringLiteral, so call sites are unchanged.
Motivation:

SocketAddress.init(_:) folded address, port and pathname lookups into a
single tuple switch with coarse error cases, making failure reasons
ambiguous and leaving the mapping untested.

Modifications:

- Replace addressOrPortNotAvailable/unsupportedAddressType with the more
  precise addressNotAvailable and portNotAvailable cases
- Rebuild init(_:) around per-field throwing accessors and an exhaustive
  switch over the address kind
- Switch the NIOClient test helper over SocketAddress.base instead of the
  optional host/port/path accessors
- Add SocketAddressTests covering the IPv4, IPv6, UDS and nil mappings

Result:

Listening-address construction has precise error reasons and unit-test
coverage; behaviour is unchanged.
Switch connectToTestSecureUpgradeHTTPServer over SocketAddress.base like
the HTTP/1.1 helper, and drop the now-unused TestError.unsupportedAddress.
@mob-connection
mob-connection marked this pull request as ready for review July 18, 2026 13:49

@0xTim 0xTim left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking mostly good, I think we should add H2 support as well though

… HTTP/3

Merges swift-server#101 and rejects unix domain socket bind targets when HTTP/3 is enabled, since HTTP/3 runs over QUIC/UDP.
Comment thread Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfiguration.swift Outdated
)
}
serverChannels.append((serverChannel, serverQuiescingHelper))
case .unixDomainSocket(let path):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid duplication with the case above, we should define an extension on ServerBootstrap's bind method that accepts a NIOHTTPServerConfiguration.BindTarget. That method should switch over the bind target and call into either the host and port variant or the UDS variant of ServerBootstrap.bind.

)
}
serverChannels.append((serverChannel, serverQuiescingHelper))
case .unixDomainSocket(let path):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. You will be able to use the extension here too.

Resolve test-client conflicts, fix HTTP/3 test client for optional SocketAddress host/port, and dedup ServerBootstrap.bind via a BindTarget-aware helper.
NIO unlinks the socket path when the listening socket closes, so our own unlink always ran too late: it logged a spurious failure and could delete a path another process had since bound

Added a request-response test over a UDS, parameterised over plaintext HTTP/1.1, HTTP/1.1 and HTTP/2
`import System` is Darwin-only; depend on swift-system and import SystemPackage so Linux builds. Also enforce the UDS/HTTP/3 rule when `bindTargets` or `supportedHTTPVersions` is set after creation, not only in init.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support binding to a unix socket

4 participants