From 668b33ab342624bda6b5d9ac5f5db39dd0f4f429 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Thu, 16 Mar 2023 10:55:29 +0000 Subject: [PATCH 01/16] Collect function fix Motivation: not accumulate too many bytes Modifications: Implementing collect function to use NIOCore version to prevent overflowing --- .../AsyncAwait/HTTPClientResponse.swift | 9 ++++ .../AsyncAwaitEndToEndTests.swift | 43 ++++++++++++------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index b68e8db8b..91f29f2cb 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -83,6 +83,15 @@ extension HTTPClientResponse { @inlinable public func makeAsyncIterator() -> AsyncIterator { .init(storage: self.storage.makeAsyncIterator()) } + + /// Accumulates an ``Swift/AsyncSequence`` of ``ByteBuffer``s into a single ``ByteBuffer``. + /// - Parameters: + /// - maxBytes: The maximum number of bytes this method is allowed to accumulate + /// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`. + /// - Returns: the number of bytes collected over time + func collect(maxBytes: Int) async throws -> ByteBuffer { + return try await self.collect(upTo: maxBytes) + } } } diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index bd6720220..2c6ec80d6 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -114,7 +114,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], ["4"]) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect() + try await response.body.collect(maxBytes: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -137,7 +137,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect() + try await response.body.collect(maxBytes: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -160,7 +160,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect() + try await response.body.collect(maxBytes: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -183,7 +183,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], ["4"]) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect() + try await response.body.collect(maxBytes: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -210,7 +210,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect() + try await response.body.collect(maxBytes: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -233,7 +233,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect() + try await response.body.collect(maxBytes: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -522,7 +522,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } - guard let body = await XCTAssertNoThrowWithResult(try await response.body.collect()) else { return } + guard let body = await XCTAssertNoThrowWithResult(try await response.body.collect(maxBytes: 1024)) else { return } var maybeRequestInfo: RequestInfo? XCTAssertNoThrow(maybeRequestInfo = try JSONDecoder().decode(RequestInfo.self, from: body)) guard let requestInfo = maybeRequestInfo else { return } @@ -583,7 +583,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response1.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response1.body.collect() + try await response1.body.collect(maxBytes: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) @@ -592,18 +592,31 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response2.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response2.body.collect() + try await response2.body.collect(maxBytes: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } } -} -extension AsyncSequence where Element == ByteBuffer { - func collect() async rethrows -> ByteBuffer { - try await self.reduce(into: ByteBuffer()) { accumulatingBuffer, nextBuffer in - var nextBuffer = nextBuffer - accumulatingBuffer.writeBuffer(&nextBuffer) + func testBytesOverflowError() { + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + let bin = HTTPBin(.http2(compress: false)) { _ in HTTPEchoHandler() } + defer { XCTAssertNoThrow(try bin.shutdown()) } + let client = makeDefaultHTTPClient() + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/") + request.method = .POST + request.body = .bytes(ByteBuffer(string: "123456")) + + guard let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) else { return } + XCTAssertEqual(response.headers["content-length"], ["6"]) + await XCTAssertThrowsError( + try await response.body.collect(maxBytes: 1) + ) } } } From 6276e514348896104ee314948a2ccc5ce62b5752 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Thu, 16 Mar 2023 10:59:01 +0000 Subject: [PATCH 02/16] minor change --- Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index 2c6ec80d6..99a143736 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -615,8 +615,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], ["6"]) await XCTAssertThrowsError( - try await response.body.collect(maxBytes: 1) - ) + try await response.body.collect(maxBytes: 1)) } } } From 2ad4a6b589606b61e5315efe2662ff56f3eb2fc0 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:54:18 +0000 Subject: [PATCH 03/16] putting back in tests accidentally deleted --- .../AsyncAwaitEndToEndTests.swift | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index 12f16e42f..be5f55ebd 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -597,6 +597,172 @@ final class AsyncAwaitEndToEndTests: XCTestCase { XCTAssertEqual(body, ByteBuffer(string: "1234")) } } + func testRejectsInvalidCharactersInHeaderFieldNames_http1() { + self._rejectsInvalidCharactersInHeaderFieldNames(mode: .http1_1(ssl: true)) + } + + func testRejectsInvalidCharactersInHeaderFieldNames_http2() { + self._rejectsInvalidCharactersInHeaderFieldNames(mode: .http2(compress: false)) + } + + private func _rejectsInvalidCharactersInHeaderFieldNames(mode: HTTPBin.Mode) { + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + let bin = HTTPBin(mode) + defer { XCTAssertNoThrow(try bin.shutdown()) } + let client = makeDefaultHTTPClient() + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) + + // The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid + // characters as the following: + // + // ``` + // field-name = token + // + // token = 1*tchar + // + // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + // / DIGIT / ALPHA + // ; any VCHAR, except delimiters + let weirdAllowedFieldName = "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") + request.headers.add(name: weirdAllowedFieldName, value: "present") + + // This should work fine. + guard let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) else { + return + } + + XCTAssertEqual(response.status, .ok) + + // Now, let's confirm all other bytes are rejected. We want to stay within the ASCII space as the HTTPHeaders type will forbid anything else. + for byte in UInt8(0)...UInt8(127) { + // Skip bytes that we already believe are allowed. + if weirdAllowedFieldName.utf8.contains(byte) { + continue + } + let forbiddenFieldName = weirdAllowedFieldName + String(decoding: [byte], as: UTF8.self) + + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") + request.headers.add(name: forbiddenFieldName, value: "present") + + await XCTAssertThrowsError(try await client.execute(request, deadline: .now() + .seconds(10), logger: logger)) { error in + XCTAssertEqual(error as? HTTPClientError, .invalidHeaderFieldNames([forbiddenFieldName])) + } + } + } + } + + func testRejectsInvalidCharactersInHeaderFieldValues_http1() { + self._rejectsInvalidCharactersInHeaderFieldValues(mode: .http1_1(ssl: true)) + } + + func testRejectsInvalidCharactersInHeaderFieldValues_http2() { + self._rejectsInvalidCharactersInHeaderFieldValues(mode: .http2(compress: false)) + } + + private func _rejectsInvalidCharactersInHeaderFieldValues(mode: HTTPBin.Mode) { + func testBytesOverflowError() { + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + let bin = HTTPBin(mode) + let bin = HTTPBin(.http2(compress: false)) { _ in HTTPEchoHandler() } + defer { XCTAssertNoThrow(try bin.shutdown()) } + let client = makeDefaultHTTPClient() + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/") + request.method = .POST + request.body = .bytes(ByteBuffer(string: "123456")) + + // We reject all ASCII control characters except HTAB and tolerate everything else. + let weirdAllowedFieldValue = "!\" \t#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") + request.headers.add(name: "Weird-Value", value: weirdAllowedFieldValue) + + // This should work fine. + guard let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) else { + return + } + + XCTAssertEqual(response.status, .ok) + + // Now, let's confirm all other bytes in the ASCII range ar rejected + for byte in UInt8(0)...UInt8(127) { + // Skip bytes that we already believe are allowed. + if weirdAllowedFieldValue.utf8.contains(byte) { + continue + } + let forbiddenFieldValue = weirdAllowedFieldValue + String(decoding: [byte], as: UTF8.self) + + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") + request.headers.add(name: "Weird-Value", value: forbiddenFieldValue) + + await XCTAssertThrowsError(try await client.execute(request, deadline: .now() + .seconds(10), logger: logger)) { error in + XCTAssertEqual(error as? HTTPClientError, .invalidHeaderFieldValues([forbiddenFieldValue])) + } + } + + // All the bytes outside the ASCII range are fine though. + for byte in UInt8(128)...UInt8(255) { + let evenWeirderAllowedValue = weirdAllowedFieldValue + String(decoding: [byte], as: UTF8.self) + + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") + request.headers.add(name: "Weird-Value", value: evenWeirderAllowedValue) + + // This should work fine. + guard let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) else { + return + } + + XCTAssertEqual(response.status, .ok) + } + } + } + + func testUsingGetMethodInsteadOfWait() { + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + let bin = HTTPBin(.http2(compress: false)) + defer { XCTAssertNoThrow(try bin.shutdown()) } + let client = makeDefaultHTTPClient() + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let request = try HTTPClient.Request(url: "https://localhost:\(bin.port)/get") + + guard let response = await XCTAssertNoThrowWithResult( + try await client.execute(request: request).get() + ) else { + return + } + + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(response.version, .http2) + } + } + } + + extension AsyncSequence where Element == ByteBuffer { + func collect() async rethrows -> ByteBuffer { + try await self.reduce(into: ByteBuffer()) { accumulatingBuffer, nextBuffer in + var nextBuffer = nextBuffer + accumulatingBuffer.writeBuffer(&nextBuffer) + ) else { return } + XCTAssertEqual(response.headers["content-length"], ["6"]) + await XCTAssertThrowsError( + try await response.body.collect(maxBytes: 1)) + } + } + } func testBytesOverflowError() { guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } From e919181bbf67323f98a195d59d90ac3b3ab465b5 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Mon, 20 Mar 2023 11:21:13 +0000 Subject: [PATCH 04/16] Fixing comment --- Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index 91f29f2cb..85c0d570c 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -92,6 +92,7 @@ extension HTTPClientResponse { func collect(maxBytes: Int) async throws -> ByteBuffer { return try await self.collect(upTo: maxBytes) } + /// Accumulates `Body` of ``ByteBuffer``s into a single ``ByteBuffer``. } } From df627a513438dec47a0a182c2664c152109ff4bd Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Mon, 20 Mar 2023 11:24:12 +0000 Subject: [PATCH 05/16] incorrect ordering --- Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index 85c0d570c..ec444511a 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -84,7 +84,7 @@ extension HTTPClientResponse { .init(storage: self.storage.makeAsyncIterator()) } - /// Accumulates an ``Swift/AsyncSequence`` of ``ByteBuffer``s into a single ``ByteBuffer``. + /// Accumulates `Body` of ``ByteBuffer``s into a single ``ByteBuffer``. /// - Parameters: /// - maxBytes: The maximum number of bytes this method is allowed to accumulate /// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`. @@ -92,7 +92,6 @@ extension HTTPClientResponse { func collect(maxBytes: Int) async throws -> ByteBuffer { return try await self.collect(upTo: maxBytes) } - /// Accumulates `Body` of ``ByteBuffer``s into a single ``ByteBuffer``. } } From 81568442fc2724027f78b9a44d8d29761ae02b34 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Mon, 20 Mar 2023 14:54:37 +0000 Subject: [PATCH 06/16] Main to this file --- .../AsyncAwaitEndToEndTests.swift | 133 +++++++----------- 1 file changed, 47 insertions(+), 86 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index be5f55ebd..33dda1cbc 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -667,125 +667,86 @@ final class AsyncAwaitEndToEndTests: XCTestCase { } private func _rejectsInvalidCharactersInHeaderFieldValues(mode: HTTPBin.Mode) { - func testBytesOverflowError() { - guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } - XCTAsyncTest { - let bin = HTTPBin(mode) - let bin = HTTPBin(.http2(compress: false)) { _ in HTTPEchoHandler() } - defer { XCTAssertNoThrow(try bin.shutdown()) } - let client = makeDefaultHTTPClient() - defer { XCTAssertNoThrow(try client.syncShutdown()) } - let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) - var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/") - request.method = .POST - request.body = .bytes(ByteBuffer(string: "123456")) - - // We reject all ASCII control characters except HTAB and tolerate everything else. - let weirdAllowedFieldValue = "!\" \t#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" - - var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") - request.headers.add(name: "Weird-Value", value: weirdAllowedFieldValue) + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + let bin = HTTPBin(mode) + defer { XCTAssertNoThrow(try bin.shutdown()) } + let client = makeDefaultHTTPClient() + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) - // This should work fine. - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { - return - } + // We reject all ASCII control characters except HTAB and tolerate everything else. + let weirdAllowedFieldValue = "!\" \t#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" - XCTAssertEqual(response.status, .ok) + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") + request.headers.add(name: "Weird-Value", value: weirdAllowedFieldValue) - // Now, let's confirm all other bytes in the ASCII range ar rejected - for byte in UInt8(0)...UInt8(127) { - // Skip bytes that we already believe are allowed. - if weirdAllowedFieldValue.utf8.contains(byte) { - continue - } - let forbiddenFieldValue = weirdAllowedFieldValue + String(decoding: [byte], as: UTF8.self) + // This should work fine. + guard let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) else { + return + } - var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") - request.headers.add(name: "Weird-Value", value: forbiddenFieldValue) + XCTAssertEqual(response.status, .ok) - await XCTAssertThrowsError(try await client.execute(request, deadline: .now() + .seconds(10), logger: logger)) { error in - XCTAssertEqual(error as? HTTPClientError, .invalidHeaderFieldValues([forbiddenFieldValue])) - } + // Now, let's confirm all other bytes in the ASCII range ar rejected + for byte in UInt8(0)...UInt8(127) { + // Skip bytes that we already believe are allowed. + if weirdAllowedFieldValue.utf8.contains(byte) { + continue } + let forbiddenFieldValue = weirdAllowedFieldValue + String(decoding: [byte], as: UTF8.self) - // All the bytes outside the ASCII range are fine though. - for byte in UInt8(128)...UInt8(255) { - let evenWeirderAllowedValue = weirdAllowedFieldValue + String(decoding: [byte], as: UTF8.self) - - var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") - request.headers.add(name: "Weird-Value", value: evenWeirderAllowedValue) - - // This should work fine. - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { - return - } + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") + request.headers.add(name: "Weird-Value", value: forbiddenFieldValue) - XCTAssertEqual(response.status, .ok) + await XCTAssertThrowsError(try await client.execute(request, deadline: .now() + .seconds(10), logger: logger)) { error in + XCTAssertEqual(error as? HTTPClientError, .invalidHeaderFieldValues([forbiddenFieldValue])) } } - } - func testUsingGetMethodInsteadOfWait() { - guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } - XCTAsyncTest { - let bin = HTTPBin(.http2(compress: false)) - defer { XCTAssertNoThrow(try bin.shutdown()) } - let client = makeDefaultHTTPClient() - defer { XCTAssertNoThrow(try client.syncShutdown()) } - let request = try HTTPClient.Request(url: "https://localhost:\(bin.port)/get") + // All the bytes outside the ASCII range are fine though. + for byte in UInt8(128)...UInt8(255) { + let evenWeirderAllowedValue = weirdAllowedFieldValue + String(decoding: [byte], as: UTF8.self) + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") + request.headers.add(name: "Weird-Value", value: evenWeirderAllowedValue) + + // This should work fine. guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request: request).get() + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) ) else { return } XCTAssertEqual(response.status, .ok) - XCTAssertEqual(response.version, .http2) } } } - extension AsyncSequence where Element == ByteBuffer { - func collect() async rethrows -> ByteBuffer { - try await self.reduce(into: ByteBuffer()) { accumulatingBuffer, nextBuffer in - var nextBuffer = nextBuffer - accumulatingBuffer.writeBuffer(&nextBuffer) - ) else { return } - XCTAssertEqual(response.headers["content-length"], ["6"]) - await XCTAssertThrowsError( - try await response.body.collect(maxBytes: 1)) - } - } - } - - func testBytesOverflowError() { + func testUsingGetMethodInsteadOfWait() { guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } XCTAsyncTest { - let bin = HTTPBin(.http2(compress: false)) { _ in HTTPEchoHandler() } + let bin = HTTPBin(.http2(compress: false)) defer { XCTAssertNoThrow(try bin.shutdown()) } let client = makeDefaultHTTPClient() defer { XCTAssertNoThrow(try client.syncShutdown()) } - let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) - var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/") - request.method = .POST - request.body = .bytes(ByteBuffer(string: "123456")) + let request = try HTTPClient.Request(url: "https://localhost:\(bin.port)/get") guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } - XCTAssertEqual(response.headers["content-length"], ["6"]) - await XCTAssertThrowsError( - try await response.body.collect(maxBytes: 1)) + try await client.execute(request: request).get() + ) else { + return + } + + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(response.version, .http2) } } } + struct AnySendableSequence: @unchecked Sendable { private let wrapped: AnySequence init( From 9b6381501959969e5f6429f037706701d53b725f Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Tue, 21 Mar 2023 13:54:14 +0000 Subject: [PATCH 07/16] Checking the Content Length through requestMethod and status --- .../AsyncAwait/HTTPClientResponse.swift | 58 +++++++++++-- .../AsyncAwait/Transaction.swift | 3 +- .../AsyncAwaitEndToEndTests.swift | 18 ++-- .../HTTPClientResponseTests.swift | 85 +++++++++++++++++++ 4 files changed, 145 insertions(+), 19 deletions(-) create mode 100644 Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index ec444511a..c0e2cb2bf 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -32,16 +32,31 @@ public struct HTTPClientResponse: Sendable { /// The body of this HTTP response. public var body: Body + @usableFromInline internal var requestMethod: HTTPMethod? + + @inlinable init( - bag: Transaction, - version: HTTPVersion, - status: HTTPResponseStatus, - headers: HTTPHeaders + version: HTTPVersion = .http1_1, + status: HTTPResponseStatus = .ok, + headers: HTTPHeaders = [:], + body: Body = Body(), + requestMethod: HTTPMethod? ) { self.version = version self.status = status self.headers = headers - self.body = Body(TransactionBody(bag)) + self.body = body + self.requestMethod = requestMethod + } + + init( + bag: Transaction, + version: HTTPVersion, + status: HTTPResponseStatus, + headers: HTTPHeaders, + requestMethod: HTTPMethod + ) { + self.init(version: version, status: status, headers: headers, body: .init(TransactionBody(bag)), requestMethod: requestMethod) } @inlinable public init( @@ -50,10 +65,7 @@ public struct HTTPClientResponse: Sendable { headers: HTTPHeaders = [:], body: Body = Body() ) { - self.version = version - self.status = status - self.headers = headers - self.body = body + self.init(version: version, status: status, headers: headers, body: body, requestMethod: nil) } } @@ -95,6 +107,34 @@ extension HTTPClientResponse { } } +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +extension HTTPClientResponse { + /// Accumulates `Body` of ``ByteBuffer``s into a single ``ByteBuffer``. + /// - Parameters: + /// - maxBytes: The maximum number of bytes this method is allowed to accumulate + /// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`. + /// - Returns: the number of bytes collected over time + @inlinable public func collect(upTo maxBytes: Int) async throws -> ByteBuffer { + if let contentLength = expectedContentLength() { + if contentLength > maxBytes { + throw NIOTooManyBytesError() + } + } + return try await self.body.collect(upTo: maxBytes) + } + @inlinable func expectedContentLength() -> Int? { + if let requestMethod = self.requestMethod { + if self.status == .notModified { + return 0 + } else if requestMethod == .HEAD { + return 0 + } + } + let contentLengths = headers["content-length"].first.flatMap({Int($0, radix: 10)}) + return contentLengths + } +} + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientResponse.Body { @usableFromInline enum Storage: Sendable { diff --git a/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift b/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift index d81fbfd28..38412d528 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift @@ -236,7 +236,8 @@ extension Transaction: HTTPExecutableRequest { bag: self, version: head.version, status: head.status, - headers: head.headers + headers: head.headers, + requestMethod: requestHead.method ) continuation.resume(returning: asyncResponse) } diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index 33dda1cbc..96f41d0e3 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -114,7 +114,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], ["4"]) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(maxBytes: 1024) + try await response.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -137,7 +137,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(maxBytes: 1024) + try await response.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -160,7 +160,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(maxBytes: 1024) + try await response.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -183,7 +183,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], ["4"]) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(maxBytes: 1024) + try await response.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -210,7 +210,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(maxBytes: 1024) + try await response.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -233,7 +233,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(maxBytes: 1024) + try await response.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -522,7 +522,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } - guard let body = await XCTAssertNoThrowWithResult(try await response.body.collect(maxBytes: 1024)) else { return } + guard let body = await XCTAssertNoThrowWithResult(try await response.collect(upTo: 1024)) else { return } var maybeRequestInfo: RequestInfo? XCTAssertNoThrow(maybeRequestInfo = try JSONDecoder().decode(RequestInfo.self, from: body)) guard let requestInfo = maybeRequestInfo else { return } @@ -583,7 +583,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response1.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response1.body.collect(maxBytes: 1024) + try await response1.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) @@ -592,7 +592,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response2.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response2.body.collect(maxBytes: 1024) + try await response2.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift new file mode 100644 index 000000000..7ff0cc14c --- /dev/null +++ b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2023 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + + + +@testable import AsyncHTTPClient +import Logging +import NIOCore +import XCTest + + +private func makeDefaultHTTPClient( + eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .createNew +) -> HTTPClient { + var config = HTTPClient.Configuration() + config.tlsConfiguration = .clientDefault + config.tlsConfiguration?.certificateVerification = .none + config.httpVersion = .automatic + return HTTPClient( + eventLoopGroupProvider: eventLoopGroupProvider, + configuration: config, + backgroundActivityLogger: Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) + ) +} + +final class HTTPClientResponseTests: XCTestCase { + func testReponseInit() { + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + let response = HTTPClientResponse(requestMethod: .HEAD) + XCTAssertEqual(response.headers["content-length"], []) + guard let body = await XCTAssertNoThrowWithResult( + try await response.collect(upTo: 1024) + ) else { return } + XCTAssertEqual(0, body.readableBytes) + } + } + + func testReponseInitFail() { + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + var response = HTTPClientResponse(requestMethod: .HEAD) + response.headers.replaceOrAdd(name: "content-length", value: "1025") + guard let body = await XCTAssertNoThrowWithResult( + try await response.collect(upTo: 1024) + ) else { return } + XCTAssertEqual(0, body.readableBytes) + } + } + + func testReponseInitThrows() { + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + var response = HTTPClientResponse(requestMethod: .GET) + response.headers.replaceOrAdd(name: "content-length", value: "1025") + await XCTAssertThrowsError( + try await response.collect(upTo: 1024) + ) { + XCTAssertEqualTypeAndValue($0, NIOTooManyBytesError()) + } + } + } + func testReponseInitWithStatus() { + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + var response = HTTPClientResponse(status: .notModified , requestMethod: .GET) + response.headers.replaceOrAdd(name: "content-length", value: "1025") + guard let body = await XCTAssertNoThrowWithResult( + try await response.collect(upTo: 1024) + ) else { return } + XCTAssertEqual(0, body.readableBytes) + } + } +} From e8d8f859359ccc1119166fdd19fc1260aad9d977 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Tue, 21 Mar 2023 13:56:56 +0000 Subject: [PATCH 08/16] minor move --- .../AsyncAwait/HTTPClientResponse.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index c0e2cb2bf..343f377ba 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -96,20 +96,12 @@ extension HTTPClientResponse { .init(storage: self.storage.makeAsyncIterator()) } - /// Accumulates `Body` of ``ByteBuffer``s into a single ``ByteBuffer``. - /// - Parameters: - /// - maxBytes: The maximum number of bytes this method is allowed to accumulate - /// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`. - /// - Returns: the number of bytes collected over time - func collect(maxBytes: Int) async throws -> ByteBuffer { - return try await self.collect(upTo: maxBytes) - } } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientResponse { - /// Accumulates `Body` of ``ByteBuffer``s into a single ``ByteBuffer``. + /// Accumulates `AsyncSequence` of ``ByteBuffer``s into a single ``ByteBuffer``. /// - Parameters: /// - maxBytes: The maximum number of bytes this method is allowed to accumulate /// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`. From 0de9e27b55f097bb11b81ad3cc645d52bd381ad8 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Wed, 22 Mar 2023 15:46:35 +0000 Subject: [PATCH 09/16] Changing placement and fixing tests to expectecdContentLength --- .../AsyncAwait/HTTPClientResponse.swift | 54 ++++++++++++------- .../AsyncAwait/TransactionBody.swift | 4 +- .../AsyncAwaitEndToEndTests.swift | 43 +++++++++++---- .../HTTPClientResponseTests.swift | 48 +++++------------ 4 files changed, 85 insertions(+), 64 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index 343f377ba..1c046e8b6 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -56,7 +56,8 @@ public struct HTTPClientResponse: Sendable { headers: HTTPHeaders, requestMethod: HTTPMethod ) { - self.init(version: version, status: status, headers: headers, body: .init(TransactionBody(bag)), requestMethod: requestMethod) + let contentLength = HTTPClientResponse.expectedContentLength(requestMethod: requestMethod, headers: headers, status: status) + self.init(version: version, status: status, headers: headers, body: .init(TransactionBody(bag, expextedContentLength: contentLength)), requestMethod: requestMethod) } @inlinable public init( @@ -96,27 +97,40 @@ extension HTTPClientResponse { .init(storage: self.storage.makeAsyncIterator()) } + /// Accumulates `Body` of ``ByteBuffer``s into a single ``ByteBuffer``. + /// - Parameters: + /// - maxBytes: The maximum number of bytes this method is allowed to accumulate + /// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`. + /// - Returns: the number of bytes collected over time + @inlinable public func collect(upTo maxBytes: Int) async throws -> ByteBuffer { + switch storage { + case .transaction(let transactionBody): + if let contentLength = transactionBody.expectedContentLength { + print(maxBytes) + print("contentLength", contentLength) + if contentLength > maxBytes { + throw NIOTooManyBytesError() + } + } + case .anyAsyncSequence: + break + } + func collect(_ body: Body, maxBytes: Int) async throws -> ByteBuffer where Body.Element == ByteBuffer { + try await body.collect(upTo: maxBytes) + } + return try await collect(self, maxBytes: maxBytes) + + } + } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientResponse { - /// Accumulates `AsyncSequence` of ``ByteBuffer``s into a single ``ByteBuffer``. - /// - Parameters: - /// - maxBytes: The maximum number of bytes this method is allowed to accumulate - /// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`. - /// - Returns: the number of bytes collected over time - @inlinable public func collect(upTo maxBytes: Int) async throws -> ByteBuffer { - if let contentLength = expectedContentLength() { - if contentLength > maxBytes { - throw NIOTooManyBytesError() - } - } - return try await self.body.collect(upTo: maxBytes) - } - @inlinable func expectedContentLength() -> Int? { - if let requestMethod = self.requestMethod { - if self.status == .notModified { +// need to pass in the arguments + static func expectedContentLength(requestMethod: HTTPMethod?, headers: HTTPHeaders, status: HTTPResponseStatus) -> Int? { + if let requestMethod { + if status == .notModified { return 0 } else if requestMethod == .HEAD { return 0 @@ -173,10 +187,10 @@ extension HTTPClientResponse.Body.Storage.AsyncIterator: AsyncIteratorProtocol { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientResponse.Body { init(_ body: TransactionBody) { - self.init(.transaction(body)) + self.init(.transaction(body), expectedContentLength: body.expectedContentLength) } - @usableFromInline init(_ storage: Storage) { + @usableFromInline init(_ storage: Storage, expectedContentLength: Int?) { self.storage = storage } @@ -187,7 +201,7 @@ extension HTTPClientResponse.Body { @inlinable public static func stream( _ sequenceOfBytes: SequenceOfBytes ) -> Self where SequenceOfBytes: AsyncSequence & Sendable, SequenceOfBytes.Element == ByteBuffer { - self.init(.anyAsyncSequence(AnyAsyncSequence(sequenceOfBytes.singleIteratorPrecondition))) + self.init(.anyAsyncSequence(AnyAsyncSequence(sequenceOfBytes.singleIteratorPrecondition)), expectedContentLength: nil) } public static func bytes(_ byteBuffer: ByteBuffer) -> Self { diff --git a/Sources/AsyncHTTPClient/AsyncAwait/TransactionBody.swift b/Sources/AsyncHTTPClient/AsyncAwait/TransactionBody.swift index 497a3cc72..819108ce3 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/TransactionBody.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/TransactionBody.swift @@ -20,9 +20,11 @@ import NIOCore @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @usableFromInline final class TransactionBody: Sendable { @usableFromInline let transaction: Transaction + @usableFromInline let expectedContentLength: Int? - init(_ transaction: Transaction) { + init(_ transaction: Transaction, expextedContentLength: Int?) { self.transaction = transaction + self.expectedContentLength = expextedContentLength } deinit { diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index 96f41d0e3..f20aa2161 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -114,7 +114,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], ["4"]) guard let body = await XCTAssertNoThrowWithResult( - try await response.collect(upTo: 1024) + try await response.body.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -137,7 +137,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.collect(upTo: 1024) + try await response.body.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -160,7 +160,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.collect(upTo: 1024) + try await response.body.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -183,7 +183,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], ["4"]) guard let body = await XCTAssertNoThrowWithResult( - try await response.collect(upTo: 1024) + try await response.body.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -210,7 +210,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.collect(upTo: 1024) + try await response.body.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -233,7 +233,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response.collect(upTo: 1024) + try await response.body.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -522,7 +522,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } - guard let body = await XCTAssertNoThrowWithResult(try await response.collect(upTo: 1024)) else { return } + guard let body = await XCTAssertNoThrowWithResult(try await response.body.collect(upTo: 1024)) else { return } var maybeRequestInfo: RequestInfo? XCTAssertNoThrow(maybeRequestInfo = try JSONDecoder().decode(RequestInfo.self, from: body)) guard let requestInfo = maybeRequestInfo else { return } @@ -583,7 +583,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response1.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response1.collect(upTo: 1024) + try await response1.body.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) @@ -592,7 +592,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { ) else { return } XCTAssertEqual(response2.headers["content-length"], []) guard let body = await XCTAssertNoThrowWithResult( - try await response2.collect(upTo: 1024) + try await response2.body.collect(upTo: 1024) ) else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } @@ -744,6 +744,31 @@ final class AsyncAwaitEndToEndTests: XCTestCase { XCTAssertEqual(response.version, .http2) } } + + func testSimpleContentLengthError() { + guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } + XCTAsyncTest { + let bin = HTTPBin(.http2(compress: false)) { _ in HTTPEchoHandler() } + defer { XCTAssertNoThrow(try bin.shutdown()) } + let client = makeDefaultHTTPClient() + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) + var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/") + request.method = .GET + request.body = .bytes(ByteBuffer(string: "1235")) + + guard var response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) else { return } + response.headers.replaceOrAdd(name: "content-length", value: "1025") + XCTAssertEqual(response.headers["content-length"], ["1025"]) + await XCTAssertThrowsError( + try await response.body.collect(upTo: 3) + ) { + XCTAssertEqualTypeAndValue($0, NIOTooManyBytesError()) + } + } + } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift index 7ff0cc14c..607f793c9 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift @@ -35,49 +35,29 @@ private func makeDefaultHTTPClient( } final class HTTPClientResponseTests: XCTestCase { - func testReponseInit() { - guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } - XCTAsyncTest { - let response = HTTPClientResponse(requestMethod: .HEAD) - XCTAssertEqual(response.headers["content-length"], []) - guard let body = await XCTAssertNoThrowWithResult( - try await response.collect(upTo: 1024) - ) else { return } - XCTAssertEqual(0, body.readableBytes) - } + + func testSimpleResponse() { + let response = HTTPClientResponse.expectedContentLength(requestMethod: .GET, headers: ["content-length": "1025"], status: .ok) + XCTAssertEqual(response, 1025) } - - func testReponseInitFail() { - guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } - XCTAsyncTest { - var response = HTTPClientResponse(requestMethod: .HEAD) - response.headers.replaceOrAdd(name: "content-length", value: "1025") - guard let body = await XCTAssertNoThrowWithResult( - try await response.collect(upTo: 1024) - ) else { return } - XCTAssertEqual(0, body.readableBytes) - } + + func testSimpleResponseNotModified() { + let response = HTTPClientResponse.expectedContentLength(requestMethod: .GET, headers: ["content-length": "1025"], status: .notModified) + XCTAssertEqual(response, 0) } - - func testReponseInitThrows() { - guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } - XCTAsyncTest { - var response = HTTPClientResponse(requestMethod: .GET) - response.headers.replaceOrAdd(name: "content-length", value: "1025") - await XCTAssertThrowsError( - try await response.collect(upTo: 1024) - ) { - XCTAssertEqualTypeAndValue($0, NIOTooManyBytesError()) - } - } + + func testSimpleResponseHeadRequestMethod() { + let response = HTTPClientResponse.expectedContentLength(requestMethod: .HEAD, headers: ["content-length": "1025"], status: .ok) + XCTAssertEqual(response, 0) } + func testReponseInitWithStatus() { guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } XCTAsyncTest { var response = HTTPClientResponse(status: .notModified , requestMethod: .GET) response.headers.replaceOrAdd(name: "content-length", value: "1025") guard let body = await XCTAssertNoThrowWithResult( - try await response.collect(upTo: 1024) + try await response.body.collect(upTo: 1024) ) else { return } XCTAssertEqual(0, body.readableBytes) } From 045a063d3a3f9c702b3fa9a088be5c4fc5054e6b Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Thu, 23 Mar 2023 10:03:10 +0000 Subject: [PATCH 10/16] suggested fixes --- .../AsyncAwait/HTTPClientResponse.swift | 42 +++++++++++-------- .../AsyncAwaitEndToEndTests.swift | 4 +- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index 1c046e8b6..10deff920 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -32,7 +32,6 @@ public struct HTTPClientResponse: Sendable { /// The body of this HTTP response. public var body: Body - @usableFromInline internal var requestMethod: HTTPMethod? @inlinable init( @@ -46,7 +45,6 @@ public struct HTTPClientResponse: Sendable { self.status = status self.headers = headers self.body = body - self.requestMethod = requestMethod } init( @@ -97,6 +95,10 @@ extension HTTPClientResponse { .init(storage: self.storage.makeAsyncIterator()) } + @inlinable init(storage: Storage) { + self.storage = storage + } + /// Accumulates `Body` of ``ByteBuffer``s into a single ``ByteBuffer``. /// - Parameters: /// - maxBytes: The maximum number of bytes this method is allowed to accumulate @@ -106,8 +108,6 @@ extension HTTPClientResponse { switch storage { case .transaction(let transactionBody): if let contentLength = transactionBody.expectedContentLength { - print(maxBytes) - print("contentLength", contentLength) if contentLength > maxBytes { throw NIOTooManyBytesError() } @@ -115,6 +115,13 @@ extension HTTPClientResponse { case .anyAsyncSequence: break } + + /// <#Description#> + /// - Parameters: + /// - body: <#body description#> + /// - maxBytes: <#maxBytes description#> + /// - Throws: <#description#> + /// - Returns: <#description#> func collect(_ body: Body, maxBytes: Int) async throws -> ByteBuffer where Body.Element == ByteBuffer { try await body.collect(upTo: maxBytes) } @@ -127,17 +134,16 @@ extension HTTPClientResponse { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientResponse { -// need to pass in the arguments - static func expectedContentLength(requestMethod: HTTPMethod?, headers: HTTPHeaders, status: HTTPResponseStatus) -> Int? { - if let requestMethod { - if status == .notModified { - return 0 - } else if requestMethod == .HEAD { - return 0 - } - } - let contentLengths = headers["content-length"].first.flatMap({Int($0, radix: 10)}) - return contentLengths + static func expectedContentLength(requestMethod: HTTPMethod, headers: HTTPHeaders, status: HTTPResponseStatus) -> Int? { + if status == .notModified { + return 0 + } else if requestMethod == .HEAD { + return 0 + } + else { + let contentLength = headers["content-length"].first.flatMap({Int($0, radix: 10)}) + return contentLength + } } } @@ -187,10 +193,10 @@ extension HTTPClientResponse.Body.Storage.AsyncIterator: AsyncIteratorProtocol { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientResponse.Body { init(_ body: TransactionBody) { - self.init(.transaction(body), expectedContentLength: body.expectedContentLength) + self.init(storage: .transaction(body)) } - @usableFromInline init(_ storage: Storage, expectedContentLength: Int?) { + @inlinable init(_ storage: Storage) { self.storage = storage } @@ -201,7 +207,7 @@ extension HTTPClientResponse.Body { @inlinable public static func stream( _ sequenceOfBytes: SequenceOfBytes ) -> Self where SequenceOfBytes: AsyncSequence & Sendable, SequenceOfBytes.Element == ByteBuffer { - self.init(.anyAsyncSequence(AnyAsyncSequence(sequenceOfBytes.singleIteratorPrecondition)), expectedContentLength: nil) + Self.init(storage: .anyAsyncSequence(AnyAsyncSequence(sequenceOfBytes.singleIteratorPrecondition))) } public static func bytes(_ byteBuffer: ByteBuffer) -> Self { diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index f20aa2161..8cb19ef26 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -755,13 +755,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/") request.method = .GET - request.body = .bytes(ByteBuffer(string: "1235")) + request.body = .bytes(ByteBuffer(string: "1234")) guard var response = await XCTAssertNoThrowWithResult( try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) ) else { return } - response.headers.replaceOrAdd(name: "content-length", value: "1025") - XCTAssertEqual(response.headers["content-length"], ["1025"]) await XCTAssertThrowsError( try await response.body.collect(upTo: 3) ) { From 9dc6f336b0f5839f4508ed6a7e20e8256a688b11 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Thu, 23 Mar 2023 10:10:27 +0000 Subject: [PATCH 11/16] fixing comments and deleting excess functions --- .../AsyncAwait/HTTPClientResponse.swift | 7 +---- .../HTTPClientResponseTests.swift | 26 ------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index 10deff920..3d7fab871 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -116,12 +116,7 @@ extension HTTPClientResponse { break } - /// <#Description#> - /// - Parameters: - /// - body: <#body description#> - /// - maxBytes: <#maxBytes description#> - /// - Throws: <#description#> - /// - Returns: <#description#> + /// calling collect function within here in order to ensure the correct nested type func collect(_ body: Body, maxBytes: Int) async throws -> ByteBuffer where Body.Element == ByteBuffer { try await body.collect(upTo: maxBytes) } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift index 607f793c9..737e39a04 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift @@ -20,20 +20,6 @@ import NIOCore import XCTest -private func makeDefaultHTTPClient( - eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .createNew -) -> HTTPClient { - var config = HTTPClient.Configuration() - config.tlsConfiguration = .clientDefault - config.tlsConfiguration?.certificateVerification = .none - config.httpVersion = .automatic - return HTTPClient( - eventLoopGroupProvider: eventLoopGroupProvider, - configuration: config, - backgroundActivityLogger: Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) - ) -} - final class HTTPClientResponseTests: XCTestCase { func testSimpleResponse() { @@ -50,16 +36,4 @@ final class HTTPClientResponseTests: XCTestCase { let response = HTTPClientResponse.expectedContentLength(requestMethod: .HEAD, headers: ["content-length": "1025"], status: .ok) XCTAssertEqual(response, 0) } - - func testReponseInitWithStatus() { - guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } - XCTAsyncTest { - var response = HTTPClientResponse(status: .notModified , requestMethod: .GET) - response.headers.replaceOrAdd(name: "content-length", value: "1025") - guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(upTo: 1024) - ) else { return } - XCTAssertEqual(0, body.readableBytes) - } - } } From ecbcd90b7710b4e01802c123e18e05af912e82a4 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Thu, 23 Mar 2023 10:44:25 +0000 Subject: [PATCH 12/16] getting rid of request Method --- .../AsyncAwait/HTTPClientResponse.swift | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index 3d7fab871..d7a732166 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -33,13 +33,11 @@ public struct HTTPClientResponse: Sendable { public var body: Body - @inlinable - init( + @inlinable init( version: HTTPVersion = .http1_1, status: HTTPResponseStatus = .ok, headers: HTTPHeaders = [:], - body: Body = Body(), - requestMethod: HTTPMethod? + body: Body = Body() ) { self.version = version self.status = status @@ -55,16 +53,7 @@ public struct HTTPClientResponse: Sendable { requestMethod: HTTPMethod ) { let contentLength = HTTPClientResponse.expectedContentLength(requestMethod: requestMethod, headers: headers, status: status) - self.init(version: version, status: status, headers: headers, body: .init(TransactionBody(bag, expextedContentLength: contentLength)), requestMethod: requestMethod) - } - - @inlinable public init( - version: HTTPVersion = .http1_1, - status: HTTPResponseStatus = .ok, - headers: HTTPHeaders = [:], - body: Body = Body() - ) { - self.init(version: version, status: status, headers: headers, body: body, requestMethod: nil) + self.init(version: version, status: status, headers: headers, body: .init(TransactionBody(bag, expextedContentLength: contentLength))) } } @@ -134,8 +123,7 @@ extension HTTPClientResponse { return 0 } else if requestMethod == .HEAD { return 0 - } - else { + } else { let contentLength = headers["content-length"].first.flatMap({Int($0, radix: 10)}) return contentLength } From ae17940e9bc227223ec1a7f77d686c342f694100 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Thu, 23 Mar 2023 11:08:16 +0000 Subject: [PATCH 13/16] adding tests for expectedContentLength and public init --- .../AsyncAwait/HTTPClientResponse.swift | 2 +- .../AsyncHTTPClientTests/HTTPClientResponseTests.swift | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index d7a732166..defcf93c9 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -33,7 +33,7 @@ public struct HTTPClientResponse: Sendable { public var body: Body - @inlinable init( + @inlinable public init( version: HTTPVersion = .http1_1, status: HTTPResponseStatus = .ok, headers: HTTPHeaders = [:], diff --git a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift index 737e39a04..ca50ad84d 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift @@ -36,4 +36,14 @@ final class HTTPClientResponseTests: XCTestCase { let response = HTTPClientResponse.expectedContentLength(requestMethod: .HEAD, headers: ["content-length": "1025"], status: .ok) XCTAssertEqual(response, 0) } + + func testResponseNoContentLengthHeader() { + let response = HTTPClientResponse.expectedContentLength(requestMethod: .GET, headers: [:], status: .ok) + XCTAssertEqual(response, nil) + } + + func testResponseInvalidInteger() { + let response = HTTPClientResponse.expectedContentLength(requestMethod: .GET, headers: ["content-length": "none"], status: .ok) + XCTAssertEqual(response, nil) + } } From 483a3f2bff62676e59b5afc042b996990cf569b0 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Mon, 27 Mar 2023 12:52:30 +0100 Subject: [PATCH 14/16] Fixing test and spelling --- .../AsyncAwait/HTTPClientResponse.swift | 2 +- .../AsyncHTTPClient/AsyncAwait/TransactionBody.swift | 4 ++-- .../AsyncAwaitEndToEndTests.swift | 11 ++++------- Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift | 5 +++++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index defcf93c9..2be22f1e5 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -53,7 +53,7 @@ public struct HTTPClientResponse: Sendable { requestMethod: HTTPMethod ) { let contentLength = HTTPClientResponse.expectedContentLength(requestMethod: requestMethod, headers: headers, status: status) - self.init(version: version, status: status, headers: headers, body: .init(TransactionBody(bag, expextedContentLength: contentLength))) + self.init(version: version, status: status, headers: headers, body: .init(TransactionBody(bag, expectedContentLength: contentLength))) } } diff --git a/Sources/AsyncHTTPClient/AsyncAwait/TransactionBody.swift b/Sources/AsyncHTTPClient/AsyncAwait/TransactionBody.swift index 819108ce3..23a8e505e 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/TransactionBody.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/TransactionBody.swift @@ -22,9 +22,9 @@ import NIOCore @usableFromInline let transaction: Transaction @usableFromInline let expectedContentLength: Int? - init(_ transaction: Transaction, expextedContentLength: Int?) { + init(_ transaction: Transaction, expectedContentLength: Int?) { self.transaction = transaction - self.expectedContentLength = expextedContentLength + self.expectedContentLength = expectedContentLength } deinit { diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index 8cb19ef26..7f108efe4 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -745,19 +745,16 @@ final class AsyncAwaitEndToEndTests: XCTestCase { } } - func testSimpleContentLengthError() { + func testSimpleContentLengthErrorNoBody() { guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } XCTAsyncTest { - let bin = HTTPBin(.http2(compress: false)) { _ in HTTPEchoHandler() } + let bin = HTTPBin(.http2(compress: false)) defer { XCTAssertNoThrow(try bin.shutdown()) } let client = makeDefaultHTTPClient() defer { XCTAssertNoThrow(try client.syncShutdown()) } let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) - var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/") - request.method = .GET - request.body = .bytes(ByteBuffer(string: "1234")) - - guard var response = await XCTAssertNoThrowWithResult( + let request = HTTPClientRequest(url: "https://localhost:\(bin.port)/content-length-without-body") + guard let response = await XCTAssertNoThrowWithResult( try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) ) else { return } await XCTAssertThrowsError( diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index ca24cba1c..06a652b24 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -943,6 +943,11 @@ internal final class HTTPBinHandler: ChannelInboundHandler { // We're forcing this closed now. self.shouldClose = true self.resps.append(builder) + case "/content-length-without-body": + var headers = self.responseHeaders + headers.replaceOrAdd(name: "content-length", value: "1234") + context.writeAndFlush(wrapOutboundOut(.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok, headers: headers))), promise: nil) + return default: context.write(wrapOutboundOut(.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .notFound))), promise: nil) context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) From 14cfdd498665353907d6c76e6a594a6bb0058fdb Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:31:09 +0100 Subject: [PATCH 15/16] running generate_linux_tests.rb --- .../AsyncAwaitEndToEndTests+XCTest.swift | 1 + .../HTTPClientResponseTests+XCTest.swift | 35 +++++++++++++++++++ Tests/LinuxMain.swift | 1 + 3 files changed, 37 insertions(+) create mode 100644 Tests/AsyncHTTPClientTests/HTTPClientResponseTests+XCTest.swift diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests+XCTest.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests+XCTest.swift index 20538c43a..03dec91a1 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests+XCTest.swift @@ -50,6 +50,7 @@ extension AsyncAwaitEndToEndTests { ("testRejectsInvalidCharactersInHeaderFieldValues_http1", testRejectsInvalidCharactersInHeaderFieldValues_http1), ("testRejectsInvalidCharactersInHeaderFieldValues_http2", testRejectsInvalidCharactersInHeaderFieldValues_http2), ("testUsingGetMethodInsteadOfWait", testUsingGetMethodInsteadOfWait), + ("testSimpleContentLengthErrorNoBody", testSimpleContentLengthErrorNoBody), ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests+XCTest.swift new file mode 100644 index 000000000..0a1a7cab6 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests+XCTest.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +// +// HTTPClientResponseTests+XCTest.swift +// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension HTTPClientResponseTests { + static var allTests: [(String, (HTTPClientResponseTests) -> () throws -> Void)] { + return [ + ("testSimpleResponse", testSimpleResponse), + ("testSimpleResponseNotModified", testSimpleResponseNotModified), + ("testSimpleResponseHeadRequestMethod", testSimpleResponseHeadRequestMethod), + ("testResponseNoContentLengthHeader", testResponseNoContentLengthHeader), + ("testResponseInvalidInteger", testResponseInvalidInteger), + ] + } +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index ca8478326..886bf2b95 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -44,6 +44,7 @@ struct LinuxMain { testCase(HTTPClientNIOTSTests.allTests), testCase(HTTPClientReproTests.allTests), testCase(HTTPClientRequestTests.allTests), + testCase(HTTPClientResponseTests.allTests), testCase(HTTPClientSOCKSTests.allTests), testCase(HTTPClientTests.allTests), testCase(HTTPClientUncleanSSLConnectionShutdownTests.allTests), From e60d65b5b0c6c48ebd626048a49f7625cc8faa18 Mon Sep 17 00:00:00 2001 From: carolinacass <67160898+carolinacass@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:07:31 +0100 Subject: [PATCH 16/16] fixing formatting --- .../AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift | 9 +++------ Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift | 2 +- Sources/AsyncHTTPClient/HTTPClient.swift | 2 +- Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift | 4 ++-- Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift | 4 ---- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index 2be22f1e5..07904b681 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -32,7 +32,6 @@ public struct HTTPClientResponse: Sendable { /// The body of this HTTP response. public var body: Body - @inlinable public init( version: HTTPVersion = .http1_1, status: HTTPResponseStatus = .ok, @@ -94,7 +93,7 @@ extension HTTPClientResponse { /// - Throws: `NIOTooManyBytesError` if the the sequence contains more than `maxBytes`. /// - Returns: the number of bytes collected over time @inlinable public func collect(upTo maxBytes: Int) async throws -> ByteBuffer { - switch storage { + switch self.storage { case .transaction(let transactionBody): if let contentLength = transactionBody.expectedContentLength { if contentLength > maxBytes { @@ -110,9 +109,7 @@ extension HTTPClientResponse { try await body.collect(upTo: maxBytes) } return try await collect(self, maxBytes: maxBytes) - } - } } @@ -124,7 +121,7 @@ extension HTTPClientResponse { } else if requestMethod == .HEAD { return 0 } else { - let contentLength = headers["content-length"].first.flatMap({Int($0, radix: 10)}) + let contentLength = headers["content-length"].first.flatMap { Int($0, radix: 10) } return contentLength } } @@ -190,7 +187,7 @@ extension HTTPClientResponse.Body { @inlinable public static func stream( _ sequenceOfBytes: SequenceOfBytes ) -> Self where SequenceOfBytes: AsyncSequence & Sendable, SequenceOfBytes.Element == ByteBuffer { - Self.init(storage: .anyAsyncSequence(AnyAsyncSequence(sequenceOfBytes.singleIteratorPrecondition))) + Self(storage: .anyAsyncSequence(AnyAsyncSequence(sequenceOfBytes.singleIteratorPrecondition))) } public static func bytes(_ byteBuffer: ByteBuffer) -> Self { diff --git a/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift b/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift index 38412d528..8846f36a5 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift @@ -237,7 +237,7 @@ extension Transaction: HTTPExecutableRequest { version: head.version, status: head.status, headers: head.headers, - requestMethod: requestHead.method + requestMethod: self.requestHead.method ) continuation.resume(returning: asyncResponse) } diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 2f4368402..0bd7b6e93 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -645,7 +645,7 @@ public class HTTPClient { "ahc-el-preference": "\(eventLoopPreference)"]) let failedTask: Task? = self.stateLock.withLock { - switch state { + switch self.state { case .upAndRunning: return nil case .shuttingDown, .shutDown: diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index 7f108efe4..e1591684c 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -597,6 +597,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { XCTAssertEqual(body, ByteBuffer(string: "1234")) } } + func testRejectsInvalidCharactersInHeaderFieldNames_http1() { self._rejectsInvalidCharactersInHeaderFieldNames(mode: .http1_1(ssl: true)) } @@ -744,7 +745,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { XCTAssertEqual(response.version, .http2) } } - + func testSimpleContentLengthErrorNoBody() { guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } XCTAsyncTest { @@ -766,7 +767,6 @@ final class AsyncAwaitEndToEndTests: XCTestCase { } } - struct AnySendableSequence: @unchecked Sendable { private let wrapped: AnySequence init( diff --git a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift index ca50ad84d..bf0ecfeb9 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift @@ -12,16 +12,12 @@ // //===----------------------------------------------------------------------===// - - @testable import AsyncHTTPClient import Logging import NIOCore import XCTest - final class HTTPClientResponseTests: XCTestCase { - func testSimpleResponse() { let response = HTTPClientResponse.expectedContentLength(requestMethod: .GET, headers: ["content-length": "1025"], status: .ok) XCTAssertEqual(response, 1025)