Skip to content

Commit d7b69d9

Browse files
authored
Make HTTPClientResponse.init public (#632)
1 parent f17a47e commit d7b69d9

File tree

9 files changed

+310
-97
lines changed

9 files changed

+310
-97
lines changed
Lines changed: 48 additions & 0 deletions
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+
structAnyAsyncSequence<Element>:Sendable,AsyncSequence{
18+
@usableFromInlinetypealiasAsyncIteratorNextCallback=()asyncthrows->Element?
19+
20+
@usableFromInlinestructAsyncIterator:AsyncIteratorProtocol{
21+
@usableFromInlineletnextCallback:AsyncIteratorNextCallback
22+
23+
@inlinableinit(nextCallback:@escapingAsyncIteratorNextCallback){
24+
self.nextCallback = nextCallback
25+
}
26+
27+
@inlinablemutatingfunc next()asyncthrows->Element?{
28+
tryawaitself.nextCallback()
29+
}
30+
}
31+
32+
@usableFromInlinevarmakeAsyncIteratorCallback:@Sendable()->AsyncIteratorNextCallback
33+
34+
@inlinableinit<SequenceOfBytes>(
35+
_ asyncSequence:SequenceOfBytes
36+
)where SequenceOfBytes:AsyncSequence&Sendable, SequenceOfBytes.Element ==Element{
37+
self.makeAsyncIteratorCallback ={
38+
variterator= asyncSequence.makeAsyncIterator()
39+
return{
40+
tryawait iterator.next()
41+
}
42+
}
43+
}
44+
45+
@inlinablefunc makeAsyncIterator()->AsyncIterator{
46+
.init(nextCallback:self.makeAsyncIteratorCallback())
47+
}
48+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
16+
@usableFromInline
17+
structAsyncLazySequence<Base:Sequence>:AsyncSequence{
18+
@usableFromInlinetypealiasElement=Base.Element
19+
@usableFromInlinestructAsyncIterator:AsyncIteratorProtocol{
20+
@usableFromInlinevariterator:Base.Iterator
21+
@inlinableinit(iterator:Base.Iterator){
22+
self.iterator = iterator
23+
}
24+
25+
@inlinablemutatingfunc next()asyncthrows->Base.Element?{
26+
self.iterator.next()
27+
}
28+
}
29+
30+
@usableFromInlinevarbase:Base
31+
32+
@inlinableinit(base:Base){
33+
self.base = base
34+
}
35+
36+
@inlinablefunc makeAsyncIterator()->AsyncIterator{
37+
.init(iterator:self.base.makeIterator())
38+
}
39+
}
40+
41+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
42+
extensionAsyncLazySequence:Sendablewhere Base:Sendable{}
43+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
44+
extensionAsyncLazySequence.AsyncIterator:Sendablewhere Base.Iterator:Sendable{}
45+
46+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
47+
extensionSequence{
48+
/// Turns `self` into an `AsyncSequence` by vending each element of `self` asynchronously.
49+
@inlinablevarasync:AsyncLazySequence<Self>{
50+
.init(base:self)
51+
}
52+
}

‎Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift‎

Lines changed: 83 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -33,98 +33,125 @@ public struct HTTPClientResponse: Sendable{
3333
/// The body of this HTTP response.
3434
publicvarbody: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-
publicstructBody:Sendable{
42-
privateletbag:Transaction
43-
privateletreference:ResponseRef
44-
45-
fileprivateinit(_ 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))
46+
}
47+
48+
@inlinablepublicinit(
49+
version:HTTPVersion=.http1_1,
50+
status:HTTPResponseStatus=.ok,
51+
headers:HTTPHeaders=[:],
52+
body:Body=Body()
53+
){
54+
self.version = version
55+
self.status = status
56+
self.headers = headers
57+
self.body = body
6158
}
6259
}
6360

6461
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
65-
extensionHTTPClientResponse.Body:AsyncSequence{
66-
publictypealiasElement=AsyncIterator.Element
62+
extensionHTTPClientResponse{
63+
/// A representation of the response body for an HTTP response.
64+
///
65+
/// The body is streamed as an `AsyncSequence` of `ByteBuffer`, where each `ByteBuffer` contains
66+
/// an arbitrarily large chunk of data. The boundaries between `ByteBuffer` objects in the sequence
67+
/// are entirely synthetic and have no semantic meaning.
68+
publicstructBody:AsyncSequence,Sendable{
69+
publictypealiasElement=ByteBuffer
70+
publicstructAsyncIterator:AsyncIteratorProtocol{
71+
@usableFromInlinevarstorage:Storage.AsyncIterator
6772

68-
publicstructAsyncIterator:AsyncIteratorProtocol{
69-
privateletstream:IteratorStream
73+
@inlinableinit(storage:Storage.AsyncIterator){
74+
self.storage = storage
75+
}
7076

71-
fileprivateinit(stream:IteratorStream){
72-
self.stream = stream
77+
@inlinablepublicmutatingfunc next()asyncthrows->ByteBuffer?{
78+
tryawaitself.storage.next()
79+
}
7380
}
7481

75-
publicmutatingfunc next()asyncthrows->ByteBuffer?{
76-
tryawaitself.stream.next()
82+
@usableFromInlinevarstorage:Storage
83+
84+
@inlinablepublicfunc makeAsyncIterator()->AsyncIterator{
85+
.init(storage:self.storage.makeAsyncIterator())
7786
}
7887
}
88+
}
7989

80-
publicfunc makeAsyncIterator()->AsyncIterator{
81-
AsyncIterator(stream:IteratorStream(bag:self.bag))
90+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
91+
extensionHTTPClientResponse.Body{
92+
@usableFromInlineenumStorage:Sendable{
93+
case transaction(TransactionBody)
94+
case anyAsyncSequence(AnyAsyncSequence<ByteBuffer>)
8295
}
8396
}
8497

8598
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
86-
extensionHTTPClientResponse.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-
fileprivatefinalclassResponseRef:Sendable{
91-
privatelettransaction:Transaction
92-
93-
init(transaction:Transaction){
94-
self.transaction = transaction
99+
extensionHTTPClientResponse.Body.Storage:AsyncSequence{
100+
@usableFromInlinetypealiasElement=ByteBuffer
101+
102+
@inlinablefunc makeAsyncIterator()->AsyncIterator{
103+
switchself{
104+
case.transaction(let transaction):
105+
return.transaction(transaction.makeAsyncIterator())
106+
case.anyAsyncSequence(let anyAsyncSequence):
107+
return.anyAsyncSequence(anyAsyncSequence.makeAsyncIterator())
95108
}
109+
}
110+
}
111+
112+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
113+
extensionHTTPClientResponse.Body.Storage{
114+
@usableFromInlineenumAsyncIterator{
115+
case transaction(TransactionBody.AsyncIterator)
116+
case anyAsyncSequence(AnyAsyncSequence<ByteBuffer>.AsyncIterator)
117+
}
118+
}
96119

97-
deinit{
98-
self.transaction.responseBodyDeinited()
120+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
121+
extensionHTTPClientResponse.Body.Storage.AsyncIterator:AsyncIteratorProtocol{
122+
@inlinablemutatingfunc next()asyncthrows->ByteBuffer?{
123+
switchself{
124+
case.transaction(let iterator):
125+
returntryawait iterator.next()
126+
case.anyAsyncSequence(var iterator):
127+
defer{self=.anyAsyncSequence(iterator)}
128+
returntryawait iterator.next()
99129
}
100130
}
101131
}
102132

103133
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
104134
extensionHTTPClientResponse.Body{
105-
internalclassIteratorStream{
106-
structID:Hashable{
107-
privateletobjectID:ObjectIdentifier
108-
109-
init(_ object:IteratorStream){
110-
self.objectID =ObjectIdentifier(object)
111-
}
112-
}
135+
init(_ body:TransactionBody){
136+
self.init(.transaction(body))
137+
}
113138

114-
privatevarid:ID{ID(self)}
115-
privateletbag:Transaction
139+
@usableFromInlineinit(_ storage:Storage){
140+
self.storage = storage
141+
}
116142

117-
init(bag:Transaction){
118-
self.bag=bag
119-
}
143+
publicinit(){
144+
self=.stream(EmptyCollection<ByteBuffer>().async)
145+
}
120146

121-
deinit{
122-
self.bag.responseBodyIteratorDeinited(streamID:self.id)
123-
}
147+
@inlinablepublicstaticfunc stream<SequenceOfBytes>(
148+
_ sequenceOfBytes:SequenceOfBytes
149+
)->Selfwhere SequenceOfBytes:AsyncSequence&Sendable, SequenceOfBytes.Element ==ByteBuffer{
150+
self.init(.anyAsyncSequence(AnyAsyncSequence(sequenceOfBytes.singleIteratorPrecondition)))
151+
}
124152

125-
func next()asyncthrows->ByteBuffer?{
126-
tryawaitself.bag.nextResponsePart(streamID:self.id)
127-
}
153+
publicstaticfunc bytes(_ byteBuffer:ByteBuffer)->Self{
154+
.stream(CollectionOfOne(byteBuffer).async)
128155
}
129156
}
130157

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
import Atomics
16+
17+
/// Makes sure that a consumer of this `AsyncSequence` only calls `makeAsyncIterator()` at most once.
18+
/// If `makeAsyncIterator()` is called multiple times, the program crashes.
19+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
20+
@usableFromInlinestructSingleIteratorPrecondition<Base:AsyncSequence>:AsyncSequence{
21+
@usableFromInlineletbase:Base
22+
@usableFromInlineletdidCreateIterator:ManagedAtomic<Bool>=.init(false)
23+
@usableFromInlinetypealiasElement=Base.Element
24+
@inlinableinit(base:Base){
25+
self.base = base
26+
}
27+
28+
@inlinablefunc makeAsyncIterator()->Base.AsyncIterator{
29+
precondition(
30+
self.didCreateIterator.exchange(true, ordering:.relaxed)==false,
31+
"makeAsyncIterator() is only allowed to be called at most once."
32+
)
33+
returnself.base.makeAsyncIterator()
34+
}
35+
}
36+
37+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
38+
extensionSingleIteratorPrecondition:@uncheckedSendablewhere Base:Sendable{}
39+
40+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0,*)
41+
extensionAsyncSequence{
42+
@inlinablevarsingleIteratorPrecondition:SingleIteratorPrecondition<Self>{
43+
.init(base:self)
44+
}
45+
}

‎Sources/AsyncHTTPClient/AsyncAwait/Transaction+StateMachine.swift‎

Lines changed: 8 additions & 8 deletions
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
fileprivateenumRequestStreamState{
@@ -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
privatevarstate:State
@@ -510,7 +510,7 @@ extension Transaction{
510510
}
511511
}
512512

513-
mutatingfunc responseBodyIteratorDeinited(streamID:HTTPClientResponse.Body.IteratorStream.ID)->FailAction{
513+
mutatingfunc responseBodyIteratorDeinited(streamID:TransactionBody.AsyncIterator.ID)->FailAction{
514514
switchself.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
mutatingfunc consumeNextResponsePart(
539-
streamID:HTTPClientResponse.Body.IteratorStream.ID,
539+
streamID:TransactionBody.AsyncIterator.ID,
540540
continuation:CheckedContinuation<ByteBuffer?,Error>
541541
)->ConsumeAction{
542542
switchself.state {
@@ -639,8 +639,8 @@ extension Transaction{
639639
}
640640

641641
privatefunc 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
){

0 commit comments

Comments
(0)