Skip to content

Commit a288b60

Browse files
committed
save progress
fix deprecation warnings for `Task.sleep(_:)` rename AsyncRequest to HTTPClientRequest rename AsyncResponse to HTTPClientResponse fix merge conflicts adopt new validation method fix compilation
1 parent c363392 commit a288b60

File tree

5 files changed

+98
-138
lines changed

5 files changed

+98
-138
lines changed

Sources/AsyncHTTPClient/AsyncAwait/AsyncRequest+Validation.swift

+19-63
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import struct Foundation.URL
1616
import NIOHTTP1
1717

1818
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
19-
extension AsyncRequest {
19+
extension HTTPClientRequest {
2020

2121
struct ValidationResult {
2222
let requestFramingMetadata: RequestFramingMetadata
@@ -45,10 +45,11 @@ extension AsyncRequest {
4545
tlsConfiguration: nil
4646
)
4747

48-
try self.headers.validateFieldNames()
49-
5048
var head = HTTPRequestHead(version: .http1_1, method: self.method, uri: url.uri, headers: self.headers)
5149

50+
let bodyLength = RequestBodyLength(self.body)
51+
let metadata = try head.headers.validateAndSetTransportFraming(method: self.method, bodyLength: bodyLength)
52+
5253
// if no host header was set, let's pick
5354
if !head.headers.contains(name: "host") {
5455
guard let urlHost = url.host else {
@@ -57,67 +58,22 @@ extension AsyncRequest {
5758
head.headers.add(name: "host", value: urlHost)
5859
}
5960

60-
let encodings = head.headers[canonicalForm: "Transfer-Encoding"].map { $0.lowercased() }
61-
if encodings.contains("identity") {
62-
throw HTTPClientError.identityCodingIncorrectlyPresent
63-
}
64-
65-
head.headers.remove(name: "Transfer-Encoding")
66-
67-
guard let body = self.body else {
68-
head.headers.remove(name: "Content-Length")
69-
// if we don't have a body we might not need to send the Content-Length field
70-
// https://tools.ietf.org/html/rfc7230#section-3.3.2
71-
switch method {
72-
case .GET, .HEAD, .DELETE, .CONNECT, .TRACE:
73-
// A user agent SHOULD NOT send a Content-Length header field when the request
74-
// message does not contain a payload body and the method semantics do not
75-
// anticipate such a body.
76-
return ValidationResult(
77-
requestFramingMetadata: .init(connectionClose: !head.isKeepAlive, body: .none),
78-
poolKey: poolKey,
79-
head: head
80-
)
81-
default:
82-
// A user agent SHOULD send a Content-Length in a request message when
83-
// no Transfer-Encoding is sent and the request method defines a meaning
84-
// for an enclosed payload body.
85-
head.headers.add(name: "Content-Length", value: "0")
86-
return ValidationResult(
87-
requestFramingMetadata: .init(connectionClose: !head.isKeepAlive, body: .none),
88-
poolKey: poolKey,
89-
head: head
90-
)
91-
}
92-
}
93-
94-
if case .TRACE = method {
95-
// A client MUST NOT send a message body in a TRACE request.
96-
// https://tools.ietf.org/html/rfc7230#section-4.3.8
97-
throw HTTPClientError.traceRequestWithBody
98-
}
61+
return .init(requestFramingMetadata: metadata, poolKey: poolKey, head: head)
62+
}
63+
}
9964

100-
guard (encodings.lazy.filter { $0 == "chunked" }.count <= 1) else {
101-
throw HTTPClientError.chunkedSpecifiedMultipleTimes
65+
extension RequestBodyLength {
66+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
67+
init(_ body: HTTPClientRequest.Body?) {
68+
switch body?.mode {
69+
case .none:
70+
self = .fixed(length: 0)
71+
case .byteBuffer(let buffer):
72+
self = .fixed(length: buffer.readableBytes)
73+
case .sequence(nil, _), .asyncSequence(nil, _):
74+
self = .dynamic
75+
case .sequence(.some(let length), _), .asyncSequence(.some(let length), _):
76+
self = .fixed(length: length)
10277
}
103-
104-
// if encodings.isEmpty {
105-
// switch self.body {
106-
// case .some(.byteBuffer(let byteBuffer)):
107-
// head.headers.add(name: "content-length", value: "\(byteBuffer.readableBytes)")
108-
// case .some(bytes(let sequence)):
109-
// // if we have a content length header, we assume this was set correctly
110-
// if head.headers.contains(name: "content-length") {
111-
//
112-
// } else {
113-
// head.headers.add(name: "transfer-encoding", value: "chunked")
114-
// }
115-
//
116-
// }
117-
//
118-
//
119-
// }
120-
121-
preconditionFailure()
12278
}
12379
}

Sources/AsyncHTTPClient/AsyncAwait/AsyncRequestBag+StateMachine.swift

+11-11
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ extension AsyncRequestBag {
2323
struct ExecutionContext {
2424
let executor: HTTPRequestExecutor
2525
let allocator: ByteBufferAllocator
26-
let continuation: UnsafeContinuation<AsyncResponse, Error>
26+
let continuation: UnsafeContinuation<HTTPClientResponse, Error>
2727
}
2828

2929
private enum State {
3030
case initialized
31-
case waiting(UnsafeContinuation<AsyncResponse, Error>)
32-
case queued(UnsafeContinuation<AsyncResponse, Error>, HTTPRequestScheduler)
31+
case waiting(UnsafeContinuation<HTTPClientResponse, Error>)
32+
case queued(UnsafeContinuation<HTTPClientResponse, Error>, HTTPRequestScheduler)
3333
case executing(ExecutionContext, RequestStreamState, ResponseStreamState)
34-
case finished(error: Error?, AsyncResponse.Body.IteratorStream.ID?)
34+
case finished(error: Error?, HTTPClientResponse.Body.IteratorStream.ID?)
3535
}
3636

3737
fileprivate enum RequestStreamState {
@@ -50,9 +50,9 @@ extension AsyncRequestBag {
5050

5151
case initialized
5252
case waitingForStream(CircularBuffer<ByteBuffer>, next: Next)
53-
case buffering(AsyncResponse.Body.IteratorStream.ID, CircularBuffer<ByteBuffer>, next: Next)
54-
case waitingForRemote(AsyncResponse.Body.IteratorStream.ID, UnsafeContinuation<ByteBuffer?, Error>)
55-
case finished(AsyncResponse.Body.IteratorStream.ID, UnsafeContinuation<ByteBuffer?, Error>)
53+
case buffering(HTTPClientResponse.Body.IteratorStream.ID, CircularBuffer<ByteBuffer>, next: Next)
54+
case waitingForRemote(HTTPClientResponse.Body.IteratorStream.ID, UnsafeContinuation<ByteBuffer?, Error>)
55+
case finished(HTTPClientResponse.Body.IteratorStream.ID, UnsafeContinuation<ByteBuffer?, Error>)
5656
}
5757

5858
private var state: State
@@ -61,7 +61,7 @@ extension AsyncRequestBag {
6161
self.state = .initialized
6262
}
6363

64-
mutating func registerContinuation(_ continuation: UnsafeContinuation<AsyncResponse, Error>) {
64+
mutating func registerContinuation(_ continuation: UnsafeContinuation<HTTPClientResponse, Error>) {
6565
guard case .initialized = self.state else {
6666
preconditionFailure("Invalid state: \(self.state)")
6767
}
@@ -95,7 +95,7 @@ extension AsyncRequestBag {
9595

9696
enum FailAction {
9797
case none
98-
case failContinuation(UnsafeContinuation<AsyncResponse, Error>, Error, HTTPRequestScheduler?, HTTPRequestExecutor?)
98+
case failContinuation(UnsafeContinuation<HTTPClientResponse, Error>, Error, HTTPRequestScheduler?, HTTPRequestExecutor?)
9999
case failResponseStream(UnsafeContinuation<ByteBuffer?, Error>, Error, HTTPRequestExecutor)
100100
}
101101

@@ -333,7 +333,7 @@ extension AsyncRequestBag {
333333
// MARK: - Response -
334334

335335
enum ReceiveResponseHeadAction {
336-
case succeedResponseHead(HTTPResponseHead, UnsafeContinuation<AsyncResponse, Error>)
336+
case succeedResponseHead(HTTPResponseHead, UnsafeContinuation<HTTPClientResponse, Error>)
337337
case none
338338
}
339339

@@ -421,7 +421,7 @@ extension AsyncRequestBag {
421421
struct TriedToRegisteredASecondConsumer: Error {}
422422

423423
mutating func consumeNextResponsePart(
424-
streamID: AsyncResponse.Body.IteratorStream.ID,
424+
streamID: HTTPClientResponse.Body.IteratorStream.ID,
425425
continuation: UnsafeContinuation<ByteBuffer?, Error>
426426
) -> ConsumeAction {
427427
switch self.state {

Sources/AsyncHTTPClient/AsyncAwait/AsyncRequestBag.swift

+19-32
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import NIOHTTP1
2020
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
2121
actor AsyncRequestBag {
2222
// TODO: We should drop the request after sending to free up resource ASAP
23-
let request: AsyncRequest
23+
let request: HTTPClientRequest
2424

2525
let logger: Logger
2626

@@ -35,36 +35,23 @@ actor AsyncRequestBag {
3535
private var state: StateMachine = .init()
3636
private var isCancelled = false
3737

38-
init(request: AsyncRequest,
39-
requestOptions: RequestOptions,
40-
logger: Logger,
41-
connectionDeadline: NIODeadline,
42-
preferredEventLoop: EventLoop) {
38+
init(
39+
request: HTTPClientRequest,
40+
requestOptions: RequestOptions,
41+
logger: Logger,
42+
connectionDeadline: NIODeadline,
43+
preferredEventLoop: EventLoop
44+
) throws {
4345
self.request = request
4446
self.requestOptions = requestOptions
4547
self.logger = logger
4648
self.connectionDeadline = connectionDeadline
4749
self.preferredEventLoop = preferredEventLoop
4850

49-
self.requestHead = HTTPRequestHead(
50-
version: .http1_1,
51-
method: request.method,
52-
uri: request.url,
53-
headers: request.headers
54-
)
55-
56-
switch request.body?.mode {
57-
case .byteBuffer(let byteBuffer):
58-
self.requestFramingMetadata = .init(connectionClose: false, body: .fixedSize(byteBuffer.readableBytes))
59-
case .sequence:
60-
self.requestFramingMetadata = .init(connectionClose: false, body: .stream)
61-
case .asyncSequence:
62-
self.requestFramingMetadata = .init(connectionClose: false, body: .stream)
63-
case .none:
64-
self.requestFramingMetadata = .init(connectionClose: false, body: .none)
65-
}
66-
67-
self.poolKey = ConnectionPool.Key(scheme: .http, host: "localhost", port: 123, unixPath: "", tlsConfiguration: nil)
51+
let validatedRequest = try request.validate()
52+
self.poolKey = validatedRequest.poolKey
53+
self.requestHead = validatedRequest.head
54+
self.requestFramingMetadata = validatedRequest.requestFramingMetadata
6855
}
6956

7057
nonisolated func cancel() {
@@ -73,7 +60,7 @@ actor AsyncRequestBag {
7360
}
7461
}
7562

76-
func result() async throws -> AsyncResponse {
63+
func result() async throws -> HTTPClientResponse {
7764
try await withUnsafeThrowingContinuation { continuation in
7865
self.state.registerContinuation(continuation)
7966
}
@@ -120,7 +107,7 @@ actor AsyncRequestBag {
120107
break
121108
case .resumeStream(let allocator):
122109
switch self.request.body?.mode {
123-
case .asyncSequence(let next):
110+
case .asyncSequence(_, let next):
124111
// it is safe to call this async here. it dispatches...
125112
await self.writeRequestStream(allocator, next: next)
126113

@@ -138,7 +125,7 @@ actor AsyncRequestBag {
138125
case .none:
139126
break
140127

141-
case .sequence(let create):
128+
case .sequence(_, let create):
142129
do {
143130
let byteBuffer = try create(allocator) // <--- only throw point
144131

@@ -176,7 +163,7 @@ actor AsyncRequestBag {
176163
case .none:
177164
break
178165
case .succeedResponseHead(let head, let continuation):
179-
let asyncResponse = AsyncResponse(
166+
let asyncResponse = HTTPClientResponse(
180167
bag: self,
181168
version: head.version,
182169
status: head.status,
@@ -317,7 +304,7 @@ extension AsyncRequestBag: HTTPExecutableRequest {
317304

318305
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
319306
extension AsyncRequestBag {
320-
func nextResponsePart(streamID: AsyncResponse.Body.IteratorStream.ID) async throws -> ByteBuffer? {
307+
func nextResponsePart(streamID: HTTPClientResponse.Body.IteratorStream.ID) async throws -> ByteBuffer? {
321308
try await withUnsafeThrowingContinuation { continuation in
322309
switch self.state.consumeNextResponsePart(streamID: streamID, continuation: continuation) {
323310
case .succeedContinuation(let continuation, let result):
@@ -330,9 +317,9 @@ extension AsyncRequestBag {
330317
}
331318
}
332319

333-
func cancelResponseStream0(streamID: AsyncResponse.Body.IteratorStream.ID) {}
320+
func cancelResponseStream0(streamID: HTTPClientResponse.Body.IteratorStream.ID) {}
334321

335-
nonisolated func cancelResponseStream(streamID: AsyncResponse.Body.IteratorStream.ID) {
322+
nonisolated func cancelResponseStream(streamID: HTTPClientResponse.Body.IteratorStream.ID) {
336323
Task.detached {
337324
await self.cancelResponseStream0(streamID: streamID)
338325
}

Sources/AsyncHTTPClient/AsyncAwait/AsyncRequest.swift renamed to Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift

+26-16
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ import NIO
1818
import NIOHTTP1
1919

2020
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
21-
// HTTPClient.AsyncRequest
22-
struct AsyncRequest {
21+
// HTTPClient.HTTPClientRequest
22+
struct HTTPClientRequest {
2323
public struct Body {
2424
internal enum Mode {
25-
case asyncSequence((ByteBufferAllocator) async throws -> (IOData?))
25+
case asyncSequence(length: Int?, (ByteBufferAllocator) async throws -> (IOData?))
2626
// case asyncSequenceFactory(() -> Mode) // typealias (ByteBufferAllocator) async throws -> IOData?
27-
case sequence((ByteBufferAllocator) throws -> ByteBuffer)
27+
case sequence(length: Int?, (ByteBufferAllocator) throws -> ByteBuffer)
2828
case byteBuffer(ByteBuffer)
2929
}
3030

@@ -39,19 +39,29 @@ struct AsyncRequest {
3939
}
4040

4141
static func bytes<S: Sequence>(_ sequence: S) -> Body where S.Element == UInt8 {
42-
self.init(.asyncSequence { allocator in
42+
self.init(.sequence(length: nil) { allocator in
4343
if let buffer = sequence.withContiguousStorageIfAvailable({ allocator.buffer(bytes: $0) }) {
4444
// fastpath
45-
return .byteBuffer(buffer)
45+
return buffer
46+
}
47+
// potentially really slow path
48+
return allocator.buffer(bytes: sequence)
49+
})
50+
}
51+
static func bytes<C: RandomAccessCollection>(_ collection: C) -> Body where C.Element == UInt8 {
52+
self.init(.sequence(length: collection.count) { allocator in
53+
if let buffer = collection.withContiguousStorageIfAvailable({ allocator.buffer(bytes: $0) }) {
54+
// fastpath
55+
return buffer
4656
}
4757
// potentially really slow path
48-
return .byteBuffer(allocator.buffer(bytes: sequence))
58+
return allocator.buffer(bytes: collection)
4959
})
5060
}
5161

5262
static func stream<S: AsyncSequence>(_ sequence: S) -> Body where S.Element == ByteBuffer {
5363
var iterator = sequence.makeAsyncIterator()
54-
let body = self.init(.asyncSequence { _ -> IOData? in
64+
let body = self.init(.asyncSequence(length: nil) { _ -> IOData? in
5565
if let byteBuffer = try await iterator.next() {
5666
return .byteBuffer(byteBuffer)
5767
}
@@ -62,7 +72,7 @@ struct AsyncRequest {
6272

6373
static func stream<S: AsyncSequence>(_ sequence: S) -> Body where S.Element == FileRegion {
6474
var iterator = sequence.makeAsyncIterator()
65-
let body = self.init(.asyncSequence { _ in
75+
let body = self.init(.asyncSequence(length: nil) { _ in
6676
if let fileRegion = try await iterator.next() {
6777
return .fileRegion(fileRegion)
6878
}
@@ -73,7 +83,7 @@ struct AsyncRequest {
7383

7484
static func stream<S: AsyncSequence>(_ sequence: S) -> Body where S.Element == UInt8 {
7585
var iterator = sequence.makeAsyncIterator()
76-
let body = self.init(.asyncSequence { allocator -> IOData? in
86+
let body = self.init(.asyncSequence(length: nil) { allocator -> IOData? in
7787
var buffer = allocator.buffer(capacity: 1024) // TODO: Magic number
7888
while buffer.writableBytes > 0, let byte = try await iterator.next() {
7989
buffer.writeInteger(byte)
@@ -102,7 +112,7 @@ struct AsyncRequest {
102112
}
103113

104114
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
105-
public struct AsyncResponse {
115+
public struct HTTPClientResponse {
106116
public var version: HTTPVersion
107117
public var status: HTTPResponseStatus
108118
public var headers: HTTPHeaders
@@ -130,7 +140,7 @@ public struct AsyncResponse {
130140
}
131141

132142
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
133-
extension AsyncResponse.Body: AsyncSequence {
143+
extension HTTPClientResponse.Body: AsyncSequence {
134144
public typealias Element = ByteBuffer
135145
public typealias AsyncIterator = Iterator
136146

@@ -180,18 +190,18 @@ extension AsyncResponse.Body: AsyncSequence {
180190

181191
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
182192
extension HTTPClient {
183-
func execute(_ request: AsyncRequest, deadline: NIODeadline, logger: Logger) async throws -> AsyncResponse {
184-
let bag = AsyncRequestBag(
193+
func execute(_ request: HTTPClientRequest, deadline: NIODeadline, logger: Logger) async throws -> HTTPClientResponse {
194+
let bag = try AsyncRequestBag(
185195
request: request,
186-
requestOptions: .init(idleReadTimeout: nil, ignoreUncleanSSLShutdown: false),
196+
requestOptions: .init(idleReadTimeout: nil),
187197
logger: logger,
188198
connectionDeadline: .now() + .seconds(10),
189199
preferredEventLoop: self.eventLoopGroup.next()
190200
)
191201

192202
return try await withTaskCancellationHandler {
193203
bag.cancel()
194-
} operation: { () -> AsyncResponse in
204+
} operation: { () -> HTTPClientResponse in
195205
// first register the completion
196206
async let result = bag.result()
197207

0 commit comments

Comments
 (0)