Skip to content

Add unix domain socket support for bind targets#96

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

Add unix domain socket support for bind targets#96
mob-connection wants to merge 5 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. Serving over a UNIX domain socket is a common requirement (e.g. a reverse proxy talking to the server over a local socket), so this PR adds a UDS bind target.

Modifications

• Added BindTarget.unixDomainSocket(path:) (typed as System.FilePath) and a matching SocketAddress case; the path is converted to String only at the NIO edge (bind(unixDomainSocketPath:) and fileIO.unlink).
• Bind over a UNIX domain socket for both plaintext (HTTP/1.1) and secure-upgrade (HTTP/1.1 + HTTP/2) 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.
• Reject UNIX domain socket bind targets when HTTP/3 is enabled (HTTP/3 runs over QUIC/UDP), with a dedicated configuration error.
• Added tests for the listening-address mapping and the HTTP/3 rejection.

Result

NIOHTTPServer can now listen on a UNIX domain socket over HTTP/1.1 and HTTP/2. Resolves #69.

@mob-connection

Copy link
Copy Markdown
Author

For #69 I used a struct-backed NIOHTTPServer.SocketAddress with an added
.unixDomainSocket(path:) case, so it stays extensible (e.g. vsock later)
without a source-breaking change.

Do you think it makes sense to expose it as a plain enum instead ?

/// ```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.
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

3 participants