Skip to content

Commit

Permalink
Add support for streaming on recent Darwin platforms (#24)
Browse files Browse the repository at this point in the history
### Motivation

Recently, the `ClientTransport` protocol was updated to be in terms of
`HTTPBody`, which is an `AsyncSequence<ArraySlice<UInt8>>`. Until now,
the URLSession transport has buffered the request and response bodies in
memory. This PR seeks to bring streaming support to recent Darwin
platforms.

### Modifications

On new enough Darwin platforms[^1], use URLSession delegate to perform
bidirectional streaming with backpressure. Request body backpressure is
provided by the use of `StreamDelegate` with a bound stream pair.
Response body backpressure is provided by the use of
`AsyncBackpressuredStream` which allows for a high and low watermark to
suspend and resume the URLSession task.

[^1]: macOS 12, iOS 15, tvOS 15, watchOS 8

In more detail:

- Vendor internal `AsyncBackpressuredStream` implementation from
SE-0406.
- Add `HTTPBodyOutputStreamBridge` which provides the
`OutputStreamDelegate` required to bridge the `AsyncSequence`-based
`HTTPBody` to an `OutputStream`.
- Add `BidirectionalStreamingURLSessionDelegate` which makes use of
`HTTPBodyOutputStreamBridge` for streaming request bodies and also uses
the delegate callbacks to bridge the response body to `HTTPBody`.
- Add `URLSession.bidirectionalStreamingRequest(...) async throws`,
which provides a high-level URLSession-like API for setting up the
appropriate URLSession task and delegate.
- Add `URLSession.bufferedRequest(...) async throws` to provide a
similar interfaces on platforms that don't support the streaming APIs on
which we depend.
- Add internal `enum URLSessionTransport.Configuration.Implementation`
to control whether to use buffered or streaming implementation.
- Detect appropriate implementation depending on the platform.

### Result

- On new enough Darwin platforms[^1], streaming will be used.

### Test Plan

- Add a set of tests that run with both buffered and streaming
implementation on platforms that support streaming.
- Add a set of tests that only run on platforms that support streaming,
to test request flows only possible with streaming.

However, it's worth noting that our CI only runs on Linux, so we won't
be testing the majority of this new feature in CI.
  • Loading branch information
simonjbeaumont authored Nov 17, 2023
1 parent 70b63ef commit 686df72
Show file tree
Hide file tree
Showing 20 changed files with 3,914 additions and 184 deletions.
18 changes: 18 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,21 @@ This product contains derivations of various scripts from SwiftNIO.
* https://www.apache.org/licenses/LICENSE-2.0
* HOMEPAGE:
* https://github.com/apple/swift-nio

-------------------------------------------------------------------------------

This product contains AsyncSequence implementations from Swift Async Algorithms.

* LICENSE (Apache License 2.0):
* https://github.com/apple/swift-async-algorithms/blob/main/LICENSE.txt
* HOMEPAGE:
* https://github.com/apple/swift-async-algorithms

-------------------------------------------------------------------------------

This product contains AsyncSequence implementations from Swift.

* LICENSE (Apache License 2.0):
* https://github.com/apple/swift/blob/main/LICENSE.txt
* HOMEPAGE:
* https://github.com/apple/swift
20 changes: 19 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ swiftSettings.append(
// Require `any` for existential types.
.enableUpcomingFeature("ExistentialAny")
)

// Strict concurrency is enabled in CI; use this environment variable to enable it locally.
if ProcessInfo.processInfo.environment["SWIFT_OPENAPI_STRICT_CONCURRENCY"].flatMap(Bool.init) ?? false {
swiftSettings.append(contentsOf: [
.define("SWIFT_OPENAPI_STRICT_CONCURRENCY"), .enableExperimentalFeature("StrictConcurrency"),
])
}
#endif


let package = Package(
name: "swift-openapi-urlsession",
platforms: [
Expand All @@ -40,19 +48,29 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.0")),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-collections", from: "1.0.0"),
],
targets: [
.target(
name: "OpenAPIURLSession",
dependencies: [
.product(name: "DequeModule", package: "swift-collections"),
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
],
swiftSettings: swiftSettings
),
.testTarget(
name: "OpenAPIURLSessionTests",
dependencies: ["OpenAPIURLSession"],
dependencies: [
"OpenAPIURLSession",
.product(name: "NIOTestUtils", package: "swift-nio"),
],
swiftSettings: swiftSettings
),
]
)

// Test-only dependencies.
package.dependencies += [
.package(url: "https://github.com/apple/swift-nio", from: "2.62.0")
]
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ A client transport that uses the [URLSession](https://developer.apple.com/docume
Use the transport with client code generated by [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator).

## Supported platforms and minimum versions
| macOS | Linux | iOS | tvOS | watchOS |
| :-: | :-: | :-: | :-: | :-: |
| ✅ 10.15+ || ✅ 13+ | ✅ 13+ | ✅ 6+ |

| macOS | Linux | iOS | tvOS | watchOS |
| :-: | :-: | :-: | :-: | :-: |
| ✅ 10.15+ || ✅ 13+ | ✅ 13+ | ✅ 6+ |

Note: Streaming support only available on macOS 12+, iOS 15+, tvOS 15+, and
watchOS 8+.For streaming support on Linux, please use the [AsyncHTTPClient
Transport](https://github.com/swift-server/swift-openapi-async-http-client)

## Usage

Expand Down
Loading

0 comments on commit 686df72

Please sign in to comment.