Skip to content

Commit 1f146a4

Browse files
committed
Make HTTPClientResponse.init public
1 parent b57bcb9 commit 1f146a4

8 files changed

+264
-106
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@usableFromInline
16+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
17+
struct AnyAsyncSequence<Element>: Sendable, AsyncSequence {
18+
@usableFromInline typealias AsyncIteratorNextCallback = () async throws -> Element?
19+
20+
@usableFromInline struct AsyncIterator: AsyncIteratorProtocol {
21+
@usableFromInline let nextCallback: AsyncIteratorNextCallback
22+
23+
@inlinable init(nextCallback: @escaping AsyncIteratorNextCallback) {
24+
self.nextCallback = nextCallback
25+
}
26+
27+
@inlinable mutating func next() async throws -> Element? {
28+
try await nextCallback()
29+
}
30+
}
31+
32+
@usableFromInline var makeAsyncIteratorCallback: @Sendable () -> AsyncIteratorNextCallback
33+
34+
@inlinable public init<SequenceOfBytes>(
35+
_ asyncSequence: SequenceOfBytes
36+
) where SequenceOfBytes: AsyncSequence & Sendable, SequenceOfBytes.Element == Element {
37+
self.makeAsyncIteratorCallback = {
38+
var iterator = asyncSequence.makeAsyncIterator()
39+
return {
40+
try await iterator.next()
41+
}
42+
}
43+
}
44+
45+
@inlinable func makeAsyncIterator() -> AsyncIterator {
46+
.init(nextCallback: makeAsyncIteratorCallback())
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
16+
@usableFromInline
17+
struct AsyncSequenceFromSyncSequence<Wrapped: Sequence & Sendable>: AsyncSequence, Sendable {
18+
@usableFromInline typealias Element = Wrapped.Element
19+
@usableFromInline struct AsyncIterator: AsyncIteratorProtocol {
20+
@usableFromInline var iterator: Wrapped.Iterator
21+
@inlinable init(iterator: Wrapped.Iterator) {
22+
self.iterator = iterator
23+
}
24+
@inlinable mutating func next() async throws -> Wrapped.Element? {
25+
self.iterator.next()
26+
}
27+
}
28+
29+
@usableFromInline var wrapped: Wrapped
30+
31+
@inlinable init(wrapped: Wrapped) {
32+
self.wrapped = wrapped
33+
}
34+
35+
@inlinable func makeAsyncIterator() -> AsyncIterator {
36+
.init(iterator: self.wrapped.makeIterator())
37+
}
38+
}
39+
40+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
41+
extension Sequence where Self: Sendable {
42+
/// Turns `self` into an `AsyncSequence` by wending each element of `self` asynchronously.
43+
@inlinable func asAsyncSequence() -> AsyncSequenceFromSyncSequence<Self> {
44+
.init(wrapped: self)
45+
}
46+
}

Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift

+46-70
Original file line numberDiff line numberDiff line change
@@ -33,98 +33,74 @@ public struct HTTPClientResponse: Sendable {
3333
/// The body of this HTTP response.
3434
public var body: Body
3535

36-
/// A representation of the response body for an HTTP response.
37-
///
38-
/// The body is streamed as an `AsyncSequence` of `ByteBuffer`, where each `ByteBuffer` contains
39-
/// an arbitrarily large chunk of data. The boundaries between `ByteBuffer` objects in the sequence
40-
/// are entirely synthetic and have no semantic meaning.
41-
public struct Body: Sendable {
42-
private let bag: Transaction
43-
private let reference: ResponseRef
44-
45-
fileprivate init(_ transaction: Transaction) {
46-
self.bag = transaction
47-
self.reference = ResponseRef(transaction: transaction)
48-
}
49-
}
50-
5136
init(
5237
bag: Transaction,
5338
version: HTTPVersion,
5439
status: HTTPResponseStatus,
5540
headers: HTTPHeaders
5641
) {
57-
self.body = Body(bag)
5842
self.version = version
5943
self.status = status
6044
self.headers = headers
45+
self.body = Body(TransactionBody(bag))
6146
}
62-
}
63-
64-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
65-
extension HTTPClientResponse.Body: AsyncSequence {
66-
public typealias Element = AsyncIterator.Element
67-
68-
public struct AsyncIterator: AsyncIteratorProtocol {
69-
private let stream: IteratorStream
70-
71-
fileprivate init(stream: IteratorStream) {
72-
self.stream = stream
73-
}
74-
75-
public mutating func next() async throws -> ByteBuffer? {
76-
try await self.stream.next()
77-
}
78-
}
79-
80-
public func makeAsyncIterator() -> AsyncIterator {
81-
AsyncIterator(stream: IteratorStream(bag: self.bag))
47+
48+
@inlinable public init(){
49+
self.version = .http1_1
50+
self.status = .ok
51+
self.headers = [:]
52+
self.body = Body()
8253
}
8354
}
8455

8556
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
86-
extension HTTPClientResponse.Body {
87-
/// The purpose of this object is to inform the transaction about the response body being deinitialized.
88-
/// If the users has not called `makeAsyncIterator` on the body, before it is deinited, the http
89-
/// request needs to be cancelled.
90-
fileprivate final class ResponseRef: Sendable {
91-
private let transaction: Transaction
92-
93-
init(transaction: Transaction) {
94-
self.transaction = transaction
57+
extension HTTPClientResponse {
58+
/// A representation of the response body for an HTTP response.
59+
///
60+
/// The body is streamed as an `AsyncSequence` of `ByteBuffer`, where each `ByteBuffer` contains
61+
/// an arbitrarily large chunk of data. The boundaries between `ByteBuffer` objects in the sequence
62+
/// are entirely synthetic and have no semantic meaning.
63+
public struct Body: AsyncSequence, Sendable {
64+
public typealias Element = ByteBuffer
65+
@usableFromInline typealias Storage = Either<TransactionBody, AnyAsyncSequence<ByteBuffer>>
66+
public struct AsyncIterator: AsyncIteratorProtocol {
67+
@usableFromInline var storage: Storage.AsyncIterator
68+
69+
@inlinable init(storage: Storage.AsyncIterator) {
70+
self.storage = storage
71+
}
72+
73+
@inlinable public mutating func next() async throws -> ByteBuffer? {
74+
try await storage.next()
75+
}
9576
}
77+
78+
@usableFromInline var storage: Storage
9679

97-
deinit {
98-
self.transaction.responseBodyDeinited()
80+
@inlinable public func makeAsyncIterator() -> AsyncIterator {
81+
.init(storage: storage.makeAsyncIterator())
9982
}
10083
}
10184
}
10285

10386
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
10487
extension HTTPClientResponse.Body {
105-
internal class IteratorStream {
106-
struct ID: Hashable {
107-
private let objectID: ObjectIdentifier
108-
109-
init(_ object: IteratorStream) {
110-
self.objectID = ObjectIdentifier(object)
111-
}
112-
}
113-
114-
private var id: ID { ID(self) }
115-
private let bag: Transaction
116-
117-
init(bag: Transaction) {
118-
self.bag = bag
119-
}
120-
121-
deinit {
122-
self.bag.responseBodyIteratorDeinited(streamID: self.id)
123-
}
124-
125-
func next() async throws -> ByteBuffer? {
126-
try await self.bag.nextResponsePart(streamID: self.id)
127-
}
88+
@inlinable init(_ body: TransactionBody) {
89+
self.storage = .a(body)
90+
}
91+
92+
@inlinable public init<SequenceOfBytes>(
93+
_ sequenceOfBytes: SequenceOfBytes
94+
) where SequenceOfBytes: AsyncSequence & Sendable, SequenceOfBytes.Element == ByteBuffer {
95+
self.storage = .b(AnyAsyncSequence(sequenceOfBytes))
96+
}
97+
98+
public init() {
99+
self.init(EmptyCollection().asAsyncSequence())
100+
}
101+
102+
public init(_ byteBuffer: ByteBuffer) {
103+
self.init(CollectionOfOne(byteBuffer).asAsyncSequence())
128104
}
129105
}
130106

Sources/AsyncHTTPClient/AsyncAwait/Transaction+StateMachine.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ extension Transaction {
3030
case queued(CheckedContinuation<HTTPClientResponse, Error>, HTTPRequestScheduler)
3131
case deadlineExceededWhileQueued(CheckedContinuation<HTTPClientResponse, Error>)
3232
case executing(ExecutionContext, RequestStreamState, ResponseStreamState)
33-
case finished(error: Error?, HTTPClientResponse.Body.IteratorStream.ID?)
33+
case finished(error: Error?, TransactionBody.AsyncIterator.ID?)
3434
}
3535

3636
fileprivate enum RequestStreamState {
@@ -52,9 +52,9 @@ extension Transaction {
5252
// We are waiting for the user to create a response body iterator and to call next on
5353
// it for the first time.
5454
case waitingForResponseIterator(CircularBuffer<ByteBuffer>, next: Next)
55-
case buffering(HTTPClientResponse.Body.IteratorStream.ID, CircularBuffer<ByteBuffer>, next: Next)
56-
case waitingForRemote(HTTPClientResponse.Body.IteratorStream.ID, CheckedContinuation<ByteBuffer?, Error>)
57-
case finished(HTTPClientResponse.Body.IteratorStream.ID, CheckedContinuation<ByteBuffer?, Error>)
55+
case buffering(TransactionBody.AsyncIterator.ID, CircularBuffer<ByteBuffer>, next: Next)
56+
case waitingForRemote(TransactionBody.AsyncIterator.ID, CheckedContinuation<ByteBuffer?, Error>)
57+
case finished(TransactionBody.AsyncIterator.ID, CheckedContinuation<ByteBuffer?, Error>)
5858
}
5959

6060
private var state: State
@@ -510,7 +510,7 @@ extension Transaction {
510510
}
511511
}
512512

513-
mutating func responseBodyIteratorDeinited(streamID: HTTPClientResponse.Body.IteratorStream.ID) -> FailAction {
513+
mutating func responseBodyIteratorDeinited(streamID: TransactionBody.AsyncIterator.ID) -> FailAction {
514514
switch self.state {
515515
case .initialized, .queued, .deadlineExceededWhileQueued, .executing(_, _, .waitingForResponseHead):
516516
preconditionFailure("Got notice about a deinited response body iterator, before we even received a response. Invalid state: \(self.state)")
@@ -536,7 +536,7 @@ extension Transaction {
536536
}
537537

538538
mutating func consumeNextResponsePart(
539-
streamID: HTTPClientResponse.Body.IteratorStream.ID,
539+
streamID: TransactionBody.AsyncIterator.ID,
540540
continuation: CheckedContinuation<ByteBuffer?, Error>
541541
) -> ConsumeAction {
542542
switch self.state {
@@ -639,8 +639,8 @@ extension Transaction {
639639
}
640640

641641
private func verifyStreamIDIsEqual(
642-
registered: HTTPClientResponse.Body.IteratorStream.ID,
643-
this: HTTPClientResponse.Body.IteratorStream.ID,
642+
registered: TransactionBody.AsyncIterator.ID,
643+
this: TransactionBody.AsyncIterator.ID,
644644
file: StaticString = #file,
645645
line: UInt = #line
646646
) {

Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import NIOHTTP1
2020
import NIOSSL
2121

2222
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
23-
final class Transaction: @unchecked Sendable {
23+
@usableFromInline final class Transaction: @unchecked Sendable {
2424
let logger: Logger
2525

2626
let request: HTTPClientRequest.Prepared
@@ -334,7 +334,7 @@ extension Transaction {
334334
}
335335
}
336336

337-
func nextResponsePart(streamID: HTTPClientResponse.Body.IteratorStream.ID) async throws -> ByteBuffer? {
337+
func nextResponsePart(streamID: TransactionBody.AsyncIterator.ID) async throws -> ByteBuffer? {
338338
try await withCheckedThrowingContinuation { continuation in
339339
let action = self.stateLock.withLock {
340340
self.state.consumeNextResponsePart(streamID: streamID, continuation: continuation)
@@ -355,7 +355,7 @@ extension Transaction {
355355
}
356356
}
357357

358-
func responseBodyIteratorDeinited(streamID: HTTPClientResponse.Body.IteratorStream.ID) {
358+
func responseBodyIteratorDeinited(streamID: TransactionBody.AsyncIterator.ID) {
359359
let action = self.stateLock.withLock {
360360
self.state.responseBodyIteratorDeinited(streamID: streamID)
361361
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2021-2022 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import NIOCore
16+
17+
/// This is a class because we need to inform the transaction about the response body being deinitialized.
18+
/// If the users has not called `makeAsyncIterator` on the body, before it is deinited, the http
19+
/// request needs to be cancelled.
20+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
21+
@usableFromInline final class TransactionBody: Sendable {
22+
@usableFromInline let transaction: Transaction
23+
24+
init(_ transaction: Transaction) {
25+
self.transaction = transaction
26+
}
27+
28+
deinit {
29+
self.transaction.responseBodyDeinited()
30+
}
31+
}
32+
33+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
34+
extension TransactionBody: AsyncSequence {
35+
@usableFromInline typealias Element = AsyncIterator.Element
36+
37+
@usableFromInline final class AsyncIterator: AsyncIteratorProtocol {
38+
@usableFromInline struct ID: Hashable {
39+
private let objectID: ObjectIdentifier
40+
41+
init(_ object: AsyncIterator) {
42+
self.objectID = ObjectIdentifier(object)
43+
}
44+
}
45+
46+
@usableFromInline var id: ID { ID(self) }
47+
@usableFromInline let transaction: Transaction
48+
49+
@inlinable init(transaction: Transaction) {
50+
self.transaction = transaction
51+
}
52+
53+
deinit {
54+
self.transaction.responseBodyIteratorDeinited(streamID: self.id)
55+
}
56+
// TODO: this should be @inlinable
57+
@usableFromInline func next() async throws -> ByteBuffer? {
58+
try await self.transaction.nextResponsePart(streamID: self.id)
59+
}
60+
}
61+
62+
@inlinable func makeAsyncIterator() -> AsyncIterator {
63+
AsyncIterator(transaction: self.transaction)
64+
}
65+
}

0 commit comments

Comments
 (0)