Skip to content

Add network path/interface restriction fields to HTTPClient.Configuration - #915

Closed
o-nnerb wants to merge 1 commit into
swift-server:mainfrom
request-dl:network-path-interface-restrictions
Closed

Add network path/interface restriction fields to HTTPClient.Configuration#915
o-nnerb wants to merge 1 commit into
swift-server:mainfrom
request-dl:network-path-interface-restrictions

Conversation

@o-nnerb

@o-nnerb o-nnerb commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Motivation

URLSessionConfiguration exposes allowsCellularAccess, allowsExpensiveNetworkAccess, and allowsConstrainedNetworkAccess. Clients migrating from URLSession, or apps that need to respect a user's Low Data Mode / cellular preferences, expect parity. HTTPClient.Configuration has no equivalent today — and critically, no library built on top of AsyncHTTPClient can add this either, since the only place these NWParameters constraints can be applied is at connection-establishment time, inside HTTPConnectionPool+Factory.swift, which is internal to this package. There is no generic escape hatch for NWParameters customization from the outside.

networkFrameworkWaitForConnectivity is the existing precedent for exactly this kind of field: declared unconditionally (no #if canImport(Network) on the declaration, it's simply inert elsewhere), and consumed only inside the two #if canImport(Network)-guarded bootstrap factories.

Modifications

  • Adds HTTPClient.Configuration.NetworkInterfaceType, a public struct mirroring NWInterface.InterfaceType's cases (.other, .wifi, .cellular, .wiredEthernet, .loopback) without requiring Network to be imported at the declaration site — following the same Backing-enum pattern DNSResolver/HTTPVersion already use in this file, so the public type stays frozen/ABI-stable while the backing can evolve.

  • Adds five new Configuration fields:

    • prohibitedInterfaceTypes: Set<NetworkInterfaceType> — mirrors NWParameters.prohibitedInterfaceTypes.
    • requiredInterfaceType: NetworkInterfaceType? — mirrors NWParameters.requiredInterfaceType, exposed as Optional instead of that property's non-optional .other-as-"unrestricted"-sentinel default, since .other is otherwise both a real interface category and the "don't care" default, which is easy to misread.
    • allowsExpensiveNetworkAccess / allowsConstrainedNetworkAccess: Bool — inverted mirrors of NWParameters.prohibitExpensivePaths / .prohibitConstrainedPaths.
    • allowsUltraConstrainedPaths: Bool — direct mirror of NWParameters.allowUltraConstrainedPaths (available macOS/iOS/watchOS/tvOS/visionOS 26.0+; a no-op on older OS versions, not just non-Darwin platforms).

    All defaults match NWParameters' own defaults, confirmed both against the Network.swiftinterface shipped in Xcode 26.5 and empirically against a live NWParameters instance.

  • Wires all five into both makePlainBootstrap and makeTLSBootstrap via one new configureNWParameters(_:localAddress:) helper. NIOTSConnectionBootstrap.configureNWParameters(_:) only retains the most recently passed closure rather than composing across calls, so the existing requiredLocalEndpoint local-address-binding closure and the new field wiring are folded into a single closure — two separate configureNWParameters { } calls would have silently dropped the local-address binding.

  • Adds two tests to HTTPClientNIOTSTests.swift: one exercising all five fields together with localAddress set (regression coverage for the closure-composition issue above), one confirming .other as an explicit requiredInterfaceType behaves the same as the nil/unrestricted default.

Result

HTTPClient.Configuration can restrict connections by interface category and by expensive/constrained/ultra-constrained network path, matching what URLSessionConfiguration already offers, on Darwin platforms using Network.framework as the transport. No-op everywhere else (non-Darwin platforms, and for allowsUltraConstrainedPaths, older OS versions), same as the existing networkFrameworkWaitForConnectivity field.

🤖 Generated with Claude Code

…tion

Motivation:

URLSessionConfiguration exposes allowsCellularAccess, allowsExpensiveNetworkAccess,
and allowsConstrainedNetworkAccess. Clients migrating from URLSession, or apps that
need to respect a user's Low Data Mode / cellular preferences, expect parity.
HTTPClient.Configuration has no equivalent today, and no library built on top of
AsyncHTTPClient can add this either: the only place these NWParameters constraints
can be applied is at connection-establishment time, inside
HTTPConnectionPool+Factory.swift, which is internal to AsyncHTTPClient. There is no
generic escape hatch for NWParameters customization from outside the package.

Modifications:

- Add HTTPClient.Configuration.NetworkInterfaceType, a public struct mirroring
  NWInterface.InterfaceType's cases (.other, .wifi, .cellular, .wiredEthernet,
  .loopback) without requiring Network to be imported at the declaration site,
  following the same Backing-enum pattern already used by DNSResolver/HTTPVersion.
- Add five new Configuration fields: prohibitedInterfaceTypes (mirrors
  NWParameters.prohibitedInterfaceTypes), requiredInterfaceType (mirrors
  NWParameters.requiredInterfaceType, exposed as Optional instead of that
  property's non-optional .other-as-sentinel default), allowsExpensiveNetworkAccess
  and allowsConstrainedNetworkAccess (inverted mirrors of
  NWParameters.prohibitExpensivePaths / .prohibitConstrainedPaths), and
  allowsUltraConstrainedPaths (direct mirror of the macOS/iOS/tvOS/watchOS/visionOS
  26.0+ NWParameters.allowUltraConstrainedPaths). All default to NWParameters' own
  defaults, verified empirically against a live NWParameters instance.
- Wire all five into both makePlainBootstrap and makeTLSBootstrap in
  HTTPConnectionPool+Factory.swift via a single new configureNWParameters(_:localAddress:)
  helper. NIOTSConnectionBootstrap.configureNWParameters(_:) only keeps the most
  recently passed closure rather than composing across calls, so the existing
  requiredLocalEndpoint-binding closure and the new field wiring had to be folded
  into one closure instead of two separate configureNWParameters { } calls, which
  would have silently dropped local-address binding.
- Add two tests in HTTPClientNIOTSTests.swift: one exercising all five fields
  together with localAddress (regression coverage for the closure-composition
  issue above), one confirming .other as an explicit requiredInterfaceType behaves
  like the nil/unrestricted default.

Result:

HTTPClient.Configuration can now restrict connections by interface category and
by expensive/constrained/ultra-constrained network path, matching what
URLSessionConfiguration already offers, on Darwin platforms using Network.framework
as the transport. This is a no-op everywhere else (non-Darwin platforms, and for
allowsUltraConstrainedPaths, older OS versions), same as the existing
networkFrameworkWaitForConnectivity field.
@fabianfett

Copy link
Copy Markdown
Member

We highly recommend using URLSession or Network.framework on Apple platforms if those features are required.

Clients migrating from URLSession, or apps that need to respect a user's Low Data Mode / cellular preferences, expect parity.

For libraries that want to abstract over the underlying HTTP transport, we recommend looking at the new abstract HTTPClient protocol:

https://forums.swift.org/t/designing-an-http-client-api-for-swift/85254
https://github.com/apple/swift-http-api-proposal/

If you have a specific use-case in mind, please open up this PR and state your use-case.

@fabianfett fabianfett closed this Aug 20, 2026
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.

2 participants