From fcf642ba644e53277769cb498bb1f59bc2469cf5 Mon Sep 17 00:00:00 2001 From: Rick Newton-Rogers Date: Mon, 28 Oct 2024 15:15:54 +0000 Subject: [PATCH 1/3] Migrate to GitHub Actions Migrate CI to use GitHub Actions. Motivation: To migrate to GitHub actions and centralised infrastructure. Modifications: Changes of note: * Adopt swift-format using rules from SwiftNIO * Remove scripts and docker files which are no longer needed Result: Feature parity with old CI. --- .github/workflows/main.yml | 19 + .github/workflows/pull_request.yml | 30 + .github/workflows/pull_request_label.yml | 18 + .licenseignore | 35 + .swift-format | 62 + .swiftformat | 24 - .unacceptablelanguageignore | 1 + CONTRIBUTING.md | 6 +- Examples/GetHTML/GetHTML.swift | 2 +- Examples/GetJSON/GetJSON.swift | 2 +- Examples/Package.swift | 9 +- Package.swift | 5 +- .../AsyncAwait/HTTPClient+execute.swift | 67 +- .../HTTPClientRequest+Prepared.swift | 9 +- .../AsyncAwait/HTTPClientRequest.swift | 134 +- .../AsyncAwait/HTTPClientResponse.swift | 25 +- .../AsyncAwait/Transaction+StateMachine.swift | 138 +- .../AsyncAwait/Transaction.swift | 8 +- Sources/AsyncHTTPClient/Base64.swift | 252 +- .../BestEffortHashableTLSConfiguration.swift | 2 +- .../Configuration+BrowserLike.swift | 3 +- Sources/AsyncHTTPClient/ConnectionPool.swift | 8 +- .../HTTP1ProxyConnectHandler.swift | 16 +- .../ChannelHandler/SOCKSEventsHandler.swift | 2 +- .../ChannelHandler/TLSEventsHandler.swift | 2 +- .../HTTP1/HTTP1ClientChannelHandler.swift | 43 +- .../HTTP1/HTTP1Connection.swift | 10 +- .../HTTP1/HTTP1ConnectionStateMachine.swift | 20 +- .../HTTP2/HTTP2ClientRequestHandler.swift | 22 +- .../HTTP2/HTTP2Connection.swift | 29 +- .../HTTP2/HTTP2IdleHandler.swift | 12 +- .../HTTPConnectionPool+Factory.swift | 123 +- .../HTTPConnectionPool+Manager.swift | 18 +- .../ConnectionPool/HTTPConnectionPool.swift | 208 +- .../HTTPRequestStateMachine+Demand.swift | 8 +- .../HTTPRequestStateMachine.swift | 185 +- .../HTTPConnectionPool+Backoff.swift | 1 + .../HTTPConnectionPool+HTTP1Connections.swift | 28 +- ...HTTPConnectionPool+HTTP1StateMachine.swift | 23 +- .../HTTPConnectionPool+HTTP2Connections.swift | 79 +- ...HTTPConnectionPool+HTTP2StateMachine.swift | 62 +- .../HTTPConnectionPool+StateMachine.swift | 193 +- .../FileDownloadDelegate.swift | 11 +- .../FoundationExtensions.swift | 15 +- .../HTTPClient+HTTPCookie.swift | 27 +- .../AsyncHTTPClient/HTTPClient+Proxy.swift | 11 +- Sources/AsyncHTTPClient/HTTPClient.swift | 449 ++-- Sources/AsyncHTTPClient/HTTPHandler.swift | 144 +- Sources/AsyncHTTPClient/LRUCache.swift | 8 +- .../NIOTransportServices/NWErrorHandler.swift | 11 +- .../NWWaitingHandler.swift | 5 +- .../TLSConfiguration.swift | 39 +- Sources/AsyncHTTPClient/RedirectState.swift | 3 +- .../RequestBag+StateMachine.swift | 59 +- Sources/AsyncHTTPClient/RequestBag.swift | 20 +- .../AsyncHTTPClient/RequestValidation.swift | 39 +- Sources/AsyncHTTPClient/SSLContextCache.swift | 26 +- Sources/AsyncHTTPClient/Singleton.swift | 2 +- .../StringConvertibleInstances.swift | 2 +- Sources/AsyncHTTPClient/Utils.swift | 15 +- .../AsyncAwaitEndToEndTests.swift | 335 ++- .../AsyncTestHelpers.swift | 6 +- ...nPoolSizeConfigValueIsRespectedTests.swift | 7 +- .../EmbeddedChannel+HTTPConvenience.swift | 5 +- .../HTTP1ClientChannelHandlerTests.swift | 521 ++-- .../HTTP1ConnectionStateMachineTests.swift | 148 +- .../HTTP1ConnectionTests.swift | 540 +++-- .../HTTP1ProxyConnectHandlerTests.swift | 3 +- .../HTTP2ClientRequestHandlerTests.swift | 328 ++- .../HTTP2ClientTests.swift | 79 +- .../HTTP2ConnectionTests.swift | 155 +- .../HTTP2IdleHandlerTests.swift | 108 +- .../HTTPClient+SOCKSTests.swift | 53 +- .../AsyncHTTPClientTests/HTTPClientBase.swift | 24 +- .../HTTPClientCookieTests.swift | 39 +- ...TTPClientInformationalResponsesTests.swift | 53 +- .../HTTPClientInternalTests.swift | 151 +- .../HTTPClientNIOTSTests.swift | 40 +- .../HTTPClientRequestTests.swift | 647 +++-- .../HTTPClientResponseTests.swift | 27 +- .../HTTPClientTestUtils.swift | 279 ++- .../HTTPClientTests.swift | 2116 +++++++++++------ ...entUncleanSSLConnectionShutdownTests.swift | 79 +- .../HTTPConnectionPool+FactoryTests.swift | 119 +- ...PConnectionPool+HTTP1ConnectionsTest.swift | 129 +- .../HTTPConnectionPool+HTTP1StateTests.swift | 118 +- ...PConnectionPool+HTTP2ConnectionsTest.swift | 90 +- ...onnectionPool+HTTP2StateMachineTests.swift | 344 ++- .../HTTPConnectionPool+ManagerTests.swift | 43 +- ...HTTPConnectionPool+RequestQueueTests.swift | 3 +- .../HTTPConnectionPool+StateTestUtils.swift | 60 +- .../HTTPConnectionPoolTests.swift | 195 +- .../HTTPRequestStateMachineTests.swift | 537 ++++- .../IdleTimeoutNoReuseTests.swift | 7 +- .../AsyncHTTPClientTests/LRUCacheTests.swift | 3 +- .../Mocks/MockConnectionPool.swift | 28 +- .../Mocks/MockHTTPExecutableRequest.swift | 3 +- .../Mocks/MockRequestExecutor.swift | 34 +- .../Mocks/MockRequestQueuer.swift | 3 +- .../NWWaitingHandlerTests.swift | 14 +- .../NoBytesSentOverBodyLimitTests.swift | 7 +- .../RacePoolIdleConnectionsAndGetTests.swift | 13 +- .../RequestBagTests.swift | 517 ++-- .../RequestValidationTests.swift | 83 +- .../ResponseDelayGetTests.swift | 19 +- .../SOCKSEventsHandlerTests.swift | 3 +- .../AsyncHTTPClientTests/SOCKSTestUtils.swift | 49 +- .../SSLContextCacheTests.swift | 53 +- .../StressGetHttpsTests.swift | 19 +- .../TLSEventsHandlerTests.swift | 3 +- .../Transaction+StateMachineTests.swift | 25 +- .../TransactionTests.swift | 67 +- .../XCTest+AsyncAwait.swift | 32 +- docker/Dockerfile | 34 - docker/docker-compose.2204.510.yaml | 22 - docker/docker-compose.2204.58.yaml | 22 - docker/docker-compose.2204.59.yaml | 22 - docker/docker-compose.2204.main.yaml | 21 - docker/docker-compose.yaml | 45 - scripts/check-docs.sh | 23 - scripts/check_no_api_breakages.sh | 68 - scripts/generate_docs.sh | 114 - scripts/soundness.sh | 152 -- 123 files changed, 7272 insertions(+), 4443 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/pull_request.yml create mode 100644 .github/workflows/pull_request_label.yml create mode 100644 .licenseignore create mode 100644 .swift-format delete mode 100644 .swiftformat create mode 100644 .unacceptablelanguageignore delete mode 100644 docker/Dockerfile delete mode 100644 docker/docker-compose.2204.510.yaml delete mode 100644 docker/docker-compose.2204.58.yaml delete mode 100644 docker/docker-compose.2204.59.yaml delete mode 100644 docker/docker-compose.2204.main.yaml delete mode 100644 docker/docker-compose.yaml delete mode 100755 scripts/check-docs.sh delete mode 100755 scripts/check_no_api_breakages.sh delete mode 100755 scripts/generate_docs.sh delete mode 100755 scripts/soundness.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..1374edfb7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,19 @@ +name: Main + +on: + push: + branches: [main] + schedule: + - cron: "0 8,20 * * *" + +jobs: + unit-tests: + name: Unit tests + uses: apple/swift-nio/.github/workflows/unit_tests.yml@main + with: + linux_5_8_enabled: false + linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 000000000..6873117ab --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,30 @@ +name: PR + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + soundness: + name: Soundness + uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main + with: + license_header_check_project_name: "AsyncHTTPClient" + unit-tests: + name: Unit tests + uses: apple/swift-nio/.github/workflows/unit_tests.yml@main + with: + linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" + linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + + cxx-interop: + name: Cxx interop + uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main + + swift-6-language-mode: + name: Swift 6 Language Mode + uses: apple/swift-nio/.github/workflows/swift_6_language_mode.yml@main + if: false # Disabled for now. diff --git a/.github/workflows/pull_request_label.yml b/.github/workflows/pull_request_label.yml new file mode 100644 index 000000000..86f199f32 --- /dev/null +++ b/.github/workflows/pull_request_label.yml @@ -0,0 +1,18 @@ +name: PR label + +on: + pull_request: + types: [labeled, unlabeled, opened, reopened, synchronize] + +jobs: + semver-label-check: + name: Semantic Version label check + runs-on: ubuntu-latest + timeout-minutes: 1 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Check for Semantic Version label + uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main diff --git a/.licenseignore b/.licenseignore new file mode 100644 index 000000000..922c4f760 --- /dev/null +++ b/.licenseignore @@ -0,0 +1,35 @@ +.gitignore +**/.gitignore +.licenseignore +.gitattributes +.git-blame-ignore-revs +.mailfilter +.mailmap +.spi.yml +.swift-format +.editorconfig +.github/* +*.md +*.txt +*.yml +*.yaml +*.json +Package.swift +**/Package.swift +Package@-*.swift +**/Package@-*.swift +Package.resolved +**/Package.resolved +Makefile +*.modulemap +**/*.modulemap +**/*.docc/* +*.xcprivacy +**/*.xcprivacy +*.symlink +**/*.symlink +Dockerfile +**/Dockerfile +.dockerignore +Snippets/* +dev/git.commit.template diff --git a/.swift-format b/.swift-format new file mode 100644 index 000000000..7fa06fb30 --- /dev/null +++ b/.swift-format @@ -0,0 +1,62 @@ +{ + "version" : 1, + "indentation" : { + "spaces" : 4 + }, + "tabWidth" : 4, + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "spacesAroundRangeFormationOperators" : false, + "indentConditionalCompilationBlocks" : false, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : true, + "lineBreakBeforeEachGenericRequirement" : true, + "lineLength" : 120, + "maximumBlankLines" : 1, + "respectsExistingLineBreaks" : true, + "prioritizeKeepingFunctionOutputTogether" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : false, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : true, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "UseEarlyExits" : false, + "UseExplicitNilCheckInConditions" : false, + "UseLetInEveryBoundCaseVariable" : false, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : false, + "UseSynthesizedInitializer" : false, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + } +} diff --git a/.swiftformat b/.swiftformat deleted file mode 100644 index 7b7c486ea..000000000 --- a/.swiftformat +++ /dev/null @@ -1,24 +0,0 @@ -# file options - ---swiftversion 5.4 ---exclude .build - -# format options - ---self insert ---patternlet inline ---ranges nospace ---stripunusedargs unnamed-only ---ifdef no-indent ---extensionacl on-declarations ---disable typeSugar # https://github.com/nicklockwood/SwiftFormat/issues/636 ---disable andOperator ---disable wrapMultilineStatementBraces ---disable enumNamespaces ---disable redundantExtensionACL ---disable redundantReturn ---disable preferKeyPath ---disable sortedSwitchCases ---disable numberFormatting - -# rules diff --git a/.unacceptablelanguageignore b/.unacceptablelanguageignore new file mode 100644 index 000000000..d89f79f62 --- /dev/null +++ b/.unacceptablelanguageignore @@ -0,0 +1 @@ +NOTICE.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3803bb618..dddcb3ba4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,10 +65,10 @@ We require that your commit messages match our template. The easiest way to do t git config commit.template dev/git.commit.template -### Make sure Tests work on Linux -AsyncHTTPClient uses XCTest to run tests on both macOS and Linux. While the macOS version of XCTest is able to use the Objective-C runtime to discover tests at execution time, the Linux version is not. -For this reason, whenever you add new tests **you have to run a script** that generates the hooks needed to run those tests on Linux, or our CI will complain that the tests are not all present on Linux. To do this, merely execute `ruby ./scripts/generate_linux_tests.rb` at the root of the package and check the changes it made. +### Run CI checks locally + +You can run the Github Actions workflows locally using [act](https://github.com/nektos/act). For detailed steps on how to do this please see [https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally](https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally). ## How to contribute your work diff --git a/Examples/GetHTML/GetHTML.swift b/Examples/GetHTML/GetHTML.swift index 98d6eb3c6..ca3bacbea 100644 --- a/Examples/GetHTML/GetHTML.swift +++ b/Examples/GetHTML/GetHTML.swift @@ -23,7 +23,7 @@ struct GetHTML { let request = HTTPClientRequest(url: "https://apple.com") let response = try await httpClient.execute(request, timeout: .seconds(30)) print("HTTP head", response) - let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB + let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB print(String(buffer: body)) } catch { print("request failed:", error) diff --git a/Examples/GetJSON/GetJSON.swift b/Examples/GetJSON/GetJSON.swift index 8f77c4a89..1af7a5144 100644 --- a/Examples/GetJSON/GetJSON.swift +++ b/Examples/GetJSON/GetJSON.swift @@ -38,7 +38,7 @@ struct GetJSON { let request = HTTPClientRequest(url: "https://xkcd.com/info.0.json") let response = try await httpClient.execute(request, timeout: .seconds(30)) print("HTTP head", response) - let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB + let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB // we use an overload defined in `NIOFoundationCompat` for `decode(_:from:)` to // efficiently decode from a `ByteBuffer` let comic = try JSONDecoder().decode(Comic.self, from: body) diff --git a/Examples/Package.swift b/Examples/Package.swift index 696092cba..9986b17b5 100644 --- a/Examples/Package.swift +++ b/Examples/Package.swift @@ -43,7 +43,8 @@ let package = Package( dependencies: [ .product(name: "AsyncHTTPClient", package: "async-http-client"), .product(name: "NIOCore", package: "swift-nio"), - ], path: "GetHTML" + ], + path: "GetHTML" ), .executableTarget( name: "GetJSON", @@ -51,14 +52,16 @@ let package = Package( .product(name: "AsyncHTTPClient", package: "async-http-client"), .product(name: "NIOCore", package: "swift-nio"), .product(name: "NIOFoundationCompat", package: "swift-nio"), - ], path: "GetJSON" + ], + path: "GetJSON" ), .executableTarget( name: "StreamingByteCounter", dependencies: [ .product(name: "AsyncHTTPClient", package: "async-http-client"), .product(name: "NIOCore", package: "swift-nio"), - ], path: "StreamingByteCounter" + ], + path: "StreamingByteCounter" ), ] ) diff --git a/Package.swift b/Package.swift index b645ee198..bec6c9114 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ import PackageDescription let package = Package( name: "async-http-client", products: [ - .library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]), + .library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]) ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.71.0"), @@ -28,14 +28,13 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.19.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.4.4"), .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), - .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.0.0"), .package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"), ], targets: [ .target( name: "CAsyncHTTPClient", cSettings: [ - .define("_GNU_SOURCE"), + .define("_GNU_SOURCE") ] ), .target( diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClient+execute.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClient+execute.swift index ef858443e..fc1dbc209 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClient+execute.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClient+execute.swift @@ -12,11 +12,12 @@ // //===----------------------------------------------------------------------===// -import struct Foundation.URL import Logging import NIOCore import NIOHTTP1 +import struct Foundation.URL + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClient { /// Execute arbitrary HTTP requests. @@ -85,11 +86,13 @@ extension HTTPClient { return response } - guard let redirectURL = response.headers.extractRedirectTarget( - status: response.status, - originalURL: preparedRequest.url, - originalScheme: preparedRequest.poolKey.scheme - ) else { + guard + let redirectURL = response.headers.extractRedirectTarget( + status: response.status, + originalURL: preparedRequest.url, + originalScheme: preparedRequest.poolKey.scheme + ) + else { // response does not want a redirect return response } @@ -120,31 +123,35 @@ extension HTTPClient { ) async throws -> HTTPClientResponse { let cancelHandler = TransactionCancelHandler() - return try await withTaskCancellationHandler(operation: { () async throws -> HTTPClientResponse in - let eventLoop = self.eventLoopGroup.any() - let deadlineTask = eventLoop.scheduleTask(deadline: deadline) { - cancelHandler.cancel(reason: .deadlineExceeded) - } - defer { - deadlineTask.cancel() - } - return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) -> Void in - let transaction = Transaction( - request: request, - requestOptions: .fromClientConfiguration(self.configuration), - logger: logger, - connectionDeadline: .now() + (self.configuration.timeout.connectionCreationTimeout), - preferredEventLoop: eventLoop, - responseContinuation: continuation - ) - - cancelHandler.registerTransaction(transaction) - - self.poolManager.executeRequest(transaction) + return try await withTaskCancellationHandler( + operation: { () async throws -> HTTPClientResponse in + let eventLoop = self.eventLoopGroup.any() + let deadlineTask = eventLoop.scheduleTask(deadline: deadline) { + cancelHandler.cancel(reason: .deadlineExceeded) + } + defer { + deadlineTask.cancel() + } + return try await withCheckedThrowingContinuation { + (continuation: CheckedContinuation) -> Void in + let transaction = Transaction( + request: request, + requestOptions: .fromClientConfiguration(self.configuration), + logger: logger, + connectionDeadline: .now() + (self.configuration.timeout.connectionCreationTimeout), + preferredEventLoop: eventLoop, + responseContinuation: continuation + ) + + cancelHandler.registerTransaction(transaction) + + self.poolManager.executeRequest(transaction) + } + }, + onCancel: { + cancelHandler.cancel(reason: .taskCanceled) } - }, onCancel: { - cancelHandler.cancel(reason: .taskCanceled) - }) + ) } } diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift index 0a3ec6442..d4eeae03e 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest+Prepared.swift @@ -12,11 +12,12 @@ // //===----------------------------------------------------------------------===// -import struct Foundation.URL import NIOCore import NIOHTTP1 import NIOSSL +import struct Foundation.URL + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientRequest { struct Prepared { @@ -81,7 +82,11 @@ extension HTTPClientRequest.Prepared.Body { case .asyncSequence(let length, let makeAsyncIterator): self = .asyncSequence(length: length, nextBodyPart: makeAsyncIterator()) case .sequence(let length, let canBeConsumedMultipleTimes, let makeCompleteBody): - self = .sequence(length: length, canBeConsumedMultipleTimes: canBeConsumedMultipleTimes, makeCompleteBody: makeCompleteBody) + self = .sequence( + length: length, + canBeConsumedMultipleTimes: canBeConsumedMultipleTimes, + makeCompleteBody: makeCompleteBody + ) case .byteBuffer(let byteBuffer): self = .byteBuffer(byteBuffer) } diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift index ad81bfa32..f07a2ed41 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift @@ -176,23 +176,29 @@ extension HTTPClientRequest.Body { // the maximum size of a ByteBuffer. if bufferPointer.count <= byteBufferMaxSize { let buffer = ByteBuffer(bytes: bufferPointer) - return Self(.sequence( - length: length.storage, - canBeConsumedMultipleTimes: true, - makeCompleteBody: { _ in buffer } - )) + return Self( + .sequence( + length: length.storage, + canBeConsumedMultipleTimes: true, + makeCompleteBody: { _ in buffer } + ) + ) } else { // we need to copy `bufferPointer` eagerly as the pointer is only valid during the call to `withContiguousStorageIfAvailable` - let buffers: Array = bufferPointer.chunks(ofCount: byteBufferMaxSize).map { ByteBuffer(bytes: $0) } - return Self(.asyncSequence( - length: length.storage, - makeAsyncIterator: { - var iterator = buffers.makeIterator() - return { _ in - iterator.next() + let buffers: [ByteBuffer] = bufferPointer.chunks(ofCount: byteBufferMaxSize).map { + ByteBuffer(bytes: $0) + } + return Self( + .asyncSequence( + length: length.storage, + makeAsyncIterator: { + var iterator = buffers.makeIterator() + return { _ in + iterator.next() + } } - } - )) + ) + ) } } if let body = body { @@ -200,21 +206,23 @@ extension HTTPClientRequest.Body { } // slow path - return Self(.asyncSequence( - length: length.storage - ) { - var iterator = bytes.makeIterator() - return { allocator in - var buffer = allocator.buffer(capacity: bagOfBytesToByteBufferConversionChunkSize) - while buffer.writableBytes > 0, let byte = iterator.next() { - buffer.writeInteger(byte) - } - if buffer.readableBytes > 0 { - return buffer + return Self( + .asyncSequence( + length: length.storage + ) { + var iterator = bytes.makeIterator() + return { allocator in + var buffer = allocator.buffer(capacity: bagOfBytesToByteBufferConversionChunkSize) + while buffer.writableBytes > 0, let byte = iterator.next() { + buffer.writeInteger(byte) + } + if buffer.readableBytes > 0 { + return buffer + } + return nil } - return nil } - }) + ) } /// Create an ``HTTPClientRequest/Body-swift.struct`` from a `Collection` of bytes. @@ -237,25 +245,29 @@ extension HTTPClientRequest.Body { length: Length ) -> Self where Bytes.Element == UInt8 { if bytes.count <= bagOfBytesToByteBufferConversionChunkSize { - return self.init(.sequence( - length: length.storage, - canBeConsumedMultipleTimes: true - ) { allocator in - allocator.buffer(bytes: bytes) - }) + return self.init( + .sequence( + length: length.storage, + canBeConsumedMultipleTimes: true + ) { allocator in + allocator.buffer(bytes: bytes) + } + ) } else { - return self.init(.asyncSequence( - length: length.storage, - makeAsyncIterator: { - var iterator = bytes.chunks(ofCount: bagOfBytesToByteBufferConversionChunkSize).makeIterator() - return { allocator in - guard let chunk = iterator.next() else { - return nil + return self.init( + .asyncSequence( + length: length.storage, + makeAsyncIterator: { + var iterator = bytes.chunks(ofCount: bagOfBytesToByteBufferConversionChunkSize).makeIterator() + return { allocator in + guard let chunk = iterator.next() else { + return nil + } + return allocator.buffer(bytes: chunk) } - return allocator.buffer(bytes: chunk) } - } - )) + ) + ) } } @@ -276,12 +288,14 @@ extension HTTPClientRequest.Body { _ sequenceOfBytes: SequenceOfBytes, length: Length ) -> Self where SequenceOfBytes.Element == ByteBuffer { - let body = self.init(.asyncSequence(length: length.storage) { - var iterator = sequenceOfBytes.makeAsyncIterator() - return { _ -> ByteBuffer? in - try await iterator.next() + let body = self.init( + .asyncSequence(length: length.storage) { + var iterator = sequenceOfBytes.makeAsyncIterator() + return { _ -> ByteBuffer? in + try await iterator.next() + } } - }) + ) return body } @@ -304,19 +318,21 @@ extension HTTPClientRequest.Body { _ bytes: Bytes, length: Length ) -> Self where Bytes.Element == UInt8 { - let body = self.init(.asyncSequence(length: length.storage) { - var iterator = bytes.makeAsyncIterator() - return { allocator -> ByteBuffer? in - var buffer = allocator.buffer(capacity: bagOfBytesToByteBufferConversionChunkSize) - while buffer.writableBytes > 0, let byte = try await iterator.next() { - buffer.writeInteger(byte) - } - if buffer.readableBytes > 0 { - return buffer + let body = self.init( + .asyncSequence(length: length.storage) { + var iterator = bytes.makeAsyncIterator() + return { allocator -> ByteBuffer? in + var buffer = allocator.buffer(capacity: bagOfBytesToByteBufferConversionChunkSize) + while buffer.writableBytes > 0, let byte = try await iterator.next() { + buffer.writeInteger(byte) + } + if buffer.readableBytes > 0 { + return buffer + } + return nil } - return nil } - }) + ) return body } } diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index 1ca01f53f..832eb7b41 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -55,14 +55,16 @@ public struct HTTPClientResponse: Sendable { version: version, status: status, headers: headers, - body: .init(.transaction( - body, - expectedContentLength: HTTPClientResponse.expectedContentLength( - requestMethod: requestMethod, - headers: headers, - status: status + body: .init( + .transaction( + body, + expectedContentLength: HTTPClientResponse.expectedContentLength( + requestMethod: requestMethod, + headers: headers, + status: status + ) ) - )) + ) ) } } @@ -116,7 +118,8 @@ extension HTTPClientResponse { } /// 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 { + 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) @@ -126,7 +129,11 @@ extension HTTPClientResponse { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientResponse { - static func expectedContentLength(requestMethod: HTTPMethod, headers: HTTPHeaders, status: HTTPResponseStatus) -> Int? { + static func expectedContentLength( + requestMethod: HTTPMethod, + headers: HTTPHeaders, + status: HTTPResponseStatus + ) -> Int? { if status == .notModified { return 0 } else if requestMethod == .HEAD { diff --git a/Sources/AsyncHTTPClient/AsyncAwait/Transaction+StateMachine.swift b/Sources/AsyncHTTPClient/AsyncAwait/Transaction+StateMachine.swift index ad49332c0..47b424f04 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/Transaction+StateMachine.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/Transaction+StateMachine.swift @@ -82,9 +82,20 @@ extension Transaction { enum FailAction { case none /// fail response before head received. scheduler and executor are exclusive here. - case failResponseHead(CheckedContinuation, Error, HTTPRequestScheduler?, HTTPRequestExecutor?, bodyStreamContinuation: CheckedContinuation?) + case failResponseHead( + CheckedContinuation, + Error, + HTTPRequestScheduler?, + HTTPRequestExecutor?, + bodyStreamContinuation: CheckedContinuation? + ) /// fail response after response head received. fail the response stream (aka call to `next()`) - case failResponseStream(TransactionBody.Source, Error, HTTPRequestExecutor, bodyStreamContinuation: CheckedContinuation?) + case failResponseStream( + TransactionBody.Source, + Error, + HTTPRequestExecutor, + bodyStreamContinuation: CheckedContinuation? + ) case failRequestStreamContinuation(CheckedContinuation, Error) } @@ -116,24 +127,41 @@ extension Transaction { switch requestStreamState { case .paused(continuation: .some(let continuation)): self.state = .finished(error: error) - return .failResponseHead(context.continuation, error, nil, context.executor, bodyStreamContinuation: continuation) + return .failResponseHead( + context.continuation, + error, + nil, + context.executor, + bodyStreamContinuation: continuation + ) case .requestHeadSent, .finished, .producing, .paused(continuation: .none): self.state = .finished(error: error) - return .failResponseHead(context.continuation, error, nil, context.executor, bodyStreamContinuation: nil) + return .failResponseHead( + context.continuation, + error, + nil, + context.executor, + bodyStreamContinuation: nil + ) } case .executing(let context, let requestStreamState, .streamingBody(let source)): self.state = .finished(error: error) switch requestStreamState { case .paused(let bodyStreamContinuation): - return .failResponseStream(source, error, context.executor, bodyStreamContinuation: bodyStreamContinuation) + return .failResponseStream( + source, + error, + context.executor, + bodyStreamContinuation: bodyStreamContinuation + ) case .finished, .producing, .requestHeadSent: return .failResponseStream(source, error, context.executor, bodyStreamContinuation: nil) } case .finished(error: _), - .executing(_, _, .finished): + .executing(_, _, .finished): return .none } } @@ -165,7 +193,7 @@ extension Transaction { return .cancel(executor) case .executing, - .finished(error: .none): + .finished(error: .none): preconditionFailure("Invalid state: \(self.state)") } } @@ -179,7 +207,9 @@ extension Transaction { mutating func resumeRequestBodyStream() -> ResumeProducingAction { switch self.state { case .initialized, .queued, .deadlineExceededWhileQueued: - preconditionFailure("Received a resumeBodyRequest on a request, that isn't executing. Invalid state: \(self.state)") + preconditionFailure( + "Received a resumeBodyRequest on a request, that isn't executing. Invalid state: \(self.state)" + ) case .executing(let context, .requestHeadSent, let responseState): // the request can start to send its body. @@ -187,7 +217,9 @@ extension Transaction { return .startStream(context.allocator) case .executing(_, .producing, _): - preconditionFailure("Received a resumeBodyRequest on a request, that is producing. Invalid state: \(self.state)") + preconditionFailure( + "Received a resumeBodyRequest on a request, that is producing. Invalid state: \(self.state)" + ) case .executing(let context, .paused(.none), let responseState): // request stream is currently paused, but there is no write waiting. We don't need @@ -213,17 +245,17 @@ extension Transaction { mutating func pauseRequestBodyStream() { switch self.state { case .initialized, - .queued, - .deadlineExceededWhileQueued, - .executing(_, .requestHeadSent, _): + .queued, + .deadlineExceededWhileQueued, + .executing(_, .requestHeadSent, _): preconditionFailure("A request stream can only be resumed, if the request was started") case .executing(let context, .producing, let responseSteam): self.state = .executing(context, .paused(continuation: nil), responseSteam) case .executing(_, .paused, _), - .executing(_, .finished, _), - .finished: + .executing(_, .finished, _), + .finished: // the channels writability changed to paused after we have already forwarded all // request bytes. Can be ignored. break @@ -239,10 +271,12 @@ extension Transaction { func writeNextRequestPart() -> NextWriteAction { switch self.state { case .initialized, - .queued, - .deadlineExceededWhileQueued, - .executing(_, .requestHeadSent, _): - preconditionFailure("A request stream can only produce, if the request was started. Invalid state: \(self.state)") + .queued, + .deadlineExceededWhileQueued, + .executing(_, .requestHeadSent, _): + preconditionFailure( + "A request stream can only produce, if the request was started. Invalid state: \(self.state)" + ) case .executing(let context, .producing, _): // We are currently producing the request body. The executors channel is writable. @@ -260,7 +294,9 @@ extension Transaction { return .writeAndWait(context.executor) case .executing(_, .paused(continuation: .some), _): - preconditionFailure("A write continuation already exists, but we tried to set another one. Invalid state: \(self.state)") + preconditionFailure( + "A write continuation already exists, but we tried to set another one. Invalid state: \(self.state)" + ) case .finished, .executing(_, .finished, _): return .fail @@ -270,11 +306,13 @@ extension Transaction { mutating func waitForRequestBodyDemand(continuation: CheckedContinuation) { switch self.state { case .initialized, - .queued, - .deadlineExceededWhileQueued, - .executing(_, .requestHeadSent, _), - .executing(_, .finished, _): - preconditionFailure("A request stream can only produce, if the request was started. Invalid state: \(self.state)") + .queued, + .deadlineExceededWhileQueued, + .executing(_, .requestHeadSent, _), + .executing(_, .finished, _): + preconditionFailure( + "A request stream can only produce, if the request was started. Invalid state: \(self.state)" + ) case .executing(_, .producing, _): preconditionFailure() @@ -303,17 +341,19 @@ extension Transaction { mutating func finishRequestBodyStream() -> FinishAction { switch self.state { case .initialized, - .queued, - .deadlineExceededWhileQueued, - .executing(_, .finished, _): + .queued, + .deadlineExceededWhileQueued, + .executing(_, .finished, _): preconditionFailure("Invalid state: \(self.state)") case .executing(_, .paused(continuation: .some), _): - preconditionFailure("Received a request body end, while having a registered back-pressure continuation. Invalid state: \(self.state)") + preconditionFailure( + "Received a request body end, while having a registered back-pressure continuation. Invalid state: \(self.state)" + ) case .executing(let context, .producing, let responseState), - .executing(let context, .paused(continuation: .none), let responseState), - .executing(let context, .requestHeadSent, let responseState): + .executing(let context, .paused(continuation: .none), let responseState), + .executing(let context, .requestHeadSent, let responseState): switch responseState { case .finished: @@ -345,10 +385,10 @@ extension Transaction { ) -> ReceiveResponseHeadAction { switch self.state { case .initialized, - .queued, - .deadlineExceededWhileQueued, - .executing(_, _, .streamingBody), - .executing(_, _, .finished): + .queued, + .deadlineExceededWhileQueued, + .executing(_, _, .streamingBody), + .executing(_, _, .finished): preconditionFailure("invalid state \(self.state)") case .executing(let context, let requestState, .waitingForResponseHead): @@ -381,15 +421,15 @@ extension Transaction { mutating func produceMore() -> ProduceMoreAction { switch self.state { case .initialized, - .queued, - .deadlineExceededWhileQueued, - .executing(_, _, .waitingForResponseHead): + .queued, + .deadlineExceededWhileQueued, + .executing(_, _, .waitingForResponseHead): preconditionFailure("invalid state \(self.state)") case .executing(let context, _, .streamingBody): return .requestMoreResponseBodyParts(context.executor) case .finished, - .executing(_, _, .finished): + .executing(_, _, .finished): return .none } } @@ -402,7 +442,9 @@ extension Transaction { mutating func receiveResponseBodyParts(_ buffer: CircularBuffer) -> ReceiveResponsePartAction { switch self.state { case .initialized, .queued, .deadlineExceededWhileQueued: - preconditionFailure("Received a response body part, but request hasn't started yet. Invalid state: \(self.state)") + preconditionFailure( + "Received a response body part, but request hasn't started yet. Invalid state: \(self.state)" + ) case .executing(_, _, .waitingForResponseHead): preconditionFailure("If we receive a response body, we must have received a head before") @@ -415,7 +457,9 @@ extension Transaction { return .none case .executing(_, _, .finished): - preconditionFailure("Received response end. Must not receive further body parts after that. Invalid state: \(self.state)") + preconditionFailure( + "Received response end. Must not receive further body parts after that. Invalid state: \(self.state)" + ) } } @@ -427,10 +471,12 @@ extension Transaction { mutating func succeedRequest(_ newChunks: CircularBuffer?) -> ReceiveResponseEndAction { switch self.state { case .initialized, - .queued, - .deadlineExceededWhileQueued, - .executing(_, _, .waitingForResponseHead): - preconditionFailure("Received no response head, but received a response end. Invalid state: \(self.state)") + .queued, + .deadlineExceededWhileQueued, + .executing(_, _, .waitingForResponseHead): + preconditionFailure( + "Received no response head, but received a response end. Invalid state: \(self.state)" + ) case .executing(let context, let requestState, .streamingBody(let source)): self.state = .executing(context, requestState, .finished) @@ -439,7 +485,9 @@ extension Transaction { // the request failed or was cancelled before, we can ignore all events return .none case .executing(_, _, .finished): - preconditionFailure("Already received an eof or error before. Must not receive further events. Invalid state: \(self.state)") + preconditionFailure( + "Already received an eof or error before. Must not receive further events. Invalid state: \(self.state)" + ) } } diff --git a/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift b/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift index 6d9192642..e420935f1 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift @@ -146,8 +146,8 @@ import NIOSSL @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension Transaction: HTTPSchedulableRequest { var poolKey: ConnectionPool.Key { self.request.poolKey } - var tlsConfiguration: TLSConfiguration? { return self.request.tlsConfiguration } - var requiredEventLoop: EventLoop? { return nil } + var tlsConfiguration: TLSConfiguration? { self.request.tlsConfiguration } + var requiredEventLoop: EventLoop? { nil } func requestWasQueued(_ scheduler: HTTPRequestScheduler) { self.stateLock.withLock { @@ -290,7 +290,7 @@ extension Transaction: HTTPExecutableRequest { case .failResponseHead(let continuation, let error, let scheduler, let executor, let bodyStreamContinuation): continuation.resume(throwing: error) bodyStreamContinuation?.resume(throwing: error) - scheduler?.cancelRequest(self) // NOTE: scheduler and executor are exclusive here + scheduler?.cancelRequest(self) // NOTE: scheduler and executor are exclusive here executor?.cancelRequest(self) case .failResponseStream(let source, let error, let executor, let requestBodyStreamContinuation): @@ -317,7 +317,7 @@ extension Transaction: HTTPExecutableRequest { scheduler?.cancelRequest(self) executor?.cancelRequest(self) bodyStreamContinuation?.resume(throwing: HTTPClientError.deadlineExceeded) - case .cancelSchedulerOnly(scheduler: let scheduler): + case .cancelSchedulerOnly(let scheduler): scheduler.cancelRequest(self) case .none: break diff --git a/Sources/AsyncHTTPClient/Base64.swift b/Sources/AsyncHTTPClient/Base64.swift index eed511a8c..3162e7251 100644 --- a/Sources/AsyncHTTPClient/Base64.swift +++ b/Sources/AsyncHTTPClient/Base64.swift @@ -19,142 +19,156 @@ extension String { - /// Base64 encode a collection of UInt8 to a string, without the use of Foundation. - @inlinable - init(base64Encoding bytes: Buffer) - where Buffer.Element == UInt8 - { - self = Base64.encode(bytes: bytes) - } + /// Base64 encode a collection of UInt8 to a string, without the use of Foundation. + @inlinable + init(base64Encoding bytes: Buffer) + where Buffer.Element == UInt8 { + self = Base64.encode(bytes: bytes) + } } +// swift-format-ignore: DontRepeatTypeInStaticProperties @usableFromInline internal struct Base64 { - @inlinable - static func encode(bytes: Buffer) - -> String where Buffer.Element == UInt8 - { - guard !bytes.isEmpty else { - return "" - } - // In Base64, 3 bytes become 4 output characters, and we pad to the - // nearest multiple of four. - let base64StringLength = ((bytes.count + 2) / 3) * 4 - let alphabet = Base64.encodeBase64 - - return String(customUnsafeUninitializedCapacity: base64StringLength) { backingStorage in - var input = bytes.makeIterator() - var offset = 0 - while let firstByte = input.next() { - let secondByte = input.next() - let thirdByte = input.next() - - backingStorage[offset] = Base64.encode(alphabet: alphabet, firstByte: firstByte) - backingStorage[offset + 1] = Base64.encode(alphabet: alphabet, firstByte: firstByte, secondByte: secondByte) - backingStorage[offset + 2] = Base64.encode(alphabet: alphabet, secondByte: secondByte, thirdByte: thirdByte) - backingStorage[offset + 3] = Base64.encode(alphabet: alphabet, thirdByte: thirdByte) - offset += 4 - } - return offset + @inlinable + static func encode( + bytes: Buffer + ) + -> String where Buffer.Element == UInt8 + { + guard !bytes.isEmpty else { + return "" + } + // In Base64, 3 bytes become 4 output characters, and we pad to the + // nearest multiple of four. + let base64StringLength = ((bytes.count + 2) / 3) * 4 + let alphabet = Base64.encodeBase64 + + return String(customUnsafeUninitializedCapacity: base64StringLength) { backingStorage in + var input = bytes.makeIterator() + var offset = 0 + while let firstByte = input.next() { + let secondByte = input.next() + let thirdByte = input.next() + + backingStorage[offset] = Base64.encode(alphabet: alphabet, firstByte: firstByte) + backingStorage[offset + 1] = Base64.encode( + alphabet: alphabet, + firstByte: firstByte, + secondByte: secondByte + ) + backingStorage[offset + 2] = Base64.encode( + alphabet: alphabet, + secondByte: secondByte, + thirdByte: thirdByte + ) + backingStorage[offset + 3] = Base64.encode(alphabet: alphabet, thirdByte: thirdByte) + offset += 4 + } + return offset + } } - } - - // MARK: Internal - - // The base64 unicode table. - @usableFromInline - static let encodeBase64: [UInt8] = [ - UInt8(ascii: "A"), UInt8(ascii: "B"), UInt8(ascii: "C"), UInt8(ascii: "D"), - UInt8(ascii: "E"), UInt8(ascii: "F"), UInt8(ascii: "G"), UInt8(ascii: "H"), - UInt8(ascii: "I"), UInt8(ascii: "J"), UInt8(ascii: "K"), UInt8(ascii: "L"), - UInt8(ascii: "M"), UInt8(ascii: "N"), UInt8(ascii: "O"), UInt8(ascii: "P"), - UInt8(ascii: "Q"), UInt8(ascii: "R"), UInt8(ascii: "S"), UInt8(ascii: "T"), - UInt8(ascii: "U"), UInt8(ascii: "V"), UInt8(ascii: "W"), UInt8(ascii: "X"), - UInt8(ascii: "Y"), UInt8(ascii: "Z"), UInt8(ascii: "a"), UInt8(ascii: "b"), - UInt8(ascii: "c"), UInt8(ascii: "d"), UInt8(ascii: "e"), UInt8(ascii: "f"), - UInt8(ascii: "g"), UInt8(ascii: "h"), UInt8(ascii: "i"), UInt8(ascii: "j"), - UInt8(ascii: "k"), UInt8(ascii: "l"), UInt8(ascii: "m"), UInt8(ascii: "n"), - UInt8(ascii: "o"), UInt8(ascii: "p"), UInt8(ascii: "q"), UInt8(ascii: "r"), - UInt8(ascii: "s"), UInt8(ascii: "t"), UInt8(ascii: "u"), UInt8(ascii: "v"), - UInt8(ascii: "w"), UInt8(ascii: "x"), UInt8(ascii: "y"), UInt8(ascii: "z"), - UInt8(ascii: "0"), UInt8(ascii: "1"), UInt8(ascii: "2"), UInt8(ascii: "3"), - UInt8(ascii: "4"), UInt8(ascii: "5"), UInt8(ascii: "6"), UInt8(ascii: "7"), - UInt8(ascii: "8"), UInt8(ascii: "9"), UInt8(ascii: "+"), UInt8(ascii: "/"), - ] - - static let encodePaddingCharacter: UInt8 = UInt8(ascii: "=") - - @usableFromInline - static func encode(alphabet: [UInt8], firstByte: UInt8) -> UInt8 { - let index = firstByte >> 2 - return alphabet[Int(index)] - } - - @usableFromInline - static func encode(alphabet: [UInt8], firstByte: UInt8, secondByte: UInt8?) -> UInt8 { - var index = (firstByte & 0b00000011) << 4 - if let secondByte = secondByte { - index += (secondByte & 0b11110000) >> 4 + + // MARK: Internal + + // The base64 unicode table. + @usableFromInline + static let encodeBase64: [UInt8] = [ + UInt8(ascii: "A"), UInt8(ascii: "B"), UInt8(ascii: "C"), UInt8(ascii: "D"), + UInt8(ascii: "E"), UInt8(ascii: "F"), UInt8(ascii: "G"), UInt8(ascii: "H"), + UInt8(ascii: "I"), UInt8(ascii: "J"), UInt8(ascii: "K"), UInt8(ascii: "L"), + UInt8(ascii: "M"), UInt8(ascii: "N"), UInt8(ascii: "O"), UInt8(ascii: "P"), + UInt8(ascii: "Q"), UInt8(ascii: "R"), UInt8(ascii: "S"), UInt8(ascii: "T"), + UInt8(ascii: "U"), UInt8(ascii: "V"), UInt8(ascii: "W"), UInt8(ascii: "X"), + UInt8(ascii: "Y"), UInt8(ascii: "Z"), UInt8(ascii: "a"), UInt8(ascii: "b"), + UInt8(ascii: "c"), UInt8(ascii: "d"), UInt8(ascii: "e"), UInt8(ascii: "f"), + UInt8(ascii: "g"), UInt8(ascii: "h"), UInt8(ascii: "i"), UInt8(ascii: "j"), + UInt8(ascii: "k"), UInt8(ascii: "l"), UInt8(ascii: "m"), UInt8(ascii: "n"), + UInt8(ascii: "o"), UInt8(ascii: "p"), UInt8(ascii: "q"), UInt8(ascii: "r"), + UInt8(ascii: "s"), UInt8(ascii: "t"), UInt8(ascii: "u"), UInt8(ascii: "v"), + UInt8(ascii: "w"), UInt8(ascii: "x"), UInt8(ascii: "y"), UInt8(ascii: "z"), + UInt8(ascii: "0"), UInt8(ascii: "1"), UInt8(ascii: "2"), UInt8(ascii: "3"), + UInt8(ascii: "4"), UInt8(ascii: "5"), UInt8(ascii: "6"), UInt8(ascii: "7"), + UInt8(ascii: "8"), UInt8(ascii: "9"), UInt8(ascii: "+"), UInt8(ascii: "/"), + ] + + static let encodePaddingCharacter: UInt8 = UInt8(ascii: "=") + + @usableFromInline + static func encode(alphabet: [UInt8], firstByte: UInt8) -> UInt8 { + let index = firstByte >> 2 + return alphabet[Int(index)] } - return alphabet[Int(index)] - } - - @usableFromInline - static func encode(alphabet: [UInt8], secondByte: UInt8?, thirdByte: UInt8?) -> UInt8 { - guard let secondByte = secondByte else { - // No second byte means we are just emitting padding. - return Base64.encodePaddingCharacter + + @usableFromInline + static func encode(alphabet: [UInt8], firstByte: UInt8, secondByte: UInt8?) -> UInt8 { + var index = (firstByte & 0b00000011) << 4 + if let secondByte = secondByte { + index += (secondByte & 0b11110000) >> 4 + } + return alphabet[Int(index)] } - var index = (secondByte & 0b00001111) << 2 - if let thirdByte = thirdByte { - index += (thirdByte & 0b11000000) >> 6 + + @usableFromInline + static func encode(alphabet: [UInt8], secondByte: UInt8?, thirdByte: UInt8?) -> UInt8 { + guard let secondByte = secondByte else { + // No second byte means we are just emitting padding. + return Base64.encodePaddingCharacter + } + var index = (secondByte & 0b00001111) << 2 + if let thirdByte = thirdByte { + index += (thirdByte & 0b11000000) >> 6 + } + return alphabet[Int(index)] } - return alphabet[Int(index)] - } - - @usableFromInline - static func encode(alphabet: [UInt8], thirdByte: UInt8?) -> UInt8 { - guard let thirdByte = thirdByte else { - // No third byte means just padding. - return Base64.encodePaddingCharacter + + @usableFromInline + static func encode(alphabet: [UInt8], thirdByte: UInt8?) -> UInt8 { + guard let thirdByte = thirdByte else { + // No third byte means just padding. + return Base64.encodePaddingCharacter + } + let index = thirdByte & 0b00111111 + return alphabet[Int(index)] } - let index = thirdByte & 0b00111111 - return alphabet[Int(index)] - } } extension String { - /// This is a backport of a proposed String initializer that will allow writing directly into an uninitialized String's backing memory. - /// - /// As this API does not exist prior to 5.3 on Linux, or on older Apple platforms, we fake it out with a pointer and accept the extra copy. - @inlinable - init(backportUnsafeUninitializedCapacity capacity: Int, - initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer) throws -> Int) rethrows { - // The buffer will store zero terminated C string - let buffer = UnsafeMutableBufferPointer.allocate(capacity: capacity + 1) - defer { - buffer.deallocate() + /// This is a backport of a proposed String initializer that will allow writing directly into an uninitialized String's backing memory. + /// + /// As this API does not exist prior to 5.3 on Linux, or on older Apple platforms, we fake it out with a pointer and accept the extra copy. + @inlinable + init( + backportUnsafeUninitializedCapacity capacity: Int, + initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer) throws -> Int + ) rethrows { + // The buffer will store zero terminated C string + let buffer = UnsafeMutableBufferPointer.allocate(capacity: capacity + 1) + defer { + buffer.deallocate() + } + + let initializedCount = try initializer(buffer) + precondition(initializedCount <= capacity, "Overran buffer in initializer!") + // add zero termination + buffer[initializedCount] = 0 + + self = String(cString: buffer.baseAddress!) } - - let initializedCount = try initializer(buffer) - precondition(initializedCount <= capacity, "Overran buffer in initializer!") - // add zero termination - buffer[initializedCount] = 0 - - self = String(cString: buffer.baseAddress!) - } } extension String { - @inlinable - init(customUnsafeUninitializedCapacity capacity: Int, - initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer) throws -> Int) rethrows { - if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) { - try self.init(unsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) - } else { - try self.init(backportUnsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) + @inlinable + init( + customUnsafeUninitializedCapacity capacity: Int, + initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer) throws -> Int + ) rethrows { + if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) { + try self.init(unsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) + } else { + try self.init(backportUnsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) + } } - } } diff --git a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift index 58169f645..aca0ce235 100644 --- a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift +++ b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift @@ -27,6 +27,6 @@ struct BestEffortHashableTLSConfiguration: Hashable { } static func == (lhs: BestEffortHashableTLSConfiguration, rhs: BestEffortHashableTLSConfiguration) -> Bool { - return lhs.base.bestEffortEquals(rhs.base) + lhs.base.bestEffortEquals(rhs.base) } } diff --git a/Sources/AsyncHTTPClient/Configuration+BrowserLike.swift b/Sources/AsyncHTTPClient/Configuration+BrowserLike.swift index b1ee8d5a9..39aefe975 100644 --- a/Sources/AsyncHTTPClient/Configuration+BrowserLike.swift +++ b/Sources/AsyncHTTPClient/Configuration+BrowserLike.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +// swift-format-ignore: DontRepeatTypeInStaticProperties extension HTTPClient.Configuration { /// The ``HTTPClient/Configuration`` for ``HTTPClient/shared`` which tries to mimic the platform's default or prevalent browser as closely as possible. /// @@ -27,7 +28,7 @@ extension HTTPClient.Configuration { /// - Linux (non-Android): Google Chrome public static var singletonConfiguration: HTTPClient.Configuration { // To start with, let's go with these values. Obtained from Firefox's config. - return HTTPClient.Configuration( + HTTPClient.Configuration( certificateVerification: .fullVerification, redirectConfiguration: .follow(max: 20, allowCycles: false), timeout: Timeout(connect: .seconds(90), read: .seconds(90)), diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index 8cca70750..776d1f6df 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -29,8 +29,7 @@ extension String { var ipv4Address = in_addr() var ipv6Address = in6_addr() return self.withCString { host in - inet_pton(AF_INET, host, &ipv4Address) == 1 || - inet_pton(AF_INET6, host, &ipv6Address) == 1 + inet_pton(AF_INET, host, &ipv4Address) == 1 || inet_pton(AF_INET6, host, &ipv6Address) == 1 } } } @@ -67,12 +66,13 @@ enum ConnectionPool { switch self.connectionTarget { case .ipAddress(let serialization, let addr): hostDescription = "\(serialization):\(addr.port!)" - case .domain(let domain, port: let port): + case .domain(let domain, let port): hostDescription = "\(domain):\(port)" case .unixSocket(let socketPath): hostDescription = socketPath } - return "\(self.scheme)://\(hostDescription)\(self.serverNameIndicatorOverride.map { " SNI: \($0)" } ?? "") TLS-hash: \(hash) " + return + "\(self.scheme)://\(hostDescription)\(self.serverNameIndicatorOverride.map { " SNI: \($0)" } ?? "") TLS-hash: \(hash) " } } } diff --git a/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/HTTP1ProxyConnectHandler.swift b/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/HTTP1ProxyConnectHandler.swift index fbcd4f9c0..db7b7b7ef 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/HTTP1ProxyConnectHandler.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/HTTP1ProxyConnectHandler.swift @@ -42,7 +42,7 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand private var proxyEstablishedPromise: EventLoopPromise? var proxyEstablishedFuture: EventLoopFuture? { - return self.proxyEstablishedPromise?.futureResult + self.proxyEstablishedPromise?.futureResult } convenience init( @@ -53,10 +53,10 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand let targetHost: String let targetPort: Int switch target { - case .ipAddress(serialization: let serialization, address: let address): + case .ipAddress(let serialization, let address): targetHost = serialization targetPort = address.port! - case .domain(name: let domain, port: let port): + case .domain(name: let domain, let port): targetHost = domain targetPort = port case .unixSocket: @@ -70,10 +70,12 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand ) } - init(targetHost: String, - targetPort: Int, - proxyAuthorization: HTTPClient.Authorization?, - deadline: NIODeadline) { + init( + targetHost: String, + targetPort: Int, + proxyAuthorization: HTTPClient.Authorization?, + deadline: NIODeadline + ) { self.targetHost = targetHost self.targetPort = targetPort self.proxyAuthorization = proxyAuthorization diff --git a/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/SOCKSEventsHandler.swift b/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/SOCKSEventsHandler.swift index 5a46f44a7..a98f97d4d 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/SOCKSEventsHandler.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/SOCKSEventsHandler.swift @@ -31,7 +31,7 @@ final class SOCKSEventsHandler: ChannelInboundHandler, RemovableChannelHandler { private var socksEstablishedPromise: EventLoopPromise? var socksEstablishedFuture: EventLoopFuture? { - return self.socksEstablishedPromise?.futureResult + self.socksEstablishedPromise?.futureResult } private let deadline: NIODeadline diff --git a/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/TLSEventsHandler.swift b/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/TLSEventsHandler.swift index aab26fda8..bebd0bcc7 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/TLSEventsHandler.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/ChannelHandler/TLSEventsHandler.swift @@ -31,7 +31,7 @@ final class TLSEventsHandler: ChannelInboundHandler, RemovableChannelHandler { private var tlsEstablishedPromise: EventLoopPromise? var tlsEstablishedFuture: EventLoopFuture? { - return self.tlsEstablishedPromise?.futureResult + self.tlsEstablishedPromise?.futureResult } private let deadline: NIODeadline? diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ClientChannelHandler.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ClientChannelHandler.swift index 41a56c91b..74a0c72d7 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ClientChannelHandler.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ClientChannelHandler.swift @@ -100,9 +100,12 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler { // MARK: Channel Inbound Handler func channelActive(context: ChannelHandlerContext) { - self.logger.trace("Channel active", metadata: [ - "ahc-channel-writable": "\(context.channel.isWritable)", - ]) + self.logger.trace( + "Channel active", + metadata: [ + "ahc-channel-writable": "\(context.channel.isWritable)" + ] + ) let action = self.state.channelActive(isWritable: context.channel.isWritable) self.run(action, context: context) @@ -116,9 +119,12 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler { } func channelWritabilityChanged(context: ChannelHandlerContext) { - self.logger.trace("Channel writability changed", metadata: [ - "ahc-channel-writable": "\(context.channel.isWritable)", - ]) + self.logger.trace( + "Channel writability changed", + metadata: [ + "ahc-channel-writable": "\(context.channel.isWritable)" + ] + ) if let timeoutAction = self.idleWriteTimeoutStateMachine?.channelWritabilityChanged(context: context) { self.runTimeoutAction(timeoutAction, context: context) @@ -132,9 +138,12 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler { func channelRead(context: ChannelHandlerContext, data: NIOAny) { let httpPart = self.unwrapInboundIn(data) - self.logger.trace("HTTP response part received", metadata: [ - "ahc-http-part": "\(httpPart)", - ]) + self.logger.trace( + "HTTP response part received", + metadata: [ + "ahc-http-part": "\(httpPart)" + ] + ) if let timeoutAction = self.idleReadTimeoutStateMachine?.channelRead(httpPart) { self.runTimeoutAction(timeoutAction, context: context) @@ -152,9 +161,12 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler { } func errorCaught(context: ChannelHandlerContext, error: Error) { - self.logger.trace("Channel error caught", metadata: [ - "ahc-error": "\(error)", - ]) + self.logger.trace( + "Channel error caught", + metadata: [ + "ahc-error": "\(error)" + ] + ) let action = self.state.errorHappened(error) self.run(action, context: context) @@ -447,7 +459,8 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler { // MARK: Private HTTPRequestExecutor - private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest, promise: EventLoopPromise?) { + private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest, promise: EventLoopPromise?) + { guard self.request === request, let context = self.channelContext else { // Because the HTTPExecutableRequest may run in a different thread to our eventLoop, // calls from the HTTPExecutableRequest to our ChannelHandler may arrive here after @@ -691,7 +704,9 @@ struct IdleWriteStateMachine { self.state = .waitingForWritabilityEnabled return .clearIdleWriteTimeoutTimer case .waitingForWritabilityEnabled: - preconditionFailure("If the channel was writable before, then we should have been waiting for more data.") + preconditionFailure( + "If the channel was writable before, then we should have been waiting for more data." + ) case .requestEndSent: return .none } diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1Connection.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1Connection.swift index ee0a78498..e0496f2e3 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1Connection.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1Connection.swift @@ -39,9 +39,11 @@ final class HTTP1Connection { let id: HTTPConnectionPool.Connection.ID - init(channel: Channel, - connectionID: HTTPConnectionPool.Connection.ID, - delegate: HTTP1ConnectionDelegate) { + init( + channel: Channel, + connectionID: HTTPConnectionPool.Connection.ID, + delegate: HTTP1ConnectionDelegate + ) { self.channel = channel self.id = connectionID self.delegate = delegate @@ -80,7 +82,7 @@ final class HTTP1Connection { } func close(promise: EventLoopPromise?) { - return self.channel.close(mode: .all, promise: promise) + self.channel.close(mode: .all, promise: promise) } func close() -> EventLoopFuture { diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ConnectionStateMachine.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ConnectionStateMachine.swift index ed4594183..aee0736ff 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ConnectionStateMachine.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1/HTTP1ConnectionStateMachine.swift @@ -140,7 +140,7 @@ struct HTTP1ConnectionStateMachine { self.state = .closed return .fireChannelError(error, closeConnection: false) - case .inRequest(var requestStateMachine, close: let close): + case .inRequest(var requestStateMachine, let close): return self.avoidingStateMachineCoW { state -> Action in let action = requestStateMachine.errorHappened(error) state = .inRequest(requestStateMachine, close: close) @@ -239,7 +239,9 @@ struct HTTP1ConnectionStateMachine { mutating func requestCancelled(closeConnection: Bool) -> Action { switch self.state { case .initialized: - fatalError("This event must only happen, if the connection is leased. During startup this is impossible. Invalid state: \(self.state)") + fatalError( + "This event must only happen, if the connection is leased. During startup this is impossible. Invalid state: \(self.state)" + ) case .idle: if closeConnection { @@ -249,7 +251,7 @@ struct HTTP1ConnectionStateMachine { return .wait } - case .inRequest(var requestStateMachine, close: let close): + case .inRequest(var requestStateMachine, let close): return self.avoidingStateMachineCoW { state -> Action in let action = requestStateMachine.requestCancelled() state = .inRequest(requestStateMachine, close: close || closeConnection) @@ -415,12 +417,16 @@ extension HTTP1ConnectionStateMachine { } extension HTTP1ConnectionStateMachine.State { - fileprivate mutating func modify(with action: HTTPRequestStateMachine.Action) -> HTTP1ConnectionStateMachine.Action { + fileprivate mutating func modify(with action: HTTPRequestStateMachine.Action) -> HTTP1ConnectionStateMachine.Action + { switch action { case .sendRequestHead(let head, let sendEnd): return .sendRequestHead(head, sendEnd: sendEnd) case .notifyRequestHeadSendSuccessfully(let resumeRequestBodyStream, let startIdleTimer): - return .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: resumeRequestBodyStream, startIdleTimer: startIdleTimer) + return .notifyRequestHeadSendSuccessfully( + resumeRequestBodyStream: resumeRequestBodyStream, + startIdleTimer: startIdleTimer + ) case .pauseRequestBodyStream: return .pauseRequestBodyStream case .resumeRequestBodyStream: @@ -458,7 +464,7 @@ extension HTTP1ConnectionStateMachine.State { fatalError("Invalid state: \(self)") case .idle: fatalError("How can we fail a task, if we are idle") - case .inRequest(_, close: let close): + case .inRequest(_, let close): if case .close(let promise) = finalAction { self = .closing return .failRequest(error, .close(promise)) @@ -502,7 +508,7 @@ extension HTTP1ConnectionStateMachine: CustomStringConvertible { return ".initialized" case .idle: return ".idle" - case .inRequest(let request, close: let close): + case .inRequest(let request, let close): return ".inRequest(\(request), closeAfterRequest: \(close))" case .closing: return ".closing" diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift index 1520ff414..01a248d72 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift @@ -68,8 +68,10 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler { } func handlerAdded(context: ChannelHandlerContext) { - assert(context.eventLoop === self.eventLoop, - "The handler must be added to a channel that runs on the eventLoop it was initialized with.") + assert( + context.eventLoop === self.eventLoop, + "The handler must be added to a channel that runs on the eventLoop it was initialized with." + ) self.channelContext = context let isWritable = context.channel.isActive && context.channel.isWritable @@ -216,7 +218,7 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler { // that the request is neither failed nor finished yet self.request!.resumeRequestBodyStream() - case .forwardResponseHead(let head, pauseRequestBodyStream: let pauseRequestBodyStream): + case .forwardResponseHead(let head, let pauseRequestBodyStream): // We can force unwrap the request here, as we have just validated in the state machine, // that the request is neither failed nor finished yet self.request!.receiveResponseHead(head) @@ -268,7 +270,10 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler { self.run(self.state.headSent(), context: context) } - private func runSuccessfulFinalAction(_ action: HTTPRequestStateMachine.Action.FinalSuccessfulRequestAction, context: ChannelHandlerContext) { + private func runSuccessfulFinalAction( + _ action: HTTPRequestStateMachine.Action.FinalSuccessfulRequestAction, + context: ChannelHandlerContext + ) { switch action { case .close, .none: // The actions returned here come from an `HTTPRequestStateMachine` that assumes http/1.1 @@ -281,7 +286,11 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler { } } - private func runFailedFinalAction(_ action: HTTPRequestStateMachine.Action.FinalFailedRequestAction, context: ChannelHandlerContext, error: Error) { + private func runFailedFinalAction( + _ action: HTTPRequestStateMachine.Action.FinalFailedRequestAction, + context: ChannelHandlerContext, + error: Error + ) { // We must close the http2 stream after the request has finished. Since the request failed, // we have no idea what the h2 streams state was. To be on the save side, we explicitly close // the h2 stream. This will break a reference cycle in HTTP2Connection. @@ -368,7 +377,8 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler { // MARK: Private HTTPRequestExecutor - private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest, promise: EventLoopPromise?) { + private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest, promise: EventLoopPromise?) + { guard self.request === request, let context = self.channelContext else { // Because the HTTPExecutableRequest may run in a different thread to our eventLoop, // calls from the HTTPExecutableRequest to our ChannelHandler may arrive here after diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2Connection.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2Connection.swift index ab43558c0..5e4ae6e01 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2Connection.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2Connection.swift @@ -89,12 +89,14 @@ final class HTTP2Connection { self.channel.closeFuture } - init(channel: Channel, - connectionID: HTTPConnectionPool.Connection.ID, - decompression: HTTPClient.Decompression, - maximumConnectionUses: Int?, - delegate: HTTP2ConnectionDelegate, - logger: Logger) { + init( + channel: Channel, + connectionID: HTTPConnectionPool.Connection.ID, + decompression: HTTPClient.Decompression, + maximumConnectionUses: Int?, + delegate: HTTP2ConnectionDelegate, + logger: Logger + ) { self.channel = channel self.id = connectionID self.decompression = decompression @@ -103,7 +105,7 @@ final class HTTP2Connection { self.multiplexer = HTTP2StreamMultiplexer( mode: .client, channel: channel, - targetWindowSize: 8 * 1024 * 1024, // 8mb + targetWindowSize: 8 * 1024 * 1024, // 8mb outboundBufferSizeHighWatermark: 8196, outboundBufferSizeLowWatermark: 4092, inboundStreamInitializer: { channel -> EventLoopFuture in @@ -162,7 +164,7 @@ final class HTTP2Connection { } func close(promise: EventLoopPromise?) { - return self.channel.close(mode: .all, promise: promise) + self.channel.close(mode: .all, promise: promise) } func close() -> EventLoopFuture { @@ -199,7 +201,11 @@ final class HTTP2Connection { let sync = self.channel.pipeline.syncOperations let http2Handler = NIOHTTP2Handler(mode: .client, initialSettings: Self.defaultSettings) - let idleHandler = HTTP2IdleHandler(delegate: self, logger: self.logger, maximumConnectionUses: self.maximumConnectionUses) + let idleHandler = HTTP2IdleHandler( + delegate: self, + logger: self.logger, + maximumConnectionUses: self.maximumConnectionUses + ) try sync.addHandler(http2Handler, position: .last) try sync.addHandler(idleHandler, position: .last) @@ -221,7 +227,8 @@ final class HTTP2Connection { case .active: let createStreamChannelPromise = self.channel.eventLoop.makePromise(of: Channel.self) - self.multiplexer.createStreamChannel(promise: createStreamChannelPromise) { channel -> EventLoopFuture in + self.multiplexer.createStreamChannel(promise: createStreamChannelPromise) { + channel -> EventLoopFuture in do { // the connection may have been asked to shutdown while we created the child. in // this @@ -278,7 +285,7 @@ final class HTTP2Connection { self.state = .closing // inform all open streams, that the currently running request should be cancelled. - self.openStreams.forEach { box in + for box in self.openStreams { box.channel.triggerUserOutboundEvent(HTTPConnectionEvent.shutdownRequested, promise: nil) } diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2IdleHandler.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2IdleHandler.swift index 06458cb7e..64a151489 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2IdleHandler.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2IdleHandler.swift @@ -184,9 +184,15 @@ extension HTTP2IdleHandler { self.state = .active(openStreams: 0, maxStreams: maxStreams, remainingUses: remainingUses) return .notifyConnectionNewMaxStreamsSettings(maxStreams) - case .active(openStreams: let openStreams, maxStreams: let maxStreams, remainingUses: let remainingUses): - if let newMaxStreams = settings.last(where: { $0.parameter == .maxConcurrentStreams })?.value, newMaxStreams != maxStreams { - self.state = .active(openStreams: openStreams, maxStreams: newMaxStreams, remainingUses: remainingUses) + case .active(let openStreams, let maxStreams, let remainingUses): + if let newMaxStreams = settings.last(where: { $0.parameter == .maxConcurrentStreams })?.value, + newMaxStreams != maxStreams + { + self.state = .active( + openStreams: openStreams, + maxStreams: newMaxStreams, + remainingUses: remainingUses + ) return .notifyConnectionNewMaxStreamsSettings(newMaxStreams) } return .nothing diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift index 3a0011d5e..0aad0c8dd 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift @@ -20,6 +20,7 @@ import NIOPosix import NIOSOCKS import NIOSSL import NIOTLS + #if canImport(Network) import NIOTransportServices #endif @@ -31,14 +32,17 @@ extension HTTPConnectionPool { let tlsConfiguration: TLSConfiguration let sslContextCache: SSLContextCache - init(key: ConnectionPool.Key, - tlsConfiguration: TLSConfiguration?, - clientConfiguration: HTTPClient.Configuration, - sslContextCache: SSLContextCache) { + init( + key: ConnectionPool.Key, + tlsConfiguration: TLSConfiguration?, + clientConfiguration: HTTPClient.Configuration, + sslContextCache: SSLContextCache + ) { self.key = key self.clientConfiguration = clientConfiguration self.sslContextCache = sslContextCache - self.tlsConfiguration = tlsConfiguration ?? clientConfiguration.tlsConfiguration ?? .makeClientConfiguration() + self.tlsConfiguration = + tlsConfiguration ?? clientConfiguration.tlsConfiguration ?? .makeClientConfiguration() } } } @@ -63,7 +67,13 @@ extension HTTPConnectionPool.ConnectionFactory { var logger = logger logger[metadataKey: "ahc-connection-id"] = "\(connectionID)" - self.makeChannel(requester: requester, connectionID: connectionID, deadline: deadline, eventLoop: eventLoop, logger: logger).whenComplete { result in + self.makeChannel( + requester: requester, + connectionID: connectionID, + deadline: deadline, + eventLoop: eventLoop, + logger: logger + ).whenComplete { result in switch result { case .success(.http1_1(let channel)): do { @@ -137,7 +147,13 @@ extension HTTPConnectionPool.ConnectionFactory { ) } } else { - channelFuture = self.makeNonProxiedChannel(requester: requester, connectionID: connectionID, deadline: deadline, eventLoop: eventLoop, logger: logger) + channelFuture = self.makeNonProxiedChannel( + requester: requester, + connectionID: connectionID, + deadline: deadline, + eventLoop: eventLoop, + logger: logger + ) } // let's map `ChannelError.connectTimeout` into a `HTTPClientError.connectTimeout` @@ -160,10 +176,22 @@ extension HTTPConnectionPool.ConnectionFactory { ) -> EventLoopFuture { switch self.key.scheme { case .http, .httpUnix, .unix: - return self.makePlainChannel(requester: requester, connectionID: connectionID, deadline: deadline, eventLoop: eventLoop).map { .http1_1($0) } + return self.makePlainChannel( + requester: requester, + connectionID: connectionID, + deadline: deadline, + eventLoop: eventLoop + ).map { .http1_1($0) } case .https, .httpsUnix: - return self.makeTLSChannel(requester: requester, connectionID: connectionID, deadline: deadline, eventLoop: eventLoop, logger: logger).flatMapThrowing { - channel, negotiated in + return self.makeTLSChannel( + requester: requester, + connectionID: connectionID, + deadline: deadline, + eventLoop: eventLoop, + logger: logger + ).flatMapThrowing { + channel, + negotiated in try self.matchALPNToHTTPVersion(negotiated, channel: channel) } @@ -177,7 +205,12 @@ extension HTTPConnectionPool.ConnectionFactory { eventLoop: EventLoop ) -> EventLoopFuture { precondition(!self.key.scheme.usesTLS, "Unexpected scheme") - return self.makePlainBootstrap(requester: requester, connectionID: connectionID, deadline: deadline, eventLoop: eventLoop).connect(target: self.key.connectionTarget) + return self.makePlainBootstrap( + requester: requester, + connectionID: connectionID, + deadline: deadline, + eventLoop: eventLoop + ).connect(target: self.key.connectionTarget) } private func makeHTTPProxyChannel( @@ -191,7 +224,12 @@ extension HTTPConnectionPool.ConnectionFactory { // A proxy connection starts with a plain text connection to the proxy server. After // the connection has been established with the proxy server, the connection might be // upgraded to TLS before we send our first request. - let bootstrap = self.makePlainBootstrap(requester: requester, connectionID: connectionID, deadline: deadline, eventLoop: eventLoop) + let bootstrap = self.makePlainBootstrap( + requester: requester, + connectionID: connectionID, + deadline: deadline, + eventLoop: eventLoop + ) return bootstrap.connect(host: proxy.host, port: proxy.port).flatMap { channel in let encoder = HTTPRequestEncoder() let decoder = ByteToMessageHandler(HTTPResponseDecoder(leftOverBytesStrategy: .dropBytes)) @@ -234,7 +272,12 @@ extension HTTPConnectionPool.ConnectionFactory { // A proxy connection starts with a plain text connection to the proxy server. After // the connection has been established with the proxy server, the connection might be // upgraded to TLS before we send our first request. - let bootstrap = self.makePlainBootstrap(requester: requester, connectionID: connectionID, deadline: deadline, eventLoop: eventLoop) + let bootstrap = self.makePlainBootstrap( + requester: requester, + connectionID: connectionID, + deadline: deadline, + eventLoop: eventLoop + ) return bootstrap.connect(host: proxy.host, port: proxy.port).flatMap { channel in let socksConnectHandler = SOCKSClientHandler(targetAddress: SOCKSAddress(self.key.connectionTarget)) let socksEventHandler = SOCKSEventsHandler(deadline: deadline) @@ -319,15 +362,26 @@ extension HTTPConnectionPool.ConnectionFactory { eventLoop: EventLoop ) -> NIOClientTCPBootstrapProtocol { #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { - return tsBootstrap - .channelOption(NIOTSChannelOptions.waitForActivity, value: self.clientConfiguration.networkFrameworkWaitForConnectivity) - .channelOption(NIOTSChannelOptions.multipathServiceType, value: self.clientConfiguration.enableMultipath ? .handover : .disabled) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), + let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) + { + return + tsBootstrap + .channelOption( + NIOTSChannelOptions.waitForActivity, + value: self.clientConfiguration.networkFrameworkWaitForConnectivity + ) + .channelOption( + NIOTSChannelOptions.multipathServiceType, + value: self.clientConfiguration.enableMultipath ? .handover : .disabled + ) .connectTimeout(deadline - NIODeadline.now()) .channelInitializer { channel in do { try channel.pipeline.syncOperations.addHandler(HTTPClient.NWErrorHandler()) - try channel.pipeline.syncOperations.addHandler(NWWaitingHandler(requester: requester, connectionID: connectionID)) + try channel.pipeline.syncOperations.addHandler( + NWWaitingHandler(requester: requester, connectionID: connectionID) + ) return channel.eventLoop.makeSucceededVoidFuture() } catch { return channel.eventLoop.makeFailedFuture(error) @@ -337,7 +391,8 @@ extension HTTPConnectionPool.ConnectionFactory { #endif if let nioBootstrap = ClientBootstrap(validatingGroup: eventLoop) { - return nioBootstrap + return + nioBootstrap .connectTimeout(deadline - NIODeadline.now()) .enableMPTCP(clientConfiguration.enableMultipath) } @@ -362,7 +417,7 @@ extension HTTPConnectionPool.ConnectionFactory { ) var channelFuture = bootstrapFuture.flatMap { bootstrap -> EventLoopFuture in - return bootstrap.connect(target: self.key.connectionTarget) + bootstrap.connect(target: self.key.connectionTarget) }.flatMap { channel -> EventLoopFuture<(Channel, String?)> in do { // if the channel is closed before flatMap is executed, all ChannelHandler are removed @@ -375,7 +430,10 @@ extension HTTPConnectionPool.ConnectionFactory { channel.pipeline.removeHandler(tlsEventHandler).map { (channel, negotiated) } } } catch { - assert(channel.isActive == false, "if the channel is still active then TLSEventsHandler must be present but got error \(error)") + assert( + channel.isActive == false, + "if the channel is still active then TLSEventsHandler must be present but got error \(error)" + ) return channel.eventLoop.makeFailedFuture(HTTPClientError.remoteConnectionClosed) } } @@ -410,20 +468,33 @@ extension HTTPConnectionPool.ConnectionFactory { } #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), + let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) + { // create NIOClientTCPBootstrap with NIOTS TLS provider - let bootstrapFuture = tlsConfig.getNWProtocolTLSOptions(on: eventLoop, serverNameIndicatorOverride: key.serverNameIndicatorOverride).map { + let bootstrapFuture = tlsConfig.getNWProtocolTLSOptions( + on: eventLoop, + serverNameIndicatorOverride: key.serverNameIndicatorOverride + ).map { options -> NIOClientTCPBootstrapProtocol in tsBootstrap - .channelOption(NIOTSChannelOptions.waitForActivity, value: self.clientConfiguration.networkFrameworkWaitForConnectivity) - .channelOption(NIOTSChannelOptions.multipathServiceType, value: self.clientConfiguration.enableMultipath ? .handover : .disabled) + .channelOption( + NIOTSChannelOptions.waitForActivity, + value: self.clientConfiguration.networkFrameworkWaitForConnectivity + ) + .channelOption( + NIOTSChannelOptions.multipathServiceType, + value: self.clientConfiguration.enableMultipath ? .handover : .disabled + ) .connectTimeout(deadline - NIODeadline.now()) .tlsOptions(options) .channelInitializer { channel in do { try channel.pipeline.syncOperations.addHandler(HTTPClient.NWErrorHandler()) - try channel.pipeline.syncOperations.addHandler(NWWaitingHandler(requester: requester, connectionID: connectionID)) + try channel.pipeline.syncOperations.addHandler( + NWWaitingHandler(requester: requester, connectionID: connectionID) + ) // we don't need to set a TLS deadline for NIOTS connections, since the // TLS handshake is part of the TS connection bootstrap. If the TLS // handshake times out the complete connection creation will be failed. diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Manager.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Manager.swift index f5a0540cf..3fdf93752 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Manager.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Manager.swift @@ -39,9 +39,11 @@ extension HTTPConnectionPool { private let sslContextCache = SSLContextCache() - init(eventLoopGroup: EventLoopGroup, - configuration: HTTPClient.Configuration, - backgroundActivityLogger logger: Logger) { + init( + eventLoopGroup: EventLoopGroup, + configuration: HTTPClient.Configuration, + backgroundActivityLogger logger: Logger + ) { self.eventLoopGroup = eventLoopGroup self.configuration = configuration self.logger = logger @@ -118,7 +120,7 @@ extension HTTPConnectionPool { promise?.succeed(false) case .shutdown(let pools): - pools.values.forEach { pool in + for pool in pools.values { pool.shutdown() } } @@ -140,7 +142,9 @@ extension HTTPConnectionPool.Manager: HTTPConnectionPoolDelegate { case .shuttingDown(let promise, let soFarUnclean): guard self._pools.removeValue(forKey: pool.key) === pool else { - preconditionFailure("Expected that the pool was created by this manager and is known for this reason.") + preconditionFailure( + "Expected that the pool was created by this manager and is known for this reason." + ) } if self._pools.isEmpty { @@ -154,7 +158,7 @@ extension HTTPConnectionPool.Manager: HTTPConnectionPoolDelegate { } switch closeAction { - case .close(let promise, unclean: let unclean): + case .close(let promise, let unclean): promise?.succeed(unclean) case .wait: break @@ -173,7 +177,7 @@ extension HTTPConnectionPool.Connection.ID { } func next() -> Int { - return self.atomic.loadThenWrappingIncrement(ordering: .relaxed) + self.atomic.loadThenWrappingIncrement(ordering: .relaxed) } } } diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift index 093c1e328..e7f1d8ce5 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift @@ -44,14 +44,16 @@ final class HTTPConnectionPool { let delegate: HTTPConnectionPoolDelegate - init(eventLoopGroup: EventLoopGroup, - sslContextCache: SSLContextCache, - tlsConfiguration: TLSConfiguration?, - clientConfiguration: HTTPClient.Configuration, - key: ConnectionPool.Key, - delegate: HTTPConnectionPoolDelegate, - idGenerator: Connection.ID.Generator, - backgroundActivityLogger logger: Logger) { + init( + eventLoopGroup: EventLoopGroup, + sslContextCache: SSLContextCache, + tlsConfiguration: TLSConfiguration?, + clientConfiguration: HTTPClient.Configuration, + key: ConnectionPool.Key, + delegate: HTTPConnectionPoolDelegate, + idGenerator: Connection.ID.Generator, + backgroundActivityLogger logger: Logger + ) { self.eventLoopGroup = eventLoopGroup self.connectionFactory = ConnectionFactory( key: key, @@ -70,7 +72,8 @@ final class HTTPConnectionPool { self._state = StateMachine( idGenerator: idGenerator, - maximumConcurrentHTTP1Connections: clientConfiguration.connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit, + maximumConcurrentHTTP1Connections: clientConfiguration.connectionPool + .concurrentHTTP1ConnectionsPerHostSoftLimit, retryConnectionEstablishment: clientConfiguration.connectionPool.retryConnectionEstablishment, preferHTTP1: clientConfiguration.httpVersion == .http1Only, maximumConnectionUses: clientConfiguration.maximumUsesPerConnection @@ -150,7 +153,7 @@ final class HTTPConnectionPool { self.unlocked = Unlocked(connection: .none, request: .none) switch stateMachineAction.request { - case .executeRequest(let request, let connection, cancelTimeout: let cancelTimeout): + case .executeRequest(let request, let connection, let cancelTimeout): if cancelTimeout { self.locked.request = .cancelRequestTimeout(request.id) } @@ -158,7 +161,7 @@ final class HTTPConnectionPool { case .executeRequestsAndCancelTimeouts(let requests, let connection): self.locked.request = .cancelRequestTimeouts(requests) self.unlocked.request = .executeRequests(requests, connection) - case .failRequest(let request, let error, cancelTimeout: let cancelTimeout): + case .failRequest(let request, let error, let cancelTimeout): if cancelTimeout { self.locked.request = .cancelRequestTimeout(request.id) } @@ -175,15 +178,15 @@ final class HTTPConnectionPool { switch stateMachineAction.connection { case .createConnection(let connectionID, on: let eventLoop): self.unlocked.connection = .createConnection(connectionID, on: eventLoop) - case .scheduleBackoffTimer(let connectionID, backoff: let backoff, on: let eventLoop): + case .scheduleBackoffTimer(let connectionID, let backoff, on: let eventLoop): self.locked.connection = .scheduleBackoffTimer(connectionID, backoff: backoff, on: eventLoop) case .scheduleTimeoutTimer(let connectionID, on: let eventLoop): self.locked.connection = .scheduleTimeoutTimer(connectionID, on: eventLoop) case .cancelTimeoutTimer(let connectionID): self.locked.connection = .cancelTimeoutTimer(connectionID) - case .closeConnection(let connection, isShutdown: let isShutdown): + case .closeConnection(let connection, let isShutdown): self.unlocked.connection = .closeConnection(connection, isShutdown: isShutdown) - case .cleanupConnections(var cleanupContext, isShutdown: let isShutdown): + case .cleanupConnections(var cleanupContext, let isShutdown): // self.locked.connection = .cancelBackoffTimers(cleanupContext.connectBackoff) cleanupContext.connectBackoff = [] @@ -221,7 +224,7 @@ final class HTTPConnectionPool { private func runLockedConnectionAction(_ action: Actions.ConnectionAction.Locked) { switch action { - case .scheduleBackoffTimer(let connectionID, backoff: let backoff, on: let eventLoop): + case .scheduleBackoffTimer(let connectionID, let backoff, on: let eventLoop): self.scheduleConnectionStartBackoffTimer(connectionID, backoff, on: eventLoop) case .scheduleTimeoutTimer(let connectionID, on: let eventLoop): @@ -249,7 +252,7 @@ final class HTTPConnectionPool { self.cancelRequestTimeout(requestID) case .cancelRequestTimeouts(let requests): - requests.forEach { self.cancelRequestTimeout($0.id) } + for request in requests { self.cancelRequestTimeout(request.id) } case .none: break @@ -266,10 +269,13 @@ final class HTTPConnectionPool { case .createConnection(let connectionID, let eventLoop): self.createConnection(connectionID, on: eventLoop) - case .closeConnection(let connection, isShutdown: let isShutdown): - self.logger.trace("close connection", metadata: [ - "ahc-connection-id": "\(connection.id)", - ]) + case .closeConnection(let connection, let isShutdown): + self.logger.trace( + "close connection", + metadata: [ + "ahc-connection-id": "\(connection.id)" + ] + ) // we are not interested in the close promise... connection.close(promise: nil) @@ -278,7 +284,7 @@ final class HTTPConnectionPool { self.delegate.connectionPoolDidShutdown(self, unclean: unclean) } - case .cleanupConnections(let cleanupContext, isShutdown: let isShutdown): + case .cleanupConnections(let cleanupContext, let isShutdown): for connection in cleanupContext.close { connection.close(promise: nil) } @@ -315,13 +321,13 @@ final class HTTPConnectionPool { connection.executeRequest(request.req) case .executeRequests(let requests, let connection): - requests.forEach { connection.executeRequest($0.req) } + for request in requests { connection.executeRequest(request.req) } case .failRequest(let request, let error): request.req.fail(error) case .failRequests(let requests, let error): - requests.forEach { $0.req.fail(error) } + for request in requests { request.req.fail(error) } case .none: break @@ -329,9 +335,12 @@ final class HTTPConnectionPool { } private func createConnection(_ connectionID: Connection.ID, on eventLoop: EventLoop) { - self.logger.trace("Opening fresh connection", metadata: [ - "ahc-connection-id": "\(connectionID)", - ]) + self.logger.trace( + "Opening fresh connection", + metadata: [ + "ahc-connection-id": "\(connectionID)" + ] + ) // Even though this function is called make it actually creates/establishes a connection. // TBD: Should we rename it? To what? self.connectionFactory.makeConnection( @@ -374,9 +383,12 @@ final class HTTPConnectionPool { } private func scheduleIdleTimerForConnection(_ connectionID: Connection.ID, on eventLoop: EventLoop) { - self.logger.trace("Schedule idle connection timeout timer", metadata: [ - "ahc-connection-id": "\(connectionID)", - ]) + self.logger.trace( + "Schedule idle connection timeout timer", + metadata: [ + "ahc-connection-id": "\(connectionID)" + ] + ) let scheduled = eventLoop.scheduleTask(in: self.idleConnectionTimeout) { // there might be a race between a cancelTimer call and the triggering // of this scheduled task. both want to acquire the lock @@ -394,9 +406,12 @@ final class HTTPConnectionPool { } private func cancelIdleTimerForConnection(_ connectionID: Connection.ID) { - self.logger.trace("Cancel idle connection timeout timer", metadata: [ - "ahc-connection-id": "\(connectionID)", - ]) + self.logger.trace( + "Cancel idle connection timeout timer", + metadata: [ + "ahc-connection-id": "\(connectionID)" + ] + ) guard let cancelTimer = self._idleTimer.removeValue(forKey: connectionID) else { preconditionFailure("Expected to have an idle timer for connection \(connectionID) at this point.") } @@ -408,9 +423,12 @@ final class HTTPConnectionPool { _ timeAmount: TimeAmount, on eventLoop: EventLoop ) { - self.logger.trace("Schedule connection creation backoff timer", metadata: [ - "ahc-connection-id": "\(connectionID)", - ]) + self.logger.trace( + "Schedule connection creation backoff timer", + metadata: [ + "ahc-connection-id": "\(connectionID)" + ] + ) let scheduled = eventLoop.scheduleTask(in: timeAmount) { // there might be a race between a backoffTimer and the pool shutting down. @@ -439,41 +457,53 @@ final class HTTPConnectionPool { extension HTTPConnectionPool: HTTPConnectionRequester { func http1ConnectionCreated(_ connection: HTTP1Connection) { - self.logger.trace("successfully created connection", metadata: [ - "ahc-connection-id": "\(connection.id)", - "ahc-http-version": "http/1.1", - ]) + self.logger.trace( + "successfully created connection", + metadata: [ + "ahc-connection-id": "\(connection.id)", + "ahc-http-version": "http/1.1", + ] + ) self.modifyStateAndRunActions { $0.newHTTP1ConnectionCreated(.http1_1(connection)) } } func http2ConnectionCreated(_ connection: HTTP2Connection, maximumStreams: Int) { - self.logger.trace("successfully created connection", metadata: [ - "ahc-connection-id": "\(connection.id)", - "ahc-http-version": "http/2", - "ahc-max-streams": "\(maximumStreams)", - ]) + self.logger.trace( + "successfully created connection", + metadata: [ + "ahc-connection-id": "\(connection.id)", + "ahc-http-version": "http/2", + "ahc-max-streams": "\(maximumStreams)", + ] + ) self.modifyStateAndRunActions { $0.newHTTP2ConnectionCreated(.http2(connection), maxConcurrentStreams: maximumStreams) } } func failedToCreateHTTPConnection(_ connectionID: HTTPConnectionPool.Connection.ID, error: Error) { - self.logger.debug("connection attempt failed", metadata: [ - "ahc-error": "\(error)", - "ahc-connection-id": "\(connectionID)", - ]) + self.logger.debug( + "connection attempt failed", + metadata: [ + "ahc-error": "\(error)", + "ahc-connection-id": "\(connectionID)", + ] + ) self.modifyStateAndRunActions { $0.failedToCreateNewConnection(error, connectionID: connectionID) } } func waitingForConnectivity(_ connectionID: HTTPConnectionPool.Connection.ID, error: Error) { - self.logger.debug("waiting for connectivity", metadata: [ - "ahc-error": "\(error)", - "ahc-connection-id": "\(connectionID)", - ]) + self.logger.debug( + "waiting for connectivity", + metadata: [ + "ahc-error": "\(error)", + "ahc-connection-id": "\(connectionID)", + ] + ) self.modifyStateAndRunActions { $0.waitingForConnectivity(error, connectionID: connectionID) } @@ -482,20 +512,26 @@ extension HTTPConnectionPool: HTTPConnectionRequester { extension HTTPConnectionPool: HTTP1ConnectionDelegate { func http1ConnectionClosed(_ connection: HTTP1Connection) { - self.logger.debug("connection closed", metadata: [ - "ahc-connection-id": "\(connection.id)", - "ahc-http-version": "http/1.1", - ]) + self.logger.debug( + "connection closed", + metadata: [ + "ahc-connection-id": "\(connection.id)", + "ahc-http-version": "http/1.1", + ] + ) self.modifyStateAndRunActions { $0.http1ConnectionClosed(connection.id) } } func http1ConnectionReleased(_ connection: HTTP1Connection) { - self.logger.trace("releasing connection", metadata: [ - "ahc-connection-id": "\(connection.id)", - "ahc-http-version": "http/1.1", - ]) + self.logger.trace( + "releasing connection", + metadata: [ + "ahc-connection-id": "\(connection.id)", + "ahc-http-version": "http/1.1", + ] + ) self.modifyStateAndRunActions { $0.http1ConnectionReleased(connection.id) } @@ -504,41 +540,53 @@ extension HTTPConnectionPool: HTTP1ConnectionDelegate { extension HTTPConnectionPool: HTTP2ConnectionDelegate { func http2Connection(_ connection: HTTP2Connection, newMaxStreamSetting: Int) { - self.logger.debug("new max stream setting", metadata: [ - "ahc-connection-id": "\(connection.id)", - "ahc-http-version": "http/2", - "ahc-max-streams": "\(newMaxStreamSetting)", - ]) + self.logger.debug( + "new max stream setting", + metadata: [ + "ahc-connection-id": "\(connection.id)", + "ahc-http-version": "http/2", + "ahc-max-streams": "\(newMaxStreamSetting)", + ] + ) self.modifyStateAndRunActions { $0.newHTTP2MaxConcurrentStreamsReceived(connection.id, newMaxStreams: newMaxStreamSetting) } } func http2ConnectionGoAwayReceived(_ connection: HTTP2Connection) { - self.logger.debug("connection go away received", metadata: [ - "ahc-connection-id": "\(connection.id)", - "ahc-http-version": "http/2", - ]) + self.logger.debug( + "connection go away received", + metadata: [ + "ahc-connection-id": "\(connection.id)", + "ahc-http-version": "http/2", + ] + ) self.modifyStateAndRunActions { $0.http2ConnectionGoAwayReceived(connection.id) } } func http2ConnectionClosed(_ connection: HTTP2Connection) { - self.logger.debug("connection closed", metadata: [ - "ahc-connection-id": "\(connection.id)", - "ahc-http-version": "http/2", - ]) + self.logger.debug( + "connection closed", + metadata: [ + "ahc-connection-id": "\(connection.id)", + "ahc-http-version": "http/2", + ] + ) self.modifyStateAndRunActions { $0.http2ConnectionClosed(connection.id) } } func http2ConnectionStreamClosed(_ connection: HTTP2Connection, availableStreams: Int) { - self.logger.trace("stream closed", metadata: [ - "ahc-connection-id": "\(connection.id)", - "ahc-http-version": "http/2", - ]) + self.logger.trace( + "stream closed", + metadata: [ + "ahc-connection-id": "\(connection.id)", + "ahc-http-version": "http/2", + ] + ) self.modifyStateAndRunActions { $0.http2ConnectionStreamClosed(connection.id) } @@ -642,7 +690,9 @@ extension HTTPConnectionPool { return lhsConn.id == rhsConn.id case (.http2(let lhsConn), .http2(let rhsConn)): return lhsConn.id == rhsConn.id - case (.__testOnly_connection(let lhsID, let lhsEventLoop), .__testOnly_connection(let rhsID, let rhsEventLoop)): + case ( + .__testOnly_connection(let lhsID, let lhsEventLoop), .__testOnly_connection(let rhsID, let rhsEventLoop) + ): return lhsID == rhsID && lhsEventLoop === rhsEventLoop default: return false @@ -723,7 +773,7 @@ struct EventLoopID: Hashable { } static func __testOnly_fakeID(_ id: Int) -> EventLoopID { - return EventLoopID(.__testOnly_fakeID(id)) + EventLoopID(.__testOnly_fakeID(id)) } } diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine+Demand.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine+Demand.swift index 90578bc87..5c5b893e0 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine+Demand.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine+Demand.swift @@ -104,8 +104,8 @@ extension HTTPRequestStateMachine { // forwarded to the user. case .waitingForRead, - .waitingForDemand, - .waitingForReadOrDemand: + .waitingForDemand, + .waitingForReadOrDemand: return nil case .modifying: @@ -174,8 +174,8 @@ extension HTTPRequestStateMachine { return (buffer, .none) case .waitingForReadOrDemand(let buffer), - .waitingForRead(let buffer), - .waitingForDemand(let buffer): + .waitingForRead(let buffer), + .waitingForDemand(let buffer): // Normally this code path should never be hit. However there is one way to trigger // this: // diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine.swift index 533062036..e06389360 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine.swift @@ -161,10 +161,10 @@ struct HTTPRequestStateMachine { switch self.state { case .initialized, - .running(.streaming(_, _, producer: .producing), _), - .running(.endSent, _), - .finished, - .failed: + .running(.streaming(_, _, producer: .producing), _), + .running(.endSent, _), + .finished, + .failed: return .wait case .waitForChannelToBecomeWritable(let head, let metadata): @@ -196,11 +196,11 @@ struct HTTPRequestStateMachine { switch self.state { case .initialized, - .waitForChannelToBecomeWritable, - .running(.streaming(_, _, producer: .paused), _), - .running(.endSent, _), - .finished, - .failed: + .waitForChannelToBecomeWritable, + .running(.streaming(_, _, producer: .paused), _), + .running(.endSent, _), + .finished, + .failed: return .wait case .running(.streaming(let expectedBodyLength, let sentBodyBytes, producer: .producing), let responseState): @@ -219,13 +219,16 @@ struct HTTPRequestStateMachine { mutating func errorHappened(_ error: Error) -> Action { if let error = error as? NIOSSLError, - error == .uncleanShutdown, - let action = self.handleNIOSSLUncleanShutdownError() { + error == .uncleanShutdown, + let action = self.handleNIOSSLUncleanShutdownError() + { return action } switch self.state { case .initialized: - preconditionFailure("After the state machine has been initialized, start must be called immediately. Thus this state is unreachable") + preconditionFailure( + "After the state machine has been initialized, start must be called immediately. Thus this state is unreachable" + ) case .waitForChannelToBecomeWritable: // the request failed, before it was sent onto the wire. self.state = .failed(error) @@ -247,14 +250,14 @@ struct HTTPRequestStateMachine { private mutating func handleNIOSSLUncleanShutdownError() -> Action? { switch self.state { case .running(.streaming, .waitingForHead), - .running(.endSent, .waitingForHead): + .running(.endSent, .waitingForHead): // if we received a NIOSSL.uncleanShutdown before we got an answer we should handle // this like a normal connection close. We will receive a call to channelInactive after // this error. return .wait case .running(.streaming, .receivingBody(let responseHead, _)), - .running(.endSent, .receivingBody(let responseHead, _)): + .running(.endSent, .receivingBody(let responseHead, _)): // This code is only reachable for request and responses, which we expect to have a body. // We depend on logic from the HTTPResponseDecoder here. The decoder will emit an // HTTPResponsePart.end right after the HTTPResponsePart.head, for every request with a @@ -263,7 +266,9 @@ struct HTTPRequestStateMachine { // For this reason we only need to check the "content-length" or "transfer-encoding" // headers here to determine if we are potentially in an EOF terminated response. - if responseHead.headers.contains(name: "content-length") || responseHead.headers.contains(name: "transfer-encoding") { + if responseHead.headers.contains(name: "content-length") + || responseHead.headers.contains(name: "transfer-encoding") + { // If we have already received the response head, the parser will ensure that we // receive a complete response, if the content-length or transfer-encoding header // was set. In this case we can ignore the NIOSSLError.uncleanShutdown. We will see @@ -285,9 +290,11 @@ struct HTTPRequestStateMachine { mutating func requestStreamPartReceived(_ part: IOData, promise: EventLoopPromise?) -> Action { switch self.state { case .initialized, - .waitForChannelToBecomeWritable, - .running(.endSent, _): - preconditionFailure("We must be in the request streaming phase, if we receive further body parts. Invalid state: \(self.state)") + .waitForChannelToBecomeWritable, + .running(.endSent, _): + preconditionFailure( + "We must be in the request streaming phase, if we receive further body parts. Invalid state: \(self.state)" + ) case .running(.streaming(_, _, let producerState), .receivingBody(let head, _)) where head.status.code >= 300: // If we have already received a response head with status >= 300, we won't send out any @@ -349,9 +356,11 @@ struct HTTPRequestStateMachine { mutating func requestStreamFinished(promise: EventLoopPromise?) -> Action { switch self.state { case .initialized, - .waitForChannelToBecomeWritable, - .running(.endSent, _): - preconditionFailure("A request body stream end is only expected if we are in state request streaming. Invalid state: \(self.state)") + .waitForChannelToBecomeWritable, + .running(.endSent, _): + preconditionFailure( + "A request body stream end is only expected if we are in state request streaming. Invalid state: \(self.state)" + ) case .running(.streaming(let expectedBodyLength, let sentBodyBytes, _), .waitingForHead): if let expected = expectedBodyLength, expected != sentBodyBytes { @@ -363,7 +372,10 @@ struct HTTPRequestStateMachine { self.state = .running(.endSent, .waitingForHead) return .sendRequestEnd(promise) - case .running(.streaming(let expectedBodyLength, let sentBodyBytes, _), .receivingBody(let head, let streamState)): + case .running( + .streaming(let expectedBodyLength, let sentBodyBytes, _), + .receivingBody(let head, let streamState) + ): assert(head.status.code < 300) if let expected = expectedBodyLength, expected != sentBodyBytes { @@ -456,11 +468,11 @@ struct HTTPRequestStateMachine { mutating func read() -> Action { switch self.state { case .initialized, - .waitForChannelToBecomeWritable, - .running(_, .waitingForHead), - .running(_, .endReceived), - .finished, - .failed: + .waitForChannelToBecomeWritable, + .running(_, .waitingForHead), + .running(_, .endReceived), + .finished, + .failed: // If we are not in the middle of streaming the response body, we always want to get // more data... return .read @@ -493,11 +505,11 @@ struct HTTPRequestStateMachine { mutating func channelReadComplete() -> Action { switch self.state { case .initialized, - .waitForChannelToBecomeWritable, - .running(_, .waitingForHead), - .running(_, .endReceived), - .finished, - .failed: + .waitForChannelToBecomeWritable, + .running(_, .waitingForHead), + .running(_, .endReceived), + .finished, + .failed: return .wait case .running(let requestState, .receivingBody(let head, var streamState)): @@ -528,7 +540,9 @@ struct HTTPRequestStateMachine { switch self.state { case .initialized, .waitForChannelToBecomeWritable: - preconditionFailure("How can we receive a response head before sending a request head ourselves \(self.state)") + preconditionFailure( + "How can we receive a response head before sending a request head ourselves \(self.state)" + ) case .running(.streaming(let expectedBodyLength, let sentBodyBytes, producer: .paused), .waitingForHead): self.state = .running( @@ -546,7 +560,11 @@ struct HTTPRequestStateMachine { return .forwardResponseHead(head, pauseRequestBodyStream: true) } else { self.state = .running( - .streaming(expectedBodyLength: expectedBodyLength, sentBodyBytes: sentBodyBytes, producer: .producing), + .streaming( + expectedBodyLength: expectedBodyLength, + sentBodyBytes: sentBodyBytes, + producer: .producing + ), .receivingBody(head, .init()) ) return .forwardResponseHead(head, pauseRequestBodyStream: false) @@ -557,7 +575,9 @@ struct HTTPRequestStateMachine { return .forwardResponseHead(head, pauseRequestBodyStream: false) case .running(_, .receivingBody), .running(_, .endReceived), .finished: - preconditionFailure("How can we successfully finish the request, before having received a head. Invalid state: \(self.state)") + preconditionFailure( + "How can we successfully finish the request, before having received a head. Invalid state: \(self.state)" + ) case .failed: return .wait @@ -569,10 +589,14 @@ struct HTTPRequestStateMachine { mutating func receivedHTTPResponseBodyPart(_ body: ByteBuffer) -> Action { switch self.state { case .initialized, .waitForChannelToBecomeWritable: - preconditionFailure("How can we receive a response head before completely sending a request head ourselves. Invalid state: \(self.state)") + preconditionFailure( + "How can we receive a response head before completely sending a request head ourselves. Invalid state: \(self.state)" + ) case .running(_, .waitingForHead): - preconditionFailure("How can we receive a response body, if we haven't received a head. Invalid state: \(self.state)") + preconditionFailure( + "How can we receive a response body, if we haven't received a head. Invalid state: \(self.state)" + ) case .running(let requestState, .receivingBody(let head, var responseStreamState)): return self.avoidingStateMachineCoW { state -> Action in @@ -582,7 +606,9 @@ struct HTTPRequestStateMachine { } case .running(_, .endReceived), .finished: - preconditionFailure("How can we successfully finish the request, before having received a head. Invalid state: \(self.state)") + preconditionFailure( + "How can we successfully finish the request, before having received a head. Invalid state: \(self.state)" + ) case .failed: return .wait @@ -595,20 +621,31 @@ struct HTTPRequestStateMachine { private mutating func receivedHTTPResponseEnd() -> Action { switch self.state { case .initialized, .waitForChannelToBecomeWritable: - preconditionFailure("How can we receive a response end before completely sending a request head ourselves. Invalid state: \(self.state)") + preconditionFailure( + "How can we receive a response end before completely sending a request head ourselves. Invalid state: \(self.state)" + ) case .running(_, .waitingForHead): - preconditionFailure("How can we receive a response end, if we haven't a received a head. Invalid state: \(self.state)") + preconditionFailure( + "How can we receive a response end, if we haven't a received a head. Invalid state: \(self.state)" + ) - case .running(.streaming(let expectedBodyLength, let sentBodyBytes, let producerState), .receivingBody(let head, var responseStreamState)) - where head.status.code < 300: + case .running( + .streaming(let expectedBodyLength, let sentBodyBytes, let producerState), + .receivingBody(let head, var responseStreamState) + ) + where head.status.code < 300: return self.avoidingStateMachineCoW { state -> Action in let (remainingBuffer, connectionAction) = responseStreamState.end() switch connectionAction { case .none: state = .running( - .streaming(expectedBodyLength: expectedBodyLength, sentBodyBytes: sentBodyBytes, producer: producerState), + .streaming( + expectedBodyLength: expectedBodyLength, + sentBodyBytes: sentBodyBytes, + producer: producerState + ), .endReceived ) return .forwardResponseBodyParts(remainingBuffer) @@ -624,7 +661,10 @@ struct HTTPRequestStateMachine { case .running(.streaming(_, _, let producerState), .receivingBody(let head, var responseStreamState)): assert(head.status.code >= 300) - assert(producerState == .paused, "Expected to have paused the request body stream, when the head was received. Invalid state: \(self.state)") + assert( + producerState == .paused, + "Expected to have paused the request body stream, when the head was received. Invalid state: \(self.state)" + ) return self.avoidingStateMachineCoW { state -> Action in // We can ignore the connectionAction from the responseStreamState, since the @@ -647,7 +687,9 @@ struct HTTPRequestStateMachine { } case .running(_, .endReceived), .finished: - preconditionFailure("How can we receive a response end, if another one was already received. Invalid state: \(self.state)") + preconditionFailure( + "How can we receive a response end, if another one was already received. Invalid state: \(self.state)" + ) case .failed: return .wait @@ -660,9 +702,11 @@ struct HTTPRequestStateMachine { mutating func demandMoreResponseBodyParts() -> Action { switch self.state { case .initialized, - .running(_, .waitingForHead), - .waitForChannelToBecomeWritable: - preconditionFailure("The response is expected to only ask for more data after the response head was forwarded \(self.state)") + .running(_, .waitingForHead), + .waitForChannelToBecomeWritable: + preconditionFailure( + "The response is expected to only ask for more data after the response head was forwarded \(self.state)" + ) case .running(let requestState, .receivingBody(let head, var responseStreamState)): return self.avoidingStateMachineCoW { state -> Action in @@ -672,8 +716,8 @@ struct HTTPRequestStateMachine { } case .running(_, .endReceived), - .finished, - .failed: + .finished, + .failed: return .wait case .modifying: @@ -684,9 +728,11 @@ struct HTTPRequestStateMachine { mutating func idleReadTimeoutTriggered() -> Action { switch self.state { case .initialized, - .waitForChannelToBecomeWritable, - .running(.streaming, _): - preconditionFailure("We only schedule idle read timeouts after we have sent the complete request. Invalid state: \(self.state)") + .waitForChannelToBecomeWritable, + .running(.streaming, _): + preconditionFailure( + "We only schedule idle read timeouts after we have sent the complete request. Invalid state: \(self.state)" + ) case .running(.endSent, .waitingForHead), .running(.endSent, .receivingBody): let error = HTTPClientError.readTimeout @@ -707,8 +753,10 @@ struct HTTPRequestStateMachine { mutating func idleWriteTimeoutTriggered() -> Action { switch self.state { case .initialized, - .waitForChannelToBecomeWritable: - preconditionFailure("We only schedule idle write timeouts while the request is being sent. Invalid state: \(self.state)") + .waitForChannelToBecomeWritable: + preconditionFailure( + "We only schedule idle write timeouts while the request is being sent. Invalid state: \(self.state)" + ) case .running(.streaming, _): let error = HTTPClientError.writeTimeout @@ -733,7 +781,10 @@ struct HTTPRequestStateMachine { self.state = .running(.endSent, .waitingForHead) return .sendRequestHead(head, sendEnd: true) } else { - self.state = .running(.streaming(expectedBodyLength: length, sentBodyBytes: 0, producer: .paused), .waitingForHead) + self.state = .running( + .streaming(expectedBodyLength: length, sentBodyBytes: 0, producer: .paused), + .waitingForHead + ) return .sendRequestHead(head, sendEnd: false) } } @@ -745,11 +796,14 @@ struct HTTPRequestStateMachine { case .running(.streaming(let expectedBodyLength, let sentBodyBytes, producer: .paused), let responseState): let startProducing = self.isChannelWritable && expectedBodyLength != sentBodyBytes - self.state = .running(.streaming( - expectedBodyLength: expectedBodyLength, - sentBodyBytes: sentBodyBytes, - producer: startProducing ? .producing : .paused - ), responseState) + self.state = .running( + .streaming( + expectedBodyLength: expectedBodyLength, + sentBodyBytes: sentBodyBytes, + producer: startProducing ? .producing : .paused + ), + responseState + ) return .notifyRequestHeadSendSuccessfully( resumeRequestBodyStream: startProducing, startIdleTimer: false @@ -757,7 +811,9 @@ struct HTTPRequestStateMachine { case .running(.endSent, _): return .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true) case .running(.streaming(_, _, producer: .producing), _): - preconditionFailure("request body producing can not start before we have successfully send the header \(self.state)") + preconditionFailure( + "request body producing can not start before we have successfully send the header \(self.state)" + ) case .failed: return .wait @@ -830,7 +886,8 @@ extension HTTPRequestStateMachine: CustomStringConvertible { case .waitForChannelToBecomeWritable: return "HTTPRequestStateMachine(.waitForChannelToBecomeWritable, isWritable: \(self.isChannelWritable))" case .running(let requestState, let responseState): - return "HTTPRequestStateMachine(.running(request: \(requestState), response: \(responseState)), isWritable: \(self.isChannelWritable))" + return + "HTTPRequestStateMachine(.running(request: \(requestState), response: \(responseState)), isWritable: \(self.isChannelWritable))" case .finished: return "HTTPRequestStateMachine(.finished, isWritable: \(self.isChannelWritable))" case .failed(let error): @@ -844,7 +901,7 @@ extension HTTPRequestStateMachine: CustomStringConvertible { extension HTTPRequestStateMachine.RequestState: CustomStringConvertible { var description: String { switch self { - case .streaming(expectedBodyLength: let expected, let sent, producer: let producer): + case .streaming(expectedBodyLength: let expected, let sent, let producer): return ".streaming(sent: \(expected != nil ? String(expected!) : "-"), sent: \(sent), producer: \(producer)" case .endSent: return ".endSent" diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift index cc7c7cfa1..86a54273d 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+Backoff.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import NIOCore + #if canImport(Darwin) import func Darwin.pow #elseif canImport(Musl) diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1Connections.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1Connections.swift index 1428a918b..15138a141 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1Connections.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1Connections.swift @@ -71,7 +71,7 @@ extension HTTPConnectionPool { var idleAndNoRemainingUses: Bool { switch self.state { - case .idle(_, since: _, remainingUses: let remainingUses): + case .idle(_, since: _, let remainingUses): if let remainingUses = remainingUses { return remainingUses <= 0 } else { @@ -139,7 +139,7 @@ extension HTTPConnectionPool { mutating func lease() -> Connection { switch self.state { - case .idle(let connection, since: _, remainingUses: let remainingUses): + case .idle(let connection, since: _, let remainingUses): self.state = .leased(connection, remainingUses: remainingUses.map { $0 - 1 }) return connection case .backingOff, .starting, .leased, .closed: @@ -208,7 +208,9 @@ extension HTTPConnectionPool { context.cancel.append(connection) return .keepConnection case .closed: - preconditionFailure("Unexpected state: Did not expect to have connections with this state in the state machine: \(self.state)") + preconditionFailure( + "Unexpected state: Did not expect to have connections with this state in the state machine: \(self.state)" + ) } } @@ -232,7 +234,9 @@ extension HTTPConnectionPool { case .leased: return .keepConnection case .closed: - preconditionFailure("Unexpected state: Did not expect to have connections with this state in the state machine: \(self.state)") + preconditionFailure( + "Unexpected state: Did not expect to have connections with this state in the state machine: \(self.state)" + ) } } } @@ -316,7 +320,7 @@ extension HTTPConnectionPool { } func startingEventLoopConnections(on eventLoop: EventLoop) -> Int { - return self.connections[self.overflowIndex.. [(Connection.ID, EventLoop)] in // We need a connection for each queued request with a required event loop. // Therefore, we look how many request we have queued for a given `eventLoop` and // how many connections we are already starting on the given `eventLoop`. // If we have not enough, we will create additional connections to have at least // on connection per request. - let connectionsToStart = requestCount - startingRequiredEventLoopConnectionCount[eventLoop.id, default: 0] + let connectionsToStart = + requestCount - startingRequiredEventLoopConnectionCount[eventLoop.id, default: 0] return stride(from: 0, to: connectionsToStart, by: 1).lazy.map { _ in (self.createNewOverflowConnection(on: eventLoop), eventLoop) } @@ -668,7 +677,8 @@ extension HTTPConnectionPool { // event loop we will continue with the event loop with the second most queued requests // and so on and so forth. The `generalPurposeRequestCountGroupedByPreferredEventLoop` // array is already ordered so we can just iterate over it without sorting by request count. - let newGeneralPurposeConnections: [(Connection.ID, EventLoop)] = generalPurposeRequestCountGroupedByPreferredEventLoop + let newGeneralPurposeConnections: [(Connection.ID, EventLoop)] = + generalPurposeRequestCountGroupedByPreferredEventLoop // we do not want to allocated intermediate arrays. .lazy // we flatten the grouped list of event loops by lazily repeating the event loop diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1StateMachine.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1StateMachine.swift index 2629b0ea2..09b1dc85e 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1StateMachine.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1StateMachine.swift @@ -80,7 +80,10 @@ extension HTTPConnectionPool { requests: RequestQueue ) -> ConnectionMigrationAction { precondition(self.connections.isEmpty, "expected an empty state machine but connections are not empty") - precondition(self.http2Connections == nil, "expected an empty state machine but http2Connections are not nil") + precondition( + self.http2Connections == nil, + "expected an empty state machine but http2Connections are not nil" + ) precondition(self.requests.isEmpty, "expected an empty state machine but requests are not empty") self.requests = requests @@ -100,7 +103,8 @@ extension HTTPConnectionPool { let createConnections = self.connections.createConnectionsAfterMigrationIfNeeded( requiredEventLoopOfPendingRequests: requests.requestCountGroupedByRequiredEventLoop(), - generalPurposeRequestCountGroupedByPreferredEventLoop: requests.generalPurposeRequestCountGroupedByPreferredEventLoop() + generalPurposeRequestCountGroupedByPreferredEventLoop: + requests.generalPurposeRequestCountGroupedByPreferredEventLoop() ) if !http2Connections.isEmpty { @@ -229,7 +233,9 @@ extension HTTPConnectionPool { case .running: guard self.retryConnectionEstablishment else { guard let (index, _) = self.connections.failConnection(connectionID) else { - preconditionFailure("A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost.") + preconditionFailure( + "A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost." + ) } self.connections.removeConnection(at: index) @@ -295,7 +301,10 @@ extension HTTPConnectionPool { return .none } - precondition(self.lifecycleState == .running, "If we are shutting down, we must not have any idle connections") + precondition( + self.lifecycleState == .running, + "If we are shutting down, we must not have any idle connections" + ) return .init( request: .none, @@ -561,7 +570,8 @@ extension HTTPConnectionPool { // MARK: HTTP2 - mutating func newHTTP2MaxConcurrentStreamsReceived(_ connectionID: Connection.ID, newMaxStreams: Int) -> Action { + mutating func newHTTP2MaxConcurrentStreamsReceived(_ connectionID: Connection.ID, newMaxStreams: Int) -> Action + { // The `http2Connections` are optional here: // Connections report events back to us, if they are in a shutdown that was // initiated by the state machine. For this reason this callback might be invoked @@ -663,6 +673,7 @@ extension HTTPConnectionPool.HTTP1StateMachine: CustomStringConvertible { let stats = self.connections.stats let queued = self.requests.count - return "connections: [connecting: \(stats.connecting) | backoff: \(stats.backingOff) | leased: \(stats.leased) | idle: \(stats.idle)], queued: \(queued)" + return + "connections: [connecting: \(stats.connecting) | backoff: \(stats.backingOff) | leased: \(stats.leased) | idle: \(stats.idle)], queued: \(queued)" } } diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2Connections.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2Connections.swift index 01d68b8e4..dbb6b2d30 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2Connections.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2Connections.swift @@ -117,7 +117,13 @@ extension HTTPConnectionPool { preconditionFailure("Invalid state: \(self.state)") case .starting(let maxUses): - self.state = .active(conn, maxStreams: maxStreams, usedStreams: 0, lastIdle: .now(), remainingUses: maxUses) + self.state = .active( + conn, + maxStreams: maxStreams, + usedStreams: 0, + lastIdle: .now(), + remainingUses: maxUses + ) if let maxUses = maxUses { return min(maxStreams, maxUses) } else { @@ -136,7 +142,13 @@ extension HTTPConnectionPool { preconditionFailure("Invalid state for updating max concurrent streams: \(self.state)") case .active(let conn, _, let usedStreams, let lastIdle, let remainingUses): - self.state = .active(conn, maxStreams: maxStreams, usedStreams: usedStreams, lastIdle: lastIdle, remainingUses: remainingUses) + self.state = .active( + conn, + maxStreams: maxStreams, + usedStreams: usedStreams, + lastIdle: lastIdle, + remainingUses: remainingUses + ) let availableStreams = max(maxStreams - usedStreams, 0) if let remainingUses = remainingUses { return min(remainingUses, availableStreams) @@ -192,8 +204,17 @@ extension HTTPConnectionPool { case .active(let conn, let maxStreams, var usedStreams, let lastIdle, let remainingUses): usedStreams += count precondition(usedStreams <= maxStreams, "tried to lease a connection which is not available") - precondition(remainingUses.map { $0 >= count } ?? true, "tried to lease streams from a connection which does not have enough remaining streams") - self.state = .active(conn, maxStreams: maxStreams, usedStreams: usedStreams, lastIdle: lastIdle, remainingUses: remainingUses.map { $0 - count }) + precondition( + remainingUses.map { $0 >= count } ?? true, + "tried to lease streams from a connection which does not have enough remaining streams" + ) + self.state = .active( + conn, + maxStreams: maxStreams, + usedStreams: usedStreams, + lastIdle: lastIdle, + remainingUses: remainingUses.map { $0 - count } + ) return conn } } @@ -212,7 +233,13 @@ extension HTTPConnectionPool { lastIdle = .now() } - self.state = .active(conn, maxStreams: maxStreams, usedStreams: usedStreams, lastIdle: lastIdle, remainingUses: remainingUses) + self.state = .active( + conn, + maxStreams: maxStreams, + usedStreams: usedStreams, + lastIdle: lastIdle, + remainingUses: remainingUses + ) let availableStreams = max(maxStreams &- usedStreams, 0) if let remainingUses = remainingUses { return min(availableStreams, remainingUses) @@ -282,7 +309,9 @@ extension HTTPConnectionPool { return .keepConnection case .closed: - preconditionFailure("Unexpected state for cleanup: Did not expect to have closed connections in the state machine.") + preconditionFailure( + "Unexpected state for cleanup: Did not expect to have closed connections in the state machine." + ) } } @@ -341,7 +370,9 @@ extension HTTPConnectionPool { return .removeConnection case .closed: - preconditionFailure("Unexpected state: Did not expect to have connections with this state in the state machine: \(self.state)") + preconditionFailure( + "Unexpected state: Did not expect to have connections with this state in the state machine: \(self.state)" + ) } } @@ -388,16 +419,20 @@ extension HTTPConnectionPool { backingOff: [(Connection.ID, EventLoop)] ) { for (connectionID, eventLoop) in starting { - let newConnection = HTTP2ConnectionState(connectionID: connectionID, - eventLoop: eventLoop, - maximumUses: self.maximumConnectionUses) + let newConnection = HTTP2ConnectionState( + connectionID: connectionID, + eventLoop: eventLoop, + maximumUses: self.maximumConnectionUses + ) self.connections.append(newConnection) } for (connectionID, eventLoop) in backingOff { - var backingOffConnection = HTTP2ConnectionState(connectionID: connectionID, - eventLoop: eventLoop, - maximumUses: self.maximumConnectionUses) + var backingOffConnection = HTTP2ConnectionState( + connectionID: connectionID, + eventLoop: eventLoop, + maximumUses: self.maximumConnectionUses + ) // TODO: Maybe we want to add a static init for backing off connections to HTTP2ConnectionState backingOffConnection.failedToConnect() self.connections.append(backingOffConnection) @@ -503,9 +538,11 @@ extension HTTPConnectionPool { "we should not create more than one connection per event loop" ) - let connection = HTTP2ConnectionState(connectionID: self.generator.next(), - eventLoop: eventLoop, - maximumUses: self.maximumConnectionUses) + let connection = HTTP2ConnectionState( + connectionID: self.generator.next(), + eventLoop: eventLoop, + maximumUses: self.maximumConnectionUses + ) self.connections.append(connection) return connection.connectionID } @@ -518,11 +555,17 @@ extension HTTPConnectionPool { /// - Returns: An index and an ``EstablishedConnectionContext`` to determine the next action for the now idle connection. /// Call ``leaseStreams(at:count:)`` or ``closeConnection(at:)`` with the supplied index after /// this. - mutating func newHTTP2ConnectionEstablished(_ connection: Connection, maxConcurrentStreams: Int) -> (Int, EstablishedConnectionContext) { + mutating func newHTTP2ConnectionEstablished( + _ connection: Connection, + maxConcurrentStreams: Int + ) -> (Int, EstablishedConnectionContext) { guard let index = self.connections.firstIndex(where: { $0.connectionID == connection.id }) else { preconditionFailure("There is a new connection that we didn't request!") } - precondition(connection.eventLoop === self.connections[index].eventLoop, "Expected the new connection to be on EL") + precondition( + connection.eventLoop === self.connections[index].eventLoop, + "Expected the new connection to be on EL" + ) let availableStreams = self.connections[index].connected(connection, maxStreams: maxConcurrentStreams) let context = EstablishedConnectionContext( availableStreams: availableStreams, diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2StateMachine.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2StateMachine.swift index 83a7647f4..2372cab4b 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2StateMachine.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2StateMachine.swift @@ -47,8 +47,10 @@ extension HTTPConnectionPool { self.idGenerator = idGenerator self.requests = RequestQueue() - self.connections = HTTP2Connections(generator: idGenerator, - maximumConnectionUses: maximumConnectionUses) + self.connections = HTTP2Connections( + generator: idGenerator, + maximumConnectionUses: maximumConnectionUses + ) self.lifecycleState = lifecycleState self.retryConnectionEstablishment = retryConnectionEstablishment } @@ -83,7 +85,10 @@ extension HTTPConnectionPool { requests: RequestQueue ) -> ConnectionMigrationAction { precondition(self.connections.isEmpty, "expected an empty state machine but connections are not empty") - precondition(self.http1Connections == nil, "expected an empty state machine but http1Connections are not nil") + precondition( + self.http1Connections == nil, + "expected an empty state machine but http1Connections are not nil" + ) precondition(self.requests.isEmpty, "expected an empty state machine but requests are not empty") self.requests = requests @@ -93,7 +98,7 @@ extension HTTPConnectionPool { self.connections = http2Connections } - var http1Connections = http1Connections // make http1Connections mutable + var http1Connections = http1Connections // make http1Connections mutable let context = http1Connections.migrateToHTTP2() self.connections.migrateFromHTTP1( starting: context.starting, @@ -215,7 +220,10 @@ extension HTTPConnectionPool { .init(self._newHTTP2ConnectionEstablished(connection, maxConcurrentStreams: maxConcurrentStreams)) } - private mutating func _newHTTP2ConnectionEstablished(_ connection: Connection, maxConcurrentStreams: Int) -> EstablishedAction { + private mutating func _newHTTP2ConnectionEstablished( + _ connection: Connection, + maxConcurrentStreams: Int + ) -> EstablishedAction { self.failedConsecutiveConnectionAttempts = 0 self.lastConnectFailure = nil if self.connections.hasActiveConnection(for: connection.eventLoop) { @@ -296,8 +304,14 @@ extension HTTPConnectionPool { } } - mutating func newHTTP2MaxConcurrentStreamsReceived(_ connectionID: Connection.ID, newMaxStreams: Int) -> Action { - guard let (index, context) = self.connections.newHTTP2MaxConcurrentStreamsReceived(connectionID, newMaxStreams: newMaxStreams) else { + mutating func newHTTP2MaxConcurrentStreamsReceived(_ connectionID: Connection.ID, newMaxStreams: Int) -> Action + { + guard + let (index, context) = self.connections.newHTTP2MaxConcurrentStreamsReceived( + connectionID, + newMaxStreams: newMaxStreams + ) + else { // When a connection close is initiated by the connection pool, the connection will // still report further events (like newMaxConcurrentStreamsReceived) to the state // machine. In those cases we must ignore the event. @@ -341,15 +355,15 @@ extension HTTPConnectionPool { // we need to start a new on connection in two cases: let needGeneralPurposeConnection = // 1. if we have general purpose requests - !self.requests.isEmpty(for: nil) && + !self.requests.isEmpty(for: nil) // and no connection starting or active - !context.hasGeneralPurposeConnection + && !context.hasGeneralPurposeConnection let needRequiredEventLoopConnection = // 2. or if we have requests for a required event loop - !self.requests.isEmpty(for: eventLoop) && + !self.requests.isEmpty(for: eventLoop) // and no connection starting or active for the given event loop - !context.hasConnectionOnSpecifiedEventLoop + && !context.hasConnectionOnSpecifiedEventLoop guard needGeneralPurposeConnection || needRequiredEventLoopConnection else { // otherwise we can remove the connection @@ -357,7 +371,8 @@ extension HTTPConnectionPool { return .none } - let (newConnectionID, previousEventLoop) = self.connections.createNewConnectionByReplacingClosedConnection(at: index) + let (newConnectionID, previousEventLoop) = self.connections + .createNewConnectionByReplacingClosedConnection(at: index) precondition(previousEventLoop === eventLoop) return .init( @@ -413,7 +428,9 @@ extension HTTPConnectionPool { case .running: guard self.retryConnectionEstablishment else { guard let (index, _) = self.connections.failConnection(connectionID) else { - preconditionFailure("A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost.") + preconditionFailure( + "A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost." + ) } self.connections.removeConnection(at: index) @@ -425,10 +442,15 @@ extension HTTPConnectionPool { let eventLoop = self.connections.backoffNextConnectionAttempt(connectionID) let backoff = calculateBackoff(failedAttempt: self.failedConsecutiveConnectionAttempts) - return .init(request: .none, connection: .scheduleBackoffTimer(connectionID, backoff: backoff, on: eventLoop)) + return .init( + request: .none, + connection: .scheduleBackoffTimer(connectionID, backoff: backoff, on: eventLoop) + ) case .shuttingDown: guard let (index, context) = self.connections.failConnection(connectionID) else { - preconditionFailure("A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost.") + preconditionFailure( + "A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost." + ) } return self.nextActionForFailedConnection(at: index, on: context.eventLoop) case .shutDown: @@ -505,7 +527,10 @@ extension HTTPConnectionPool { return .none } - precondition(self.lifecycleState == .running, "If we are shutting down, we must not have any idle connections") + precondition( + self.lifecycleState == .running, + "If we are shutting down, we must not have any idle connections" + ) return .init( request: .none, @@ -558,7 +583,10 @@ extension HTTPConnectionPool { case .shuttingDown(let unclean): if self.connections.isEmpty { // if the http2connections are empty as well, there are no more connections. Shutdown completed. - return .init(request: .none, connection: .closeConnection(connection, isShutdown: .yes(unclean: unclean))) + return .init( + request: .none, + connection: .closeConnection(connection, isShutdown: .yes(unclean: unclean)) + ) } else { return .init(request: .none, connection: .closeConnection(connection, isShutdown: .no)) } diff --git a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+StateMachine.swift b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+StateMachine.swift index a86bbe8a3..6dfd4223e 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+StateMachine.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+StateMachine.swift @@ -134,11 +134,14 @@ extension HTTPConnectionPool { } mutating func executeRequest(_ request: Request) -> Action { - self.state.modify(http1: { http1 in - http1.executeRequest(request) - }, http2: { http2 in - http2.executeRequest(request) - }) + self.state.modify( + http1: { http1 in + http1.executeRequest(request) + }, + http2: { http2 in + http2.executeRequest(request) + } + ) } mutating func newHTTP1ConnectionCreated(_ connection: Connection) -> Action { @@ -199,60 +202,82 @@ extension HTTPConnectionPool { } } - mutating func newHTTP2MaxConcurrentStreamsReceived(_ connectionID: Connection.ID, newMaxStreams: Int) -> Action { - self.state.modify(http1: { http1 in - http1.newHTTP2MaxConcurrentStreamsReceived(connectionID, newMaxStreams: newMaxStreams) - }, http2: { http2 in - http2.newHTTP2MaxConcurrentStreamsReceived(connectionID, newMaxStreams: newMaxStreams) - }) + mutating func newHTTP2MaxConcurrentStreamsReceived(_ connectionID: Connection.ID, newMaxStreams: Int) -> Action + { + self.state.modify( + http1: { http1 in + http1.newHTTP2MaxConcurrentStreamsReceived(connectionID, newMaxStreams: newMaxStreams) + }, + http2: { http2 in + http2.newHTTP2MaxConcurrentStreamsReceived(connectionID, newMaxStreams: newMaxStreams) + } + ) } mutating func http2ConnectionGoAwayReceived(_ connectionID: Connection.ID) -> Action { - self.state.modify(http1: { http1 in - http1.http2ConnectionGoAwayReceived(connectionID) - }, http2: { http2 in - http2.http2ConnectionGoAwayReceived(connectionID) - }) + self.state.modify( + http1: { http1 in + http1.http2ConnectionGoAwayReceived(connectionID) + }, + http2: { http2 in + http2.http2ConnectionGoAwayReceived(connectionID) + } + ) } mutating func http2ConnectionClosed(_ connectionID: Connection.ID) -> Action { - self.state.modify(http1: { http1 in - http1.http2ConnectionClosed(connectionID) - }, http2: { http2 in - http2.http2ConnectionClosed(connectionID) - }) + self.state.modify( + http1: { http1 in + http1.http2ConnectionClosed(connectionID) + }, + http2: { http2 in + http2.http2ConnectionClosed(connectionID) + } + ) } mutating func http2ConnectionStreamClosed(_ connectionID: Connection.ID) -> Action { - self.state.modify(http1: { http1 in - http1.http2ConnectionStreamClosed(connectionID) - }, http2: { http2 in - http2.http2ConnectionStreamClosed(connectionID) - }) + self.state.modify( + http1: { http1 in + http1.http2ConnectionStreamClosed(connectionID) + }, + http2: { http2 in + http2.http2ConnectionStreamClosed(connectionID) + } + ) } mutating func failedToCreateNewConnection(_ error: Error, connectionID: Connection.ID) -> Action { - self.state.modify(http1: { http1 in - http1.failedToCreateNewConnection(error, connectionID: connectionID) - }, http2: { http2 in - http2.failedToCreateNewConnection(error, connectionID: connectionID) - }) + self.state.modify( + http1: { http1 in + http1.failedToCreateNewConnection(error, connectionID: connectionID) + }, + http2: { http2 in + http2.failedToCreateNewConnection(error, connectionID: connectionID) + } + ) } mutating func waitingForConnectivity(_ error: Error, connectionID: Connection.ID) -> Action { - self.state.modify(http1: { http1 in - http1.waitingForConnectivity(error, connectionID: connectionID) - }, http2: { http2 in - http2.waitingForConnectivity(error, connectionID: connectionID) - }) + self.state.modify( + http1: { http1 in + http1.waitingForConnectivity(error, connectionID: connectionID) + }, + http2: { http2 in + http2.waitingForConnectivity(error, connectionID: connectionID) + } + ) } mutating func connectionCreationBackoffDone(_ connectionID: Connection.ID) -> Action { - self.state.modify(http1: { http1 in - http1.connectionCreationBackoffDone(connectionID) - }, http2: { http2 in - http2.connectionCreationBackoffDone(connectionID) - }) + self.state.modify( + http1: { http1 in + http1.connectionCreationBackoffDone(connectionID) + }, + http2: { http2 in + http2.connectionCreationBackoffDone(connectionID) + } + ) } /// A request has timed out. @@ -261,11 +286,14 @@ extension HTTPConnectionPool { /// request, but don't need to cancel the timer (it already triggered). If a request is cancelled /// we don't need to fail it but we need to cancel its timeout timer. mutating func timeoutRequest(_ requestID: Request.ID) -> Action { - self.state.modify(http1: { http1 in - http1.timeoutRequest(requestID) - }, http2: { http2 in - http2.timeoutRequest(requestID) - }) + self.state.modify( + http1: { http1 in + http1.timeoutRequest(requestID) + }, + http2: { http2 in + http2.timeoutRequest(requestID) + } + ) } /// A request was cancelled. @@ -274,44 +302,59 @@ extension HTTPConnectionPool { /// need to cancel its timeout timer. If a request times out, we need to fail the request, but don't /// need to cancel the timer (it already triggered). mutating func cancelRequest(_ requestID: Request.ID) -> Action { - self.state.modify(http1: { http1 in - http1.cancelRequest(requestID) - }, http2: { http2 in - http2.cancelRequest(requestID) - }) + self.state.modify( + http1: { http1 in + http1.cancelRequest(requestID) + }, + http2: { http2 in + http2.cancelRequest(requestID) + } + ) } mutating func connectionIdleTimeout(_ connectionID: Connection.ID) -> Action { - self.state.modify(http1: { http1 in - http1.connectionIdleTimeout(connectionID) - }, http2: { http2 in - http2.connectionIdleTimeout(connectionID) - }) + self.state.modify( + http1: { http1 in + http1.connectionIdleTimeout(connectionID) + }, + http2: { http2 in + http2.connectionIdleTimeout(connectionID) + } + ) } /// A connection has been closed mutating func http1ConnectionClosed(_ connectionID: Connection.ID) -> Action { - self.state.modify(http1: { http1 in - http1.http1ConnectionClosed(connectionID) - }, http2: { http2 in - http2.http1ConnectionClosed(connectionID) - }) + self.state.modify( + http1: { http1 in + http1.http1ConnectionClosed(connectionID) + }, + http2: { http2 in + http2.http1ConnectionClosed(connectionID) + } + ) } mutating func http1ConnectionReleased(_ connectionID: Connection.ID) -> Action { - self.state.modify(http1: { http1 in - http1.http1ConnectionReleased(connectionID) - }, http2: { http2 in - http2.http1ConnectionReleased(connectionID) - }) + self.state.modify( + http1: { http1 in + http1.http1ConnectionReleased(connectionID) + }, + http2: { http2 in + http2.http1ConnectionReleased(connectionID) + } + ) } mutating func shutdown() -> Action { - return self.state.modify(http1: { http1 in - http1.shutdown() - }, http2: { http2 in - http2.shutdown() - }) + self.state.modify( + http1: { http1 in + http1.shutdown() + }, + http2: { http2 in + http2.shutdown() + } + ) } } } @@ -362,7 +405,10 @@ extension HTTPConnectionPool.StateMachine { enum EstablishedConnectionAction { case none case scheduleTimeoutTimer(HTTPConnectionPool.Connection.ID, on: EventLoop) - case closeConnection(HTTPConnectionPool.Connection, isShutdown: HTTPConnectionPool.StateMachine.ConnectionAction.IsShutdown) + case closeConnection( + HTTPConnectionPool.Connection, + isShutdown: HTTPConnectionPool.StateMachine.ConnectionAction.IsShutdown + ) } } @@ -403,8 +449,7 @@ extension HTTPConnectionPool.StateMachine.ConnectionAction { case .closeConnection(let connection, let isShutdown): guard isShutdown == .no else { precondition( - migrationAction.closeConnections.isEmpty && - migrationAction.createConnections.isEmpty, + migrationAction.closeConnections.isEmpty && migrationAction.createConnections.isEmpty, "migration actions are not supported during shutdown" ) return .closeConnection(connection, isShutdown: isShutdown) diff --git a/Sources/AsyncHTTPClient/FileDownloadDelegate.swift b/Sources/AsyncHTTPClient/FileDownloadDelegate.swift index 9a351f3c1..1f869506a 100644 --- a/Sources/AsyncHTTPClient/FileDownloadDelegate.swift +++ b/Sources/AsyncHTTPClient/FileDownloadDelegate.swift @@ -87,12 +87,12 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate { path: path, pool: .some(pool), reportHead: reportHead.map { reportHead in - return { _, head in + { _, head in reportHead(head) } }, reportProgress: reportProgress.map { reportProgress in - return { _, head in + { _, head in reportProgress(head) } } @@ -117,12 +117,12 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate { path: path, pool: nil, reportHead: reportHead.map { reportHead in - return { _, head in + { _, head in reportHead(head) } }, reportProgress: reportProgress.map { reportProgress in - return { _, head in + { _, head in reportProgress(head) } } @@ -136,7 +136,8 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate { self.reportHead?(task, head) if let totalBytesString = head.headers.first(name: "Content-Length"), - let totalBytes = Int(totalBytesString) { + let totalBytes = Int(totalBytesString) + { self.progress.totalBytes = totalBytes } diff --git a/Sources/AsyncHTTPClient/FoundationExtensions.swift b/Sources/AsyncHTTPClient/FoundationExtensions.swift index 545da756b..452cb7b13 100644 --- a/Sources/AsyncHTTPClient/FoundationExtensions.swift +++ b/Sources/AsyncHTTPClient/FoundationExtensions.swift @@ -39,7 +39,16 @@ extension HTTPClient.Cookie { /// - maxAge: The cookie's age in seconds, defaults to nil. /// - httpOnly: Whether this cookie should be used by HTTP servers only, defaults to false. /// - secure: Whether this cookie should only be sent using secure channels, defaults to false. - public init(name: String, value: String, path: String = "/", domain: String? = nil, expires: Date? = nil, maxAge: Int? = nil, httpOnly: Bool = false, secure: Bool = false) { + public init( + name: String, + value: String, + path: String = "/", + domain: String? = nil, + expires: Date? = nil, + maxAge: Int? = nil, + httpOnly: Bool = false, + secure: Bool = false + ) { // FIXME: This should be failable and validate the inputs // (for example, checking that the strings are ASCII, path begins with "/", domain is not empty, etc). self.init( @@ -59,8 +68,8 @@ extension HTTPClient.Body { /// Create and stream body using `Data`. /// /// - parameters: - /// - bytes: Body `Data` representation. + /// - data: Body `Data` representation. public static func data(_ data: Data) -> HTTPClient.Body { - return self.bytes(data) + self.bytes(data) } } diff --git a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift index 9d9d6dfb7..847a99af2 100644 --- a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift +++ b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift @@ -12,7 +12,10 @@ // //===----------------------------------------------------------------------===// +import CAsyncHTTPClient +import NIOCore import NIOHTTP1 + #if canImport(xlocale) import xlocale #elseif canImport(locale_h) @@ -27,9 +30,6 @@ import Musl import Glibc #endif -import CAsyncHTTPClient -import NIOCore - extension HTTPClient { /// A representation of an HTTP cookie. public struct Cookie: Sendable { @@ -55,7 +55,6 @@ extension HTTPClient { /// - parameters: /// - header: String representation of the `Set-Cookie` response header. /// - defaultDomain: Default domain to use if cookie was sent without one. - /// - returns: nil if the header is invalid. public init?(header: String, defaultDomain: String) { // The parsing of "Set-Cookie" headers is defined by Section 5.2, RFC-6265: // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2 @@ -136,7 +135,16 @@ extension HTTPClient { /// - maxAge: The cookie's age in seconds, defaults to nil. /// - httpOnly: Whether this cookie should be used by HTTP servers only, defaults to false. /// - secure: Whether this cookie should only be sent using secure channels, defaults to false. - internal init(name: String, value: String, path: String = "/", domain: String? = nil, expires_timestamp: Int64? = nil, maxAge: Int? = nil, httpOnly: Bool = false, secure: Bool = false) { + internal init( + name: String, + value: String, + path: String = "/", + domain: String? = nil, + expires_timestamp: Int64? = nil, + maxAge: Int? = nil, + httpOnly: Bool = false, + secure: Bool = false + ) { self.name = name self.value = value self.path = path @@ -152,7 +160,7 @@ extension HTTPClient { extension HTTPClient.Response { /// List of HTTP cookies returned by the server. public var cookies: [HTTPClient.Cookie] { - return self.headers["set-cookie"].compactMap { HTTPClient.Cookie(header: $0, defaultDomain: self.host) } + self.headers["set-cookie"].compactMap { HTTPClient.Cookie(header: $0, defaultDomain: self.host) } } } @@ -222,7 +230,8 @@ private func parseTimestamp(_ utf8: String.UTF8View.SubSequence, format: String) } private func parseCookieTime(_ timestampUTF8: String.UTF8View.SubSequence) -> Int64? { - if timestampUTF8.contains(where: { $0 < 0x20 /* Control characters */ || $0 == 0x7F /* DEL */ }) { + // 0x20: Control characters or 0x7F: DEL + if timestampUTF8.contains(where: { $0 < 0x20 || $0 == 0x7F }) { return nil } var timestampUTF8 = timestampUTF8 @@ -235,8 +244,8 @@ private func parseCookieTime(_ timestampUTF8: String.UTF8View.SubSequence) -> In } guard var timeComponents = parseTimestamp(timestampUTF8, format: "%a, %d %b %Y %H:%M:%S") - ?? parseTimestamp(timestampUTF8, format: "%a, %d-%b-%y %H:%M:%S") - ?? parseTimestamp(timestampUTF8, format: "%a %b %d %H:%M:%S %Y") + ?? parseTimestamp(timestampUTF8, format: "%a, %d-%b-%y %H:%M:%S") + ?? parseTimestamp(timestampUTF8, format: "%a %b %d %H:%M:%S %Y") else { return nil } diff --git a/Sources/AsyncHTTPClient/HTTPClient+Proxy.swift b/Sources/AsyncHTTPClient/HTTPClient+Proxy.swift index 25b4b4555..e95c828ce 100644 --- a/Sources/AsyncHTTPClient/HTTPClient+Proxy.swift +++ b/Sources/AsyncHTTPClient/HTTPClient+Proxy.swift @@ -38,7 +38,10 @@ extension HTTPClient.Configuration { /// Specifies Proxy server authorization. public var authorization: HTTPClient.Authorization? { set { - precondition(self.type == .http(self.authorization), "SOCKS authorization support is not yet implemented.") + precondition( + self.type == .http(self.authorization), + "SOCKS authorization support is not yet implemented." + ) self.type = .http(newValue) } @@ -60,7 +63,7 @@ extension HTTPClient.Configuration { /// - host: proxy server host. /// - port: proxy server port. public static func server(host: String, port: Int) -> Proxy { - return .init(host: host, port: port, type: .http(nil)) + .init(host: host, port: port, type: .http(nil)) } /// Create a HTTP proxy. @@ -70,7 +73,7 @@ extension HTTPClient.Configuration { /// - port: proxy server port. /// - authorization: proxy server authorization. public static func server(host: String, port: Int, authorization: HTTPClient.Authorization? = nil) -> Proxy { - return .init(host: host, port: port, type: .http(authorization)) + .init(host: host, port: port, type: .http(authorization)) } /// Create a SOCKSv5 proxy. @@ -78,7 +81,7 @@ extension HTTPClient.Configuration { /// - parameter port: The SOCKSv5 proxy port, defaults to 1080. /// - returns: A new instance of `Proxy` configured to connect to a `SOCKSv5` server. public static func socksServer(host: String, port: Int = 1080) -> Proxy { - return .init(host: host, port: port, type: .socks) + .init(host: host, port: port, type: .socks) } } } diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 096fb9387..130a59f99 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -26,7 +26,7 @@ import NIOTransportServices extension Logger { private func requestInfo(_ request: HTTPClient.Request) -> Logger.Metadata.Value { - return "\(request.method) \(request.url)" + "\(request.method) \(request.url)" } func attachingRequestInformation(_ request: HTTPClient.Request, requestID: Int) -> Logger { @@ -80,23 +80,31 @@ public class HTTPClient { /// - parameters: /// - eventLoopGroupProvider: Specify how `EventLoopGroup` will be created. /// - configuration: Client configuration. - public convenience init(eventLoopGroupProvider: EventLoopGroupProvider, - configuration: Configuration = Configuration()) { - self.init(eventLoopGroupProvider: eventLoopGroupProvider, - configuration: configuration, - backgroundActivityLogger: HTTPClient.loggingDisabled) + public convenience init( + eventLoopGroupProvider: EventLoopGroupProvider, + configuration: Configuration = Configuration() + ) { + self.init( + eventLoopGroupProvider: eventLoopGroupProvider, + configuration: configuration, + backgroundActivityLogger: HTTPClient.loggingDisabled + ) } /// Create an ``HTTPClient`` with specified `EventLoopGroup` and configuration. /// /// - parameters: - /// - eventLoopGroupProvider: Specify how `EventLoopGroup` will be created. + /// - eventLoopGroup: Specify how `EventLoopGroup` will be created. /// - configuration: Client configuration. - public convenience init(eventLoopGroup: EventLoopGroup = HTTPClient.defaultEventLoopGroup, - configuration: Configuration = Configuration()) { - self.init(eventLoopGroupProvider: .shared(eventLoopGroup), - configuration: configuration, - backgroundActivityLogger: HTTPClient.loggingDisabled) + public convenience init( + eventLoopGroup: EventLoopGroup = HTTPClient.defaultEventLoopGroup, + configuration: Configuration = Configuration() + ) { + self.init( + eventLoopGroupProvider: .shared(eventLoopGroup), + configuration: configuration, + backgroundActivityLogger: HTTPClient.loggingDisabled + ) } /// Create an ``HTTPClient`` with specified `EventLoopGroup` provider and configuration. @@ -104,21 +112,26 @@ public class HTTPClient { /// - parameters: /// - eventLoopGroupProvider: Specify how `EventLoopGroup` will be created. /// - configuration: Client configuration. - public convenience init(eventLoopGroupProvider: EventLoopGroupProvider, - configuration: Configuration = Configuration(), - backgroundActivityLogger: Logger) { + /// - backgroundActivityLogger: The logger to use for background activity logs. + public convenience init( + eventLoopGroupProvider: EventLoopGroupProvider, + configuration: Configuration = Configuration(), + backgroundActivityLogger: Logger + ) { let eventLoopGroup: any EventLoopGroup switch eventLoopGroupProvider { case .shared(let group): eventLoopGroup = group - default: // handle `.createNew` without a deprecation warning + default: // handle `.createNew` without a deprecation warning eventLoopGroup = HTTPClient.defaultEventLoopGroup } - self.init(eventLoopGroup: eventLoopGroup, - configuration: configuration, - backgroundActivityLogger: backgroundActivityLogger) + self.init( + eventLoopGroup: eventLoopGroup, + configuration: configuration, + backgroundActivityLogger: backgroundActivityLogger + ) } /// Create an ``HTTPClient`` with specified `EventLoopGroup` and configuration. @@ -127,19 +140,25 @@ public class HTTPClient { /// - eventLoopGroup: The `EventLoopGroup` that the ``HTTPClient`` will use. /// - configuration: Client configuration. /// - backgroundActivityLogger: The `Logger` that will be used to log background any activity that's not associated with a request. - public convenience init(eventLoopGroup: any EventLoopGroup = HTTPClient.defaultEventLoopGroup, - configuration: Configuration = Configuration(), - backgroundActivityLogger: Logger) { - self.init(eventLoopGroup: eventLoopGroup, - configuration: configuration, - backgroundActivityLogger: backgroundActivityLogger, - canBeShutDown: true) + public convenience init( + eventLoopGroup: any EventLoopGroup = HTTPClient.defaultEventLoopGroup, + configuration: Configuration = Configuration(), + backgroundActivityLogger: Logger + ) { + self.init( + eventLoopGroup: eventLoopGroup, + configuration: configuration, + backgroundActivityLogger: backgroundActivityLogger, + canBeShutDown: true + ) } - internal required init(eventLoopGroup: EventLoopGroup, - configuration: Configuration = Configuration(), - backgroundActivityLogger: Logger, - canBeShutDown: Bool) { + internal required init( + eventLoopGroup: EventLoopGroup, + configuration: Configuration = Configuration(), + backgroundActivityLogger: Logger, + canBeShutDown: Bool + ) { self.canBeShutDown = canBeShutDown self.eventLoopGroup = eventLoopGroup self.configuration = configuration @@ -158,15 +177,19 @@ public class HTTPClient { case .shutDown: break case .shuttingDown: - preconditionFailure(""" - This state should be totally unreachable. While the HTTPClient is shutting down a \ - reference cycle should exist, that prevents it from deinit. - """) + preconditionFailure( + """ + This state should be totally unreachable. While the HTTPClient is shutting down a \ + reference cycle should exist, that prevents it from deinit. + """ + ) case .upAndRunning: - preconditionFailure(""" - Client not shut down before the deinit. Please call client.shutdown() when no \ - longer needed. Otherwise memory will leak. - """) + preconditionFailure( + """ + Client not shut down before the deinit. Please call client.shutdown() when no \ + longer needed. Otherwise memory will leak. + """ + ) } } } @@ -191,16 +214,19 @@ public class HTTPClient { /// In general, setting this parameter to `true` should make it easier and faster to catch related programming errors. func syncShutdown(requiresCleanClose: Bool) throws { if let eventLoop = MultiThreadedEventLoopGroup.currentEventLoop { - preconditionFailure(""" - BUG DETECTED: syncShutdown() must not be called when on an EventLoop. - Calling syncShutdown() on any EventLoop can lead to deadlocks. - Current eventLoop: \(eventLoop) - """) + preconditionFailure( + """ + BUG DETECTED: syncShutdown() must not be called when on an EventLoop. + Calling syncShutdown() on any EventLoop can lead to deadlocks. + Current eventLoop: \(eventLoop) + """ + ) } let errorStorageLock = NIOLock() let errorStorage: UnsafeMutableTransferBox = .init(nil) let continuation = DispatchWorkItem {} - self.shutdown(requiresCleanClose: requiresCleanClose, queue: DispatchQueue(label: "async-http-client.shutdown")) { error in + self.shutdown(requiresCleanClose: requiresCleanClose, queue: DispatchQueue(label: "async-http-client.shutdown")) + { error in if let error = error { errorStorageLock.withLock { errorStorage.wrappedValue = error @@ -301,7 +327,7 @@ public class HTTPClient { /// - url: Remote URL. /// - deadline: Point in time by which the request must complete. public func get(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture { - return self.get(url: url, deadline: deadline, logger: HTTPClient.loggingDisabled) + self.get(url: url, deadline: deadline, logger: HTTPClient.loggingDisabled) } /// Execute `GET` request using specified URL. @@ -311,7 +337,7 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func get(url: String, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(.GET, url: url, deadline: deadline, logger: logger) + self.execute(.GET, url: url, deadline: deadline, logger: logger) } /// Execute `POST` request using specified URL. @@ -321,7 +347,7 @@ public class HTTPClient { /// - body: Request body. /// - deadline: Point in time by which the request must complete. public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { - return self.post(url: url, body: body, deadline: deadline, logger: HTTPClient.loggingDisabled) + self.post(url: url, body: body, deadline: deadline, logger: HTTPClient.loggingDisabled) } /// Execute `POST` request using specified URL. @@ -331,8 +357,13 @@ public class HTTPClient { /// - body: Request body. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(.POST, url: url, body: body, deadline: deadline, logger: logger) + public func post( + url: String, + body: Body? = nil, + deadline: NIODeadline? = nil, + logger: Logger + ) -> EventLoopFuture { + self.execute(.POST, url: url, body: body, deadline: deadline, logger: logger) } /// Execute `PATCH` request using specified URL. @@ -342,7 +373,7 @@ public class HTTPClient { /// - body: Request body. /// - deadline: Point in time by which the request must complete. public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { - return self.patch(url: url, body: body, deadline: deadline, logger: HTTPClient.loggingDisabled) + self.patch(url: url, body: body, deadline: deadline, logger: HTTPClient.loggingDisabled) } /// Execute `PATCH` request using specified URL. @@ -352,8 +383,13 @@ public class HTTPClient { /// - body: Request body. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(.PATCH, url: url, body: body, deadline: deadline, logger: logger) + public func patch( + url: String, + body: Body? = nil, + deadline: NIODeadline? = nil, + logger: Logger + ) -> EventLoopFuture { + self.execute(.PATCH, url: url, body: body, deadline: deadline, logger: logger) } /// Execute `PUT` request using specified URL. @@ -363,7 +399,7 @@ public class HTTPClient { /// - body: Request body. /// - deadline: Point in time by which the request must complete. public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { - return self.put(url: url, body: body, deadline: deadline, logger: HTTPClient.loggingDisabled) + self.put(url: url, body: body, deadline: deadline, logger: HTTPClient.loggingDisabled) } /// Execute `PUT` request using specified URL. @@ -373,8 +409,13 @@ public class HTTPClient { /// - body: Request body. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(.PUT, url: url, body: body, deadline: deadline, logger: logger) + public func put( + url: String, + body: Body? = nil, + deadline: NIODeadline? = nil, + logger: Logger + ) -> EventLoopFuture { + self.execute(.PUT, url: url, body: body, deadline: deadline, logger: logger) } /// Execute `DELETE` request using specified URL. @@ -383,7 +424,7 @@ public class HTTPClient { /// - url: Remote URL. /// - deadline: The time when the request must have been completed by. public func delete(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture { - return self.delete(url: url, deadline: deadline, logger: HTTPClient.loggingDisabled) + self.delete(url: url, deadline: deadline, logger: HTTPClient.loggingDisabled) } /// Execute `DELETE` request using specified URL. @@ -393,7 +434,7 @@ public class HTTPClient { /// - deadline: The time when the request must have been completed by. /// - logger: The logger to use for this request. public func delete(url: String, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture { - return self.execute(.DELETE, url: url, deadline: deadline, logger: logger) + self.execute(.DELETE, url: url, deadline: deadline, logger: logger) } /// Execute arbitrary HTTP request using specified URL. @@ -404,7 +445,13 @@ public class HTTPClient { /// - body: Request body. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func execute(_ method: HTTPMethod = .GET, url: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + public func execute( + _ method: HTTPMethod = .GET, + url: String, + body: Body? = nil, + deadline: NIODeadline? = nil, + logger: Logger? = nil + ) -> EventLoopFuture { do { let request = try Request(url: url, method: method, body: body) return self.execute(request: request, deadline: deadline, logger: logger ?? HTTPClient.loggingDisabled) @@ -422,7 +469,14 @@ public class HTTPClient { /// - body: Request body. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func execute(_ method: HTTPMethod = .GET, socketPath: String, urlPath: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + public func execute( + _ method: HTTPMethod = .GET, + socketPath: String, + urlPath: String, + body: Body? = nil, + deadline: NIODeadline? = nil, + logger: Logger? = nil + ) -> EventLoopFuture { do { guard let url = URL(httpURLWithSocketPath: socketPath, uri: urlPath) else { throw HTTPClientError.invalidURL @@ -443,7 +497,14 @@ public class HTTPClient { /// - body: Request body. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func execute(_ method: HTTPMethod = .GET, secureSocketPath: String, urlPath: String, body: Body? = nil, deadline: NIODeadline? = nil, logger: Logger? = nil) -> EventLoopFuture { + public func execute( + _ method: HTTPMethod = .GET, + secureSocketPath: String, + urlPath: String, + body: Body? = nil, + deadline: NIODeadline? = nil, + logger: Logger? = nil + ) -> EventLoopFuture { do { guard let url = URL(httpsURLWithSocketPath: secureSocketPath, uri: urlPath) else { throw HTTPClientError.invalidURL @@ -461,7 +522,7 @@ public class HTTPClient { /// - request: HTTP request to execute. /// - deadline: Point in time by which the request must complete. public func execute(request: Request, deadline: NIODeadline? = nil) -> EventLoopFuture { - return self.execute(request: request, deadline: deadline, logger: HTTPClient.loggingDisabled) + self.execute(request: request, deadline: deadline, logger: HTTPClient.loggingDisabled) } /// Execute arbitrary HTTP request using specified URL. @@ -481,26 +542,40 @@ public class HTTPClient { /// - request: HTTP request to execute. /// - eventLoop: NIO Event Loop preference. /// - deadline: Point in time by which the request must complete. - public func execute(request: Request, eventLoop: EventLoopPreference, deadline: NIODeadline? = nil) -> EventLoopFuture { - return self.execute(request: request, - eventLoop: eventLoop, - deadline: deadline, - logger: HTTPClient.loggingDisabled) + public func execute( + request: Request, + eventLoop: EventLoopPreference, + deadline: NIODeadline? = nil + ) -> EventLoopFuture { + self.execute( + request: request, + eventLoop: eventLoop, + deadline: deadline, + logger: HTTPClient.loggingDisabled + ) } /// Execute arbitrary HTTP request and handle response processing using provided delegate. /// /// - parameters: /// - request: HTTP request to execute. - /// - eventLoop: NIO Event Loop preference. + /// - eventLoopPreference: NIO Event Loop preference. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func execute(request: Request, - eventLoop eventLoopPreference: EventLoopPreference, - deadline: NIODeadline? = nil, - logger: Logger?) -> EventLoopFuture { + public func execute( + request: Request, + eventLoop eventLoopPreference: EventLoopPreference, + deadline: NIODeadline? = nil, + logger: Logger? + ) -> EventLoopFuture { let accumulator = ResponseAccumulator(request: request) - return self.execute(request: request, delegate: accumulator, eventLoop: eventLoopPreference, deadline: deadline, logger: logger).futureResult + return self.execute( + request: request, + delegate: accumulator, + eventLoop: eventLoopPreference, + deadline: deadline, + logger: logger + ).futureResult } /// Execute arbitrary HTTP request and handle response processing using provided delegate. @@ -509,10 +584,12 @@ public class HTTPClient { /// - request: HTTP request to execute. /// - delegate: Delegate to process response parts. /// - deadline: Point in time by which the request must complete. - public func execute(request: Request, - delegate: Delegate, - deadline: NIODeadline? = nil) -> Task { - return self.execute(request: request, delegate: delegate, deadline: deadline, logger: HTTPClient.loggingDisabled) + public func execute( + request: Request, + delegate: Delegate, + deadline: NIODeadline? = nil + ) -> Task { + self.execute(request: request, delegate: delegate, deadline: deadline, logger: HTTPClient.loggingDisabled) } /// Execute arbitrary HTTP request and handle response processing using provided delegate. @@ -522,11 +599,13 @@ public class HTTPClient { /// - delegate: Delegate to process response parts. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. - public func execute(request: Request, - delegate: Delegate, - deadline: NIODeadline? = nil, - logger: Logger) -> Task { - return self.execute(request: request, delegate: delegate, eventLoop: .indifferent, deadline: deadline, logger: logger) + public func execute( + request: Request, + delegate: Delegate, + deadline: NIODeadline? = nil, + logger: Logger + ) -> Task { + self.execute(request: request, delegate: delegate, eventLoop: .indifferent, deadline: deadline, logger: logger) } /// Execute arbitrary HTTP request and handle response processing using provided delegate. @@ -534,18 +613,21 @@ public class HTTPClient { /// - parameters: /// - request: HTTP request to execute. /// - delegate: Delegate to process response parts. - /// - eventLoop: NIO Event Loop preference. + /// - eventLoopPreference: NIO Event Loop preference. /// - deadline: Point in time by which the request must complete. - /// - logger: The logger to use for this request. - public func execute(request: Request, - delegate: Delegate, - eventLoop eventLoopPreference: EventLoopPreference, - deadline: NIODeadline? = nil) -> Task { - return self.execute(request: request, - delegate: delegate, - eventLoop: eventLoopPreference, - deadline: deadline, - logger: HTTPClient.loggingDisabled) + public func execute( + request: Request, + delegate: Delegate, + eventLoop eventLoopPreference: EventLoopPreference, + deadline: NIODeadline? = nil + ) -> Task { + self.execute( + request: request, + delegate: delegate, + eventLoop: eventLoopPreference, + deadline: deadline, + logger: HTTPClient.loggingDisabled + ) } /// Execute arbitrary HTTP request and handle response processing using provided delegate. @@ -553,7 +635,7 @@ public class HTTPClient { /// - parameters: /// - request: HTTP request to execute. /// - delegate: Delegate to process response parts. - /// - eventLoop: NIO Event Loop preference. + /// - eventLoopPreference: NIO Event Loop preference. /// - deadline: Point in time by which the request must complete. /// - logger: The logger to use for this request. public func execute( @@ -561,14 +643,14 @@ public class HTTPClient { delegate: Delegate, eventLoop eventLoopPreference: EventLoopPreference, deadline: NIODeadline? = nil, - logger originalLogger: Logger? + logger: Logger? ) -> Task { self._execute( request: request, delegate: delegate, eventLoop: eventLoopPreference, deadline: deadline, - logger: originalLogger, + logger: logger, redirectState: RedirectState( self.configuration.redirectConfiguration.mode, initialURL: request.url.absoluteString @@ -592,25 +674,38 @@ public class HTTPClient { logger originalLogger: Logger?, redirectState: RedirectState? ) -> Task { - let logger = (originalLogger ?? HTTPClient.loggingDisabled).attachingRequestInformation(request, requestID: globalRequestID.wrappingIncrementThenLoad(ordering: .relaxed)) + let logger = (originalLogger ?? HTTPClient.loggingDisabled).attachingRequestInformation( + request, + requestID: globalRequestID.wrappingIncrementThenLoad(ordering: .relaxed) + ) let taskEL: EventLoop switch eventLoopPreference.preference { case .indifferent: // if possible we want a connection on the current `EventLoop` taskEL = self.eventLoopGroup.any() case .delegate(on: let eventLoop): - precondition(self.eventLoopGroup.makeIterator().contains { $0 === eventLoop }, "Provided EventLoop must be part of clients EventLoopGroup.") + precondition( + self.eventLoopGroup.makeIterator().contains { $0 === eventLoop }, + "Provided EventLoop must be part of clients EventLoopGroup." + ) taskEL = eventLoop case .delegateAndChannel(on: let eventLoop): - precondition(self.eventLoopGroup.makeIterator().contains { $0 === eventLoop }, "Provided EventLoop must be part of clients EventLoopGroup.") + precondition( + self.eventLoopGroup.makeIterator().contains { $0 === eventLoop }, + "Provided EventLoop must be part of clients EventLoopGroup." + ) taskEL = eventLoop case .testOnly_exact(_, delegateOn: let delegateEL): taskEL = delegateEL } - logger.trace("selected EventLoop for task given the preference", - metadata: ["ahc-eventloop": "\(taskEL)", - "ahc-el-preference": "\(eventLoopPreference)"]) + logger.trace( + "selected EventLoop for task given the preference", + metadata: [ + "ahc-eventloop": "\(taskEL)", + "ahc-el-preference": "\(eventLoopPreference)", + ] + ) let failedTask: Task? = self.stateLock.withLock { switch self.state { @@ -618,10 +713,12 @@ public class HTTPClient { return nil case .shuttingDown, .shutDown: logger.debug("client is shutting down, failing request") - return Task.failedTask(eventLoop: taskEL, - error: HTTPClientError.alreadyShutdown, - logger: logger, - makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool) + return Task.failedTask( + eventLoop: taskEL, + error: HTTPClientError.alreadyShutdown, + logger: logger, + makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool + ) } } @@ -644,7 +741,11 @@ public class HTTPClient { } }() - let task = Task(eventLoop: taskEL, logger: logger, makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool) + let task = Task( + eventLoop: taskEL, + logger: logger, + makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool + ) do { let requestBag = try RequestBag( request: request, @@ -711,7 +812,12 @@ public class HTTPClient { /// Enables automatic body decompression. Supported algorithms are gzip and deflate. public var decompression: Decompression /// Ignore TLS unclean shutdown error, defaults to `false`. - @available(*, deprecated, message: "AsyncHTTPClient now correctly supports handling unexpected SSL connection drops. This property is ignored") + @available( + *, + deprecated, + message: + "AsyncHTTPClient now correctly supports handling unexpected SSL connection drops. This property is ignored" + ) public var ignoreUncleanSSLShutdown: Bool { get { false } set {} @@ -762,12 +868,14 @@ public class HTTPClient { self.enableMultipath = false } - public init(tlsConfiguration: TLSConfiguration? = nil, - redirectConfiguration: RedirectConfiguration? = nil, - timeout: Timeout = Timeout(), - proxy: Proxy? = nil, - ignoreUncleanSSLShutdown: Bool = false, - decompression: Decompression = .disabled) { + public init( + tlsConfiguration: TLSConfiguration? = nil, + redirectConfiguration: RedirectConfiguration? = nil, + timeout: Timeout = Timeout(), + proxy: Proxy? = nil, + ignoreUncleanSSLShutdown: Bool = false, + decompression: Decompression = .disabled + ) { self.init( tlsConfiguration: tlsConfiguration, redirectConfiguration: redirectConfiguration, @@ -779,49 +887,59 @@ public class HTTPClient { ) } - public init(certificateVerification: CertificateVerification, - redirectConfiguration: RedirectConfiguration? = nil, - timeout: Timeout = Timeout(), - maximumAllowedIdleTimeInConnectionPool: TimeAmount = .seconds(60), - proxy: Proxy? = nil, - ignoreUncleanSSLShutdown: Bool = false, - decompression: Decompression = .disabled) { + public init( + certificateVerification: CertificateVerification, + redirectConfiguration: RedirectConfiguration? = nil, + timeout: Timeout = Timeout(), + maximumAllowedIdleTimeInConnectionPool: TimeAmount = .seconds(60), + proxy: Proxy? = nil, + ignoreUncleanSSLShutdown: Bool = false, + decompression: Decompression = .disabled + ) { var tlsConfig = TLSConfiguration.makeClientConfiguration() tlsConfig.certificateVerification = certificateVerification - self.init(tlsConfiguration: tlsConfig, - redirectConfiguration: redirectConfiguration, - timeout: timeout, - connectionPool: ConnectionPool(idleTimeout: maximumAllowedIdleTimeInConnectionPool), - proxy: proxy, - ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown, - decompression: decompression) + self.init( + tlsConfiguration: tlsConfig, + redirectConfiguration: redirectConfiguration, + timeout: timeout, + connectionPool: ConnectionPool(idleTimeout: maximumAllowedIdleTimeInConnectionPool), + proxy: proxy, + ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown, + decompression: decompression + ) } - public init(certificateVerification: CertificateVerification, - redirectConfiguration: RedirectConfiguration? = nil, - timeout: Timeout = Timeout(), - connectionPool: TimeAmount = .seconds(60), - proxy: Proxy? = nil, - ignoreUncleanSSLShutdown: Bool = false, - decompression: Decompression = .disabled, - backgroundActivityLogger: Logger?) { + public init( + certificateVerification: CertificateVerification, + redirectConfiguration: RedirectConfiguration? = nil, + timeout: Timeout = Timeout(), + connectionPool: TimeAmount = .seconds(60), + proxy: Proxy? = nil, + ignoreUncleanSSLShutdown: Bool = false, + decompression: Decompression = .disabled, + backgroundActivityLogger: Logger? + ) { var tlsConfig = TLSConfiguration.makeClientConfiguration() tlsConfig.certificateVerification = certificateVerification - self.init(tlsConfiguration: tlsConfig, - redirectConfiguration: redirectConfiguration, - timeout: timeout, - connectionPool: ConnectionPool(idleTimeout: connectionPool), - proxy: proxy, - ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown, - decompression: decompression) + self.init( + tlsConfiguration: tlsConfig, + redirectConfiguration: redirectConfiguration, + timeout: timeout, + connectionPool: ConnectionPool(idleTimeout: connectionPool), + proxy: proxy, + ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown, + decompression: decompression + ) } - public init(certificateVerification: CertificateVerification, - redirectConfiguration: RedirectConfiguration? = nil, - timeout: Timeout = Timeout(), - proxy: Proxy? = nil, - ignoreUncleanSSLShutdown: Bool = false, - decompression: Decompression = .disabled) { + public init( + certificateVerification: CertificateVerification, + redirectConfiguration: RedirectConfiguration? = nil, + timeout: Timeout = Timeout(), + proxy: Proxy? = nil, + ignoreUncleanSSLShutdown: Bool = false, + decompression: Decompression = .disabled + ) { self.init( certificateVerification: certificateVerification, redirectConfiguration: redirectConfiguration, @@ -875,7 +993,7 @@ public class HTTPClient { /// `EventLoop` but will not establish a new network connection just to satisfy the `EventLoop` preference if /// another existing connection on a different `EventLoop` is readily available from a connection pool. public static func delegate(on eventLoop: EventLoop) -> EventLoopPreference { - return EventLoopPreference(.delegate(on: eventLoop)) + EventLoopPreference(.delegate(on: eventLoop)) } /// The delegate and the `Channel` will be run on the specified EventLoop. @@ -883,7 +1001,7 @@ public class HTTPClient { /// Use this for use-cases where you prefer a new connection to be established over re-using an existing /// connection that might be on a different `EventLoop`. public static func delegateAndChannel(on eventLoop: EventLoop) -> EventLoopPreference { - return EventLoopPreference(.delegateAndChannel(on: eventLoop)) + EventLoopPreference(.delegateAndChannel(on: eventLoop)) } } @@ -907,7 +1025,7 @@ public class HTTPClient { extension HTTPClient.EventLoopGroupProvider { /// Shares ``HTTPClient/defaultEventLoopGroup`` which is a singleton `EventLoopGroup` suitable for the platform. public static var singleton: Self { - return .shared(HTTPClient.defaultEventLoopGroup) + .shared(HTTPClient.defaultEventLoopGroup) } } @@ -1010,7 +1128,9 @@ extension HTTPClient.Configuration { /// - allowCycles: Whether cycles are allowed. /// /// - warning: Cycle detection will keep all visited URLs in memory which means a malicious server could use this as a denial-of-service vector. - public static func follow(max: Int, allowCycles: Bool) -> RedirectConfiguration { return .init(configuration: .follow(max: max, allowCycles: allowCycles)) } + public static func follow(max: Int, allowCycles: Bool) -> RedirectConfiguration { + .init(configuration: .follow(max: max, allowCycles: allowCycles)) + } } /// Connection pool configuration. @@ -1108,7 +1228,7 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible { } public var description: String { - return "HTTPClientError.\(String(describing: self.code))" + "HTTPClientError.\(String(describing: self.code))" } /// Short description of the error that can be used in case a bounded set of error descriptions is expected, e.g. to @@ -1198,7 +1318,9 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible { /// URL does not contain scheme. public static let emptyScheme = HTTPClientError(code: .emptyScheme) /// Provided URL scheme is not supported, supported schemes are: `http` and `https` - public static func unsupportedScheme(_ scheme: String) -> HTTPClientError { return HTTPClientError(code: .unsupportedScheme(scheme)) } + public static func unsupportedScheme(_ scheme: String) -> HTTPClientError { + HTTPClientError(code: .unsupportedScheme(scheme)) + } /// Request timed out while waiting for response. public static let readTimeout = HTTPClientError(code: .readTimeout) /// Request timed out. @@ -1227,9 +1349,13 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible { /// A body was sent in a request with method TRACE. public static let traceRequestWithBody = HTTPClientError(code: .traceRequestWithBody) /// Header field names contain invalid characters. - public static func invalidHeaderFieldNames(_ names: [String]) -> HTTPClientError { return HTTPClientError(code: .invalidHeaderFieldNames(names)) } + public static func invalidHeaderFieldNames(_ names: [String]) -> HTTPClientError { + HTTPClientError(code: .invalidHeaderFieldNames(names)) + } /// Header field values contain invalid characters. - public static func invalidHeaderFieldValues(_ values: [String]) -> HTTPClientError { return HTTPClientError(code: .invalidHeaderFieldValues(values)) } + public static func invalidHeaderFieldValues(_ values: [String]) -> HTTPClientError { + HTTPClientError(code: .invalidHeaderFieldValues(values)) + } /// Body length is not equal to `Content-Length`. public static let bodyLengthMismatch = HTTPClientError(code: .bodyLengthMismatch) /// Body part was written after request was fully sent. @@ -1247,12 +1373,12 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible { public static let tlsHandshakeTimeout = HTTPClientError(code: .tlsHandshakeTimeout) /// The remote server only offered an unsupported application protocol public static func serverOfferedUnsupportedApplicationProtocol(_ proto: String) -> HTTPClientError { - return HTTPClientError(code: .serverOfferedUnsupportedApplicationProtocol(proto)) + HTTPClientError(code: .serverOfferedUnsupportedApplicationProtocol(proto)) } /// The globally shared singleton ``HTTPClient`` cannot be shut down. public static var shutdownUnsupported: HTTPClientError { - return HTTPClientError(code: .shutdownUnsupported) + HTTPClientError(code: .shutdownUnsupported) } /// The request deadline was exceeded. The request was cancelled because of this. @@ -1269,6 +1395,11 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible { /// - Tasks are not processed fast enough on the existing connections, to process all waiters in time public static let getConnectionFromPoolTimeout = HTTPClientError(code: .getConnectionFromPoolTimeout) - @available(*, deprecated, message: "AsyncHTTPClient now correctly supports informational headers. For this reason `httpEndReceivedAfterHeadWith1xx` will not be thrown anymore.") + @available( + *, + deprecated, + message: + "AsyncHTTPClient now correctly supports informational headers. For this reason `httpEndReceivedAfterHeadWith1xx` will not be thrown anymore." + ) public static let httpEndReceivedAfterHeadWith1xx = HTTPClientError(code: .httpEndReceivedAfterHeadWith1xx) } diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index d989b8a6c..7db1ce33c 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -43,17 +43,18 @@ extension HTTPClient { /// - parameters: /// - data: `IOData` to write. public func write(_ data: IOData) -> EventLoopFuture { - return self.closure(data) + self.closure(data) } @inlinable - func writeChunks(of bytes: Bytes, maxChunkSize: Int) -> EventLoopFuture where Bytes.Element == UInt8 { + func writeChunks(of bytes: Bytes, maxChunkSize: Int) -> EventLoopFuture + where Bytes.Element == UInt8 { let iterator = UnsafeMutableTransferBox(bytes.chunks(ofCount: maxChunkSize).makeIterator()) guard let chunk = iterator.wrappedValue.next() else { return self.write(IOData.byteBuffer(.init())) } - @Sendable // can't use closure here as we recursively call ourselves which closures can't do + @Sendable // can't use closure here as we recursively call ourselves which closures can't do func writeNextChunk(_ chunk: Bytes.SubSequence) -> EventLoopFuture { if let nextChunk = iterator.wrappedValue.next() { return self.write(.byteBuffer(ByteBuffer(bytes: chunk))).flatMap { @@ -100,7 +101,7 @@ extension HTTPClient { /// - parameters: /// - buffer: Body `ByteBuffer` representation. public static func byteBuffer(_ buffer: ByteBuffer) -> Body { - return Body(contentLength: Int64(buffer.readableBytes)) { writer in + Body(contentLength: Int64(buffer.readableBytes)) { writer in writer.write(.byteBuffer(buffer)) } } @@ -113,8 +114,11 @@ extension HTTPClient { /// - stream: Body chunk provider. @_disfavoredOverload @preconcurrency - public static func stream(length: Int? = nil, _ stream: @Sendable @escaping (StreamWriter) -> EventLoopFuture) -> Body { - return Body(contentLength: length.flatMap { Int64($0) }, stream: stream) + public static func stream( + length: Int? = nil, + _ stream: @Sendable @escaping (StreamWriter) -> EventLoopFuture + ) -> Body { + Body(contentLength: length.flatMap { Int64($0) }, stream: stream) } /// Create and stream body using ``StreamWriter``. @@ -122,19 +126,23 @@ extension HTTPClient { /// - parameters: /// - contentLength: Body size. If nil, `Transfer-Encoding` will automatically be set to `chunked`. Otherwise a `Content-Length` /// header is set with the given `contentLength`. - /// - bodyStream: Body chunk provider. - public static func stream(contentLength: Int64? = nil, _ stream: @Sendable @escaping (StreamWriter) -> EventLoopFuture) -> Body { - return Body(contentLength: contentLength, stream: stream) + /// - stream: Body chunk provider. + public static func stream( + contentLength: Int64? = nil, + _ stream: @Sendable @escaping (StreamWriter) -> EventLoopFuture + ) -> Body { + Body(contentLength: contentLength, stream: stream) } /// Create and stream body using a collection of bytes. /// /// - parameters: - /// - data: Body binary representation. + /// - bytes: Body binary representation. @preconcurrency @inlinable - public static func bytes(_ bytes: Bytes) -> Body where Bytes: RandomAccessCollection, Bytes: Sendable, Bytes.Element == UInt8 { - return Body(contentLength: Int64(bytes.count)) { writer in + public static func bytes(_ bytes: Bytes) -> Body + where Bytes: RandomAccessCollection, Bytes: Sendable, Bytes.Element == UInt8 { + Body(contentLength: Int64(bytes.count)) { writer in if bytes.count <= bagOfBytesToByteBufferConversionChunkSize { return writer.write(.byteBuffer(ByteBuffer(bytes: bytes))) } else { @@ -148,7 +156,7 @@ extension HTTPClient { /// - parameters: /// - string: Body `String` representation. public static func string(_ string: String) -> Body { - return Body(contentLength: Int64(string.utf8.count)) { writer in + Body(contentLength: Int64(string.utf8.count)) { writer in if string.utf8.count <= bagOfBytesToByteBufferConversionChunkSize { return writer.write(.byteBuffer(ByteBuffer(string: string))) } else { @@ -184,7 +192,6 @@ extension HTTPClient { /// /// - parameters: /// - url: Remote `URL`. - /// - version: HTTP version. /// - method: HTTP method. /// - headers: Custom HTTP headers. /// - body: Request body. @@ -193,7 +200,12 @@ extension HTTPClient { /// - `emptyScheme` if URL does not contain HTTP scheme. /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. /// - `emptyHost` if URL does not contains a host. - public init(url: String, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { + public init( + url: String, + method: HTTPMethod = .GET, + headers: HTTPHeaders = HTTPHeaders(), + body: Body? = nil + ) throws { try self.init(url: url, method: method, headers: headers, body: body, tlsConfiguration: nil) } @@ -201,7 +213,6 @@ extension HTTPClient { /// /// - parameters: /// - url: Remote `URL`. - /// - version: HTTP version. /// - method: HTTP method. /// - headers: Custom HTTP headers. /// - body: Request body. @@ -211,7 +222,13 @@ extension HTTPClient { /// - `emptyScheme` if URL does not contain HTTP scheme. /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. /// - `emptyHost` if URL does not contains a host. - public init(url: String, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration?) throws { + public init( + url: String, + method: HTTPMethod = .GET, + headers: HTTPHeaders = HTTPHeaders(), + body: Body? = nil, + tlsConfiguration: TLSConfiguration? + ) throws { guard let url = URL(string: url) else { throw HTTPClientError.invalidURL } @@ -231,7 +248,8 @@ extension HTTPClient { /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. /// - `emptyHost` if URL does not contains a host. /// - `missingSocketPath` if URL does not contains a socketPath as an encoded host. - public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { + public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws + { try self.init(url: url, method: method, headers: headers, body: body, tlsConfiguration: nil) } @@ -248,7 +266,13 @@ extension HTTPClient { /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. /// - `emptyHost` if URL does not contains a host. /// - `missingSocketPath` if URL does not contains a socketPath as an encoded host. - public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration?) throws { + public init( + url: URL, + method: HTTPMethod = .GET, + headers: HTTPHeaders = HTTPHeaders(), + body: Body? = nil, + tlsConfiguration: TLSConfiguration? + ) throws { self.deconstructedURL = try DeconstructedURL(url: url) self.url = url @@ -281,7 +305,10 @@ extension HTTPClient { head.headers.addHostIfNeeded(for: self.deconstructedURL) - let metadata = try head.headers.validateAndSetTransportFraming(method: self.method, bodyLength: .init(self.body)) + let metadata = try head.headers.validateAndSetTransportFraming( + method: self.method, + bodyLength: .init(self.body) + ) return (head, metadata) } @@ -333,7 +360,13 @@ extension HTTPClient { /// - version: Response HTTP version. /// - headers: Reponse HTTP headers. /// - body: Response body. - public init(host: String, status: HTTPResponseStatus, version: HTTPVersion, headers: HTTPHeaders, body: ByteBuffer?) { + public init( + host: String, + status: HTTPResponseStatus, + version: HTTPVersion, + headers: HTTPHeaders, + body: ByteBuffer? + ) { self.host = host self.status = status self.version = version @@ -357,19 +390,19 @@ extension HTTPClient { /// HTTP basic auth. public static func basic(username: String, password: String) -> HTTPClient.Authorization { - return .basic(credentials: Base64.encode(bytes: "\(username):\(password)".utf8)) + .basic(credentials: Base64.encode(bytes: "\(username):\(password)".utf8)) } /// HTTP basic auth. /// /// This version uses the raw string directly. public static func basic(credentials: String) -> HTTPClient.Authorization { - return .init(scheme: .Basic(credentials)) + .init(scheme: .Basic(credentials)) } /// HTTP bearer auth public static func bearer(tokens: String) -> HTTPClient.Authorization { - return .init(scheme: .Bearer(tokens)) + .init(scheme: .Bearer(tokens)) } /// The header string for this auth field. @@ -406,7 +439,7 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate { } public var description: String { - return "ResponseTooBigError: received response body exceeds maximum accepted size of \(self.maxBodySize) bytes" + "ResponseTooBigError: received response body exceeds maximum accepted size of \(self.maxBodySize) bytes" } } @@ -450,9 +483,10 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate { switch self.state { case .idle: if self.requestMethod != .HEAD, - let contentLength = head.headers.first(name: "Content-Length"), - let announcedBodySize = Int(contentLength), - announcedBodySize > self.maxBodySize { + let contentLength = head.headers.first(name: "Content-Length"), + let announcedBodySize = Int(contentLength), + announcedBodySize > self.maxBodySize + { let error = ResponseTooBigError(maxBodySize: maxBodySize) self.state = .error(error) return task.eventLoop.makeFailedFuture(error) @@ -515,9 +549,21 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate { case .idle: preconditionFailure("no head received before end") case .head(let head): - return Response(host: self.requestHost, status: head.status, version: head.version, headers: head.headers, body: nil) + return Response( + host: self.requestHost, + status: head.status, + version: head.version, + headers: head.headers, + body: nil + ) case .body(let head, let body): - return Response(host: self.requestHost, status: head.status, version: head.version, headers: head.headers, body: body) + return Response( + host: self.requestHost, + status: head.status, + version: head.version, + headers: head.headers, + body: body + ) case .end: preconditionFailure("request already processed") case .error(let error): @@ -650,14 +696,14 @@ extension HTTPClientResponseDelegate { /// /// By default, this does nothing. public func didReceiveHead(task: HTTPClient.Task, _: HTTPResponseHead) -> EventLoopFuture { - return task.eventLoop.makeSucceededVoidFuture() + task.eventLoop.makeSucceededVoidFuture() } /// Default implementation of ``HTTPClientResponseDelegate/didReceiveBodyPart(task:_:)-4fd4v``. /// /// By default, this does nothing. public func didReceiveBodyPart(task: HTTPClient.Task, _: ByteBuffer) -> EventLoopFuture { - return task.eventLoop.makeSucceededVoidFuture() + task.eventLoop.makeSucceededVoidFuture() } /// Default implementation of ``HTTPClientResponseDelegate/didReceiveError(task:_:)-fhsg``. @@ -685,7 +731,7 @@ extension URL { } func hasTheSameOrigin(as other: URL) -> Bool { - return self.host == other.host && self.scheme == other.scheme && self.port == other.port + self.host == other.host && self.scheme == other.scheme && self.port == other.port } /// Initializes a newly created HTTP URL connecting to a unix domain socket path. The socket path is encoded as the URL's host, replacing percent encoding invalid path characters, and will use the "http+unix" scheme. @@ -732,7 +778,7 @@ extension HTTPClient { /// The `EventLoop` the delegate will be executed on. public let eventLoop: EventLoop /// The `Logger` used by the `Task` for logging. - public let logger: Logger // We are okay to store the logger here because a Task is for only one request. + public let logger: Logger // We are okay to store the logger here because a Task is for only one request. let promise: EventLoopPromise @@ -772,14 +818,18 @@ extension HTTPClient { logger: Logger, makeOrGetFileIOThreadPool: @escaping () -> NIOThreadPool ) -> Task { - let task = self.init(eventLoop: eventLoop, logger: logger, makeOrGetFileIOThreadPool: makeOrGetFileIOThreadPool) + let task = self.init( + eventLoop: eventLoop, + logger: logger, + makeOrGetFileIOThreadPool: makeOrGetFileIOThreadPool + ) task.promise.fail(error) return task } /// `EventLoopFuture` for the response returned by this request. public var futureResult: EventLoopFuture { - return self.promise.futureResult + self.promise.futureResult } /// Waits for execution of this request to complete. @@ -788,7 +838,7 @@ extension HTTPClient { /// - throws: The error value of ``futureResult`` if it errors. @available(*, noasync, message: "wait() can block indefinitely, prefer get()", renamed: "get()") public func wait() throws -> Response { - return try self.promise.futureResult.wait() + try self.promise.futureResult.wait() } /// Provides the result of this request. @@ -797,7 +847,7 @@ extension HTTPClient { /// - throws: The error value of ``futureResult`` if it errors. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func get() async throws -> Response { - return try await self.promise.futureResult.get() + try await self.promise.futureResult.get() } /// Cancels the request execution. @@ -806,7 +856,7 @@ extension HTTPClient { } /// Cancels the request execution with a custom `Error`. - /// - Parameter reason: the error that is used to fail the promise + /// - Parameter error: the error that is used to fail the promise public func fail(reason error: Error) { let taskDelegate = self.lock.withLock { () -> HTTPClientTaskDelegate? in self._isCancelled = true @@ -816,15 +866,19 @@ extension HTTPClient { taskDelegate?.fail(error) } - func succeed(promise: EventLoopPromise?, - with value: Response, - delegateType: Delegate.Type, - closing: Bool) { + func succeed( + promise: EventLoopPromise?, + with value: Response, + delegateType: Delegate.Type, + closing: Bool + ) { promise?.succeed(value) } - func fail(with error: Error, - delegateType: Delegate.Type) { + func fail( + with error: Error, + delegateType: Delegate.Type + ) { self.promise.fail(error) } } diff --git a/Sources/AsyncHTTPClient/LRUCache.swift b/Sources/AsyncHTTPClient/LRUCache.swift index 0a01da0d2..f8b58c36a 100644 --- a/Sources/AsyncHTTPClient/LRUCache.swift +++ b/Sources/AsyncHTTPClient/LRUCache.swift @@ -52,9 +52,11 @@ struct LRUCache { @discardableResult mutating func append(key: Key, value: Value) -> Value { - let newElement = Element(generation: self.generation, - key: key, - value: value) + let newElement = Element( + generation: self.generation, + key: key, + value: value + ) if let found = self.bumpGenerationAndFindIndex(key: key) { self.elements[found] = newElement return value diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift index 9796bc2af..148b4a4c4 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -#if canImport(Network) -import Network -#endif import NIOCore import NIOHTTP1 import NIOTransportServices +#if canImport(Network) +import Network +#endif + extension HTTPClient { #if canImport(Network) /// A wrapper for `POSIX` errors thrown by `Network.framework`. @@ -38,7 +39,7 @@ extension HTTPClient { self.reason = reason } - public var description: String { return self.reason } + public var description: String { self.reason } } /// A wrapper for TLS errors thrown by `Network.framework`. @@ -58,7 +59,7 @@ extension HTTPClient { self.reason = reason } - public var description: String { return self.reason } + public var description: String { self.reason } } #endif diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/NWWaitingHandler.swift b/Sources/AsyncHTTPClient/NIOTransportServices/NWWaitingHandler.swift index 3474a8821..d7c6055ec 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/NWWaitingHandler.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/NWWaitingHandler.swift @@ -33,7 +33,10 @@ final class NWWaitingHandler: ChannelInbound func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { if let waitingEvent = event as? NIOTSNetworkEvents.WaitingForConnectivity { - self.requester.waitingForConnectivity(self.connectionID, error: HTTPClient.NWErrorHandler.translateError(waitingEvent.transientError)) + self.requester.waitingForConnectivity( + self.connectionID, + error: HTTPClient.NWErrorHandler.translateError(waitingEvent.transientError) + ) } context.fireUserInboundEventTriggered(event) } diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift index cb6bd43bd..ef505e3b7 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift @@ -66,7 +66,10 @@ extension TLSConfiguration { /// /// - Parameter eventLoop: EventLoop to wait for creation of options on /// - Returns: Future holding NWProtocolTLS Options - func getNWProtocolTLSOptions(on eventLoop: EventLoop, serverNameIndicatorOverride: String?) -> EventLoopFuture { + func getNWProtocolTLSOptions( + on eventLoop: EventLoop, + serverNameIndicatorOverride: String? + ) -> EventLoopFuture { let promise = eventLoop.makePromise(of: NWProtocolTLS.Options.self) Self.tlsDispatchQueue.async { do { @@ -86,11 +89,11 @@ extension TLSConfiguration { let options = NWProtocolTLS.Options() let useMTELGExplainer = """ - You can still use this configuration option on macOS if you initialize HTTPClient \ - with a MultiThreadedEventLoopGroup. Please note that using MultiThreadedEventLoopGroup \ - will make AsyncHTTPClient use NIO on BSD Sockets and not Network.framework (which is the preferred \ - platform networking stack). - """ + You can still use this configuration option on macOS if you initialize HTTPClient \ + with a MultiThreadedEventLoopGroup. Please note that using MultiThreadedEventLoopGroup \ + will make AsyncHTTPClient use NIO on BSD Sockets and not Network.framework (which is the preferred \ + platform networking stack). + """ if let serverNameIndicatorOverride = serverNameIndicatorOverride { serverNameIndicatorOverride.withCString { serverNameIndicatorOverride in @@ -100,15 +103,24 @@ extension TLSConfiguration { // minimum TLS protocol if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { - sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, self.minimumTLSVersion.nwTLSProtocolVersion) + sec_protocol_options_set_min_tls_protocol_version( + options.securityProtocolOptions, + self.minimumTLSVersion.nwTLSProtocolVersion + ) } else { - sec_protocol_options_set_tls_min_version(options.securityProtocolOptions, self.minimumTLSVersion.sslProtocol) + sec_protocol_options_set_tls_min_version( + options.securityProtocolOptions, + self.minimumTLSVersion.sslProtocol + ) } // maximum TLS protocol if let maximumTLSVersion = self.maximumTLSVersion { if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { - sec_protocol_options_set_max_tls_protocol_version(options.securityProtocolOptions, maximumTLSVersion.nwTLSProtocolVersion) + sec_protocol_options_set_max_tls_protocol_version( + options.securityProtocolOptions, + maximumTLSVersion.nwTLSProtocolVersion + ) } else { sec_protocol_options_set_tls_max_version(options.securityProtocolOptions, maximumTLSVersion.sslProtocol) } @@ -161,8 +173,10 @@ extension TLSConfiguration { break } - precondition(self.certificateVerification != .noHostnameVerification, - "TLSConfiguration.certificateVerification = .noHostnameVerification is not supported. \(useMTELGExplainer)") + precondition( + self.certificateVerification != .noHostnameVerification, + "TLSConfiguration.certificateVerification = .noHostnameVerification is not supported. \(useMTELGExplainer)" + ) if certificateVerification != .fullVerification || trustRoots != nil { // add verify block to control certificate verification @@ -196,7 +210,8 @@ extension TLSConfiguration { } } } - }, Self.tlsDispatchQueue + }, + Self.tlsDispatchQueue ) } return options diff --git a/Sources/AsyncHTTPClient/RedirectState.swift b/Sources/AsyncHTTPClient/RedirectState.swift index c4e427ef1..95de2d508 100644 --- a/Sources/AsyncHTTPClient/RedirectState.swift +++ b/Sources/AsyncHTTPClient/RedirectState.swift @@ -12,9 +12,10 @@ // //===----------------------------------------------------------------------===// -import struct Foundation.URL import NIOHTTP1 +import struct Foundation.URL + typealias RedirectMode = HTTPClient.Configuration.RedirectConfiguration.Mode struct RedirectState { diff --git a/Sources/AsyncHTTPClient/RequestBag+StateMachine.swift b/Sources/AsyncHTTPClient/RequestBag+StateMachine.swift index e7fad6850..37b2a42f0 100644 --- a/Sources/AsyncHTTPClient/RequestBag+StateMachine.swift +++ b/Sources/AsyncHTTPClient/RequestBag+StateMachine.swift @@ -12,10 +12,11 @@ // //===----------------------------------------------------------------------===// -import struct Foundation.URL import NIOCore import NIOHTTP1 +import struct Foundation.URL + extension HTTPClient { /// The maximum body size allowed, before a redirect response is cancelled. 3KB. /// @@ -302,10 +303,12 @@ extension RequestBag.StateMachine { preconditionFailure("If we receive a response, we must not have received something else before") } - if let redirectHandler = redirectHandler, let redirectURL = redirectHandler.redirectTarget( - status: head.status, - responseHeaders: head.headers - ) { + if let redirectHandler = redirectHandler, + let redirectURL = redirectHandler.redirectTarget( + status: head.status, + responseHeaders: head.headers + ) + { // If we will redirect, we need to consume the response's body ASAP, to be able to // reuse the existing connection. We will consume a response body, if the body is // smaller than 3kb. @@ -348,7 +351,9 @@ extension RequestBag.StateMachine { case .executing(let executor, let requestState, .buffering(var currentBuffer, next: let next)): guard case .askExecutorForMore = next else { - preconditionFailure("If we have received an error or eof before, why did we get another body part? Next: \(next)") + preconditionFailure( + "If we have received an error or eof before, why did we get another body part? Next: \(next)" + ) } self.state = .modifying @@ -405,7 +410,9 @@ extension RequestBag.StateMachine { case .executing(let executor, let requestState, .buffering(var buffer, next: let next)): guard case .askExecutorForMore = next else { - preconditionFailure("If we have received an error or eof before, why did we get another body part? Next: \(next)") + preconditionFailure( + "If we have received an error or eof before, why did we get another body part? Next: \(next)" + ) } if buffer.isEmpty, let newChunks = newChunks, !newChunks.isEmpty { @@ -463,7 +470,9 @@ extension RequestBag.StateMachine { case .initialized, .queued, .deadlineExceededWhileQueued: preconditionFailure("Invalid state: \(self.state)") case .executing(_, _, .initialized): - preconditionFailure("Invalid state: Must have received response head, before this method is called for the first time") + preconditionFailure( + "Invalid state: Must have received response head, before this method is called for the first time" + ) case .executing(_, _, .buffering(_, next: .error(let connectionError))): // if an error was received from the connection, we fail the task with the one @@ -476,17 +485,23 @@ extension RequestBag.StateMachine { return .failTask(error, executorToCancel: executor) case .executing(_, _, .waitingForRemote): - preconditionFailure("Invalid state... We just returned from a consumption function. We can't already be waiting") + preconditionFailure( + "Invalid state... We just returned from a consumption function. We can't already be waiting" + ) case .redirected: - preconditionFailure("Invalid state... Redirect don't call out to delegate functions. Thus we should never land here.") + preconditionFailure( + "Invalid state... Redirect don't call out to delegate functions. Thus we should never land here." + ) case .finished(error: .some): // don't overwrite existing errors return .doNothing case .finished(error: .none): - preconditionFailure("Invalid state... If no error occured, this must not be called, after the request was finished") + preconditionFailure( + "Invalid state... If no error occured, this must not be called, after the request was finished" + ) case .modifying: preconditionFailure() @@ -499,7 +514,9 @@ extension RequestBag.StateMachine { preconditionFailure("Invalid state: \(self.state)") case .executing(_, _, .initialized): - preconditionFailure("Invalid state: Must have received response head, before this method is called for the first time") + preconditionFailure( + "Invalid state: Must have received response head, before this method is called for the first time" + ) case .executing(let executor, let requestState, .buffering(var buffer, next: .askExecutorForMore)): self.state = .modifying @@ -529,7 +546,9 @@ extension RequestBag.StateMachine { return .failTask(error, executorToCancel: nil) case .executing(_, _, .waitingForRemote): - preconditionFailure("Invalid state... We just returned from a consumption function. We can't already be waiting") + preconditionFailure( + "Invalid state... We just returned from a consumption function. We can't already be waiting" + ) case .redirected: return .doNothing @@ -538,7 +557,9 @@ extension RequestBag.StateMachine { return .doNothing case .finished(error: .none): - preconditionFailure("Invalid state... If no error occurred, this must not be called, after the request was finished") + preconditionFailure( + "Invalid state... If no error occurred, this must not be called, after the request was finished" + ) case .modifying: preconditionFailure() @@ -559,11 +580,11 @@ extension RequestBag.StateMachine { return .cancelScheduler(queuer) case .initialized, - .deadlineExceededWhileQueued, - .executing, - .finished, - .redirected, - .modifying: + .deadlineExceededWhileQueued, + .executing, + .finished, + .redirected, + .modifying: /// if we are not in the queued state, we can fail early by just calling down to `self.fail(_:)` /// which does the appropriate state transition for us. return .fail(self.fail(HTTPClientError.deadlineExceeded)) diff --git a/Sources/AsyncHTTPClient/RequestBag.swift b/Sources/AsyncHTTPClient/RequestBag.swift index c5472fc6f..f2720d9ef 100644 --- a/Sources/AsyncHTTPClient/RequestBag.swift +++ b/Sources/AsyncHTTPClient/RequestBag.swift @@ -58,13 +58,15 @@ final class RequestBag { let eventLoopPreference: HTTPClient.EventLoopPreference - init(request: HTTPClient.Request, - eventLoopPreference: HTTPClient.EventLoopPreference, - task: HTTPClient.Task, - redirectHandler: RedirectHandler?, - connectionDeadline: NIODeadline, - requestOptions: RequestOptions, - delegate: Delegate) throws { + init( + request: HTTPClient.Request, + eventLoopPreference: HTTPClient.EventLoopPreference, + task: HTTPClient.Task, + redirectHandler: RedirectHandler?, + connectionDeadline: NIODeadline, + requestOptions: RequestOptions, + delegate: Delegate + ) throws { self.poolKey = .init(request, dnsOverride: requestOptions.dnsOverride) self.eventLoopPreference = eventLoopPreference self.task = task @@ -435,8 +437,8 @@ extension RequestBag: HTTPExecutableRequest { case .indifferent: return self.task.eventLoop case .delegate(let eventLoop), - .delegateAndChannel(on: let eventLoop), - .testOnly_exact(channelOn: let eventLoop, delegateOn: _): + .delegateAndChannel(on: let eventLoop), + .testOnly_exact(channelOn: let eventLoop, delegateOn: _): return eventLoop } } diff --git a/Sources/AsyncHTTPClient/RequestValidation.swift b/Sources/AsyncHTTPClient/RequestValidation.swift index 87224a3b2..f338e06a9 100644 --- a/Sources/AsyncHTTPClient/RequestValidation.swift +++ b/Sources/AsyncHTTPClient/RequestValidation.swift @@ -50,23 +50,23 @@ extension HTTPHeaders { let satisfy = name.utf8.allSatisfy { char -> Bool in switch char { case UInt8(ascii: "a")...UInt8(ascii: "z"), - UInt8(ascii: "A")...UInt8(ascii: "Z"), - UInt8(ascii: "0")...UInt8(ascii: "9"), - UInt8(ascii: "!"), - UInt8(ascii: "#"), - UInt8(ascii: "$"), - UInt8(ascii: "%"), - UInt8(ascii: "&"), - UInt8(ascii: "'"), - UInt8(ascii: "*"), - UInt8(ascii: "+"), - UInt8(ascii: "-"), - UInt8(ascii: "."), - UInt8(ascii: "^"), - UInt8(ascii: "_"), - UInt8(ascii: "`"), - UInt8(ascii: "|"), - UInt8(ascii: "~"): + UInt8(ascii: "A")...UInt8(ascii: "Z"), + UInt8(ascii: "0")...UInt8(ascii: "9"), + UInt8(ascii: "!"), + UInt8(ascii: "#"), + UInt8(ascii: "$"), + UInt8(ascii: "%"), + UInt8(ascii: "&"), + UInt8(ascii: "'"), + UInt8(ascii: "*"), + UInt8(ascii: "+"), + UInt8(ascii: "-"), + UInt8(ascii: "."), + UInt8(ascii: "^"), + UInt8(ascii: "_"), + UInt8(ascii: "`"), + UInt8(ascii: "|"), + UInt8(ascii: "~"): return true default: return false @@ -166,13 +166,14 @@ extension HTTPHeaders { mutating func addHostIfNeeded(for url: DeconstructedURL) { // if no host header was set, let's use the url host guard !self.contains(name: "host"), - var host = url.connectionTarget.host + var host = url.connectionTarget.host else { return } // if the request uses a non-default port, we need to add it after the host if let port = url.connectionTarget.port, - port != url.scheme.defaultPort { + port != url.scheme.defaultPort + { host += ":\(port)" } self.add(name: "host", value: host) diff --git a/Sources/AsyncHTTPClient/SSLContextCache.swift b/Sources/AsyncHTTPClient/SSLContextCache.swift index 660a04942..599003e56 100644 --- a/Sources/AsyncHTTPClient/SSLContextCache.swift +++ b/Sources/AsyncHTTPClient/SSLContextCache.swift @@ -25,30 +25,38 @@ final class SSLContextCache { } extension SSLContextCache { - func sslContext(tlsConfiguration: TLSConfiguration, - eventLoop: EventLoop, - logger: Logger) -> EventLoopFuture { + func sslContext( + tlsConfiguration: TLSConfiguration, + eventLoop: EventLoop, + logger: Logger + ) -> EventLoopFuture { let eqTLSConfiguration = BestEffortHashableTLSConfiguration(wrapping: tlsConfiguration) let sslContext = self.lock.withLock { self.sslContextCache.find(key: eqTLSConfiguration) } if let sslContext = sslContext { - logger.trace("found SSL context in cache", - metadata: ["ahc-tls-config": "\(tlsConfiguration)"]) + logger.trace( + "found SSL context in cache", + metadata: ["ahc-tls-config": "\(tlsConfiguration)"] + ) return eventLoop.makeSucceededFuture(sslContext) } - logger.trace("creating new SSL context", - metadata: ["ahc-tls-config": "\(tlsConfiguration)"]) + logger.trace( + "creating new SSL context", + metadata: ["ahc-tls-config": "\(tlsConfiguration)"] + ) let newSSLContext = self.offloadQueue.asyncWithFuture(eventLoop: eventLoop) { try NIOSSLContext(configuration: tlsConfiguration) } newSSLContext.whenSuccess { (newSSLContext: NIOSSLContext) -> Void in self.lock.withLock { () -> Void in - self.sslContextCache.append(key: eqTLSConfiguration, - value: newSSLContext) + self.sslContextCache.append( + key: eqTLSConfiguration, + value: newSSLContext + ) } } diff --git a/Sources/AsyncHTTPClient/Singleton.swift b/Sources/AsyncHTTPClient/Singleton.swift index 149f7586f..0ddf1bc40 100644 --- a/Sources/AsyncHTTPClient/Singleton.swift +++ b/Sources/AsyncHTTPClient/Singleton.swift @@ -20,7 +20,7 @@ extension HTTPClient { /// - `EventLoopGroup` is ``HTTPClient/defaultEventLoopGroup`` (matching the platform default) /// - logging is disabled public static var shared: HTTPClient { - return globallySharedHTTPClient + globallySharedHTTPClient } } diff --git a/Sources/AsyncHTTPClient/StringConvertibleInstances.swift b/Sources/AsyncHTTPClient/StringConvertibleInstances.swift index f75fb0d87..61d4b067a 100644 --- a/Sources/AsyncHTTPClient/StringConvertibleInstances.swift +++ b/Sources/AsyncHTTPClient/StringConvertibleInstances.swift @@ -14,6 +14,6 @@ extension HTTPClient.EventLoopPreference: CustomStringConvertible { public var description: String { - return "\(self.preference)" + "\(self.preference)" } } diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index f8618ea17..abdd5bbc2 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -29,11 +29,11 @@ public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate { } public func didReceiveBodyPart(task: HTTPClient.Task, _ buffer: ByteBuffer) -> EventLoopFuture { - return self.chunkHandler(buffer) + self.chunkHandler(buffer) } public func didFinishRequest(task: HTTPClient.Task) throws { - return () + () } } @@ -44,7 +44,12 @@ public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate { /// https://forums.swift.org/t/support-debug-only-code/11037 for a discussion. @inlinable internal func debugOnly(_ body: () -> Void) { - assert({ body(); return true }()) + assert( + { + body() + return true + }() + ) } extension BidirectionalCollection where Element: Equatable { @@ -61,8 +66,8 @@ extension BidirectionalCollection where Element: Equatable { guard self[ourIdx] == suffix[suffixIdx] else { return false } } guard suffixIdx == suffix.startIndex else { - return false // Exhausted self, but 'suffix' has elements remaining. + return false // Exhausted self, but 'suffix' has elements remaining. } - return true // Exhausted 'other' without finding a mismatch. + return true // Exhausted 'other' without finding a mismatch. } } diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index d44d047f6..f58e07730 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOPosix import NIOSSL import XCTest +@testable import AsyncHTTPClient + private func makeDefaultHTTPClient( eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .singleton ) -> HTTPClient { @@ -65,9 +66,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) let request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } @@ -85,9 +88,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) let request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get") - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } @@ -107,13 +112,17 @@ final class AsyncAwaitEndToEndTests: XCTestCase { request.method = .POST request.body = .bytes(ByteBuffer(string: "1234")) - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response.headers["content-length"], ["4"]) - guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(upTo: 1024) - ) else { return } + guard + let body = await XCTAssertNoThrowWithResult( + try await response.body.collect(upTo: 1024) + ) + else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } } @@ -129,13 +138,17 @@ final class AsyncAwaitEndToEndTests: XCTestCase { request.method = .POST request.body = .bytes(AnySendableSequence("1234".utf8), length: .unknown) - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response.headers["content-length"], []) - guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(upTo: 1024) - ) else { return } + guard + let body = await XCTAssertNoThrowWithResult( + try await response.body.collect(upTo: 1024) + ) + else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } } @@ -151,13 +164,17 @@ final class AsyncAwaitEndToEndTests: XCTestCase { request.method = .POST request.body = .bytes(AnySendableCollection("1234".utf8), length: .unknown) - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response.headers["content-length"], []) - guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(upTo: 1024) - ) else { return } + guard + let body = await XCTAssertNoThrowWithResult( + try await response.body.collect(upTo: 1024) + ) + else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } } @@ -173,13 +190,17 @@ final class AsyncAwaitEndToEndTests: XCTestCase { request.method = .POST request.body = .bytes(ByteBuffer(string: "1234").readableBytesView) - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response.headers["content-length"], ["4"]) - guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(upTo: 1024) - ) else { return } + guard + let body = await XCTAssertNoThrowWithResult( + try await response.body.collect(upTo: 1024) + ) + else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } } @@ -206,7 +227,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { } func makeAsyncIterator() -> AsyncSequenceByteBufferGenerator { - return self + self } } @@ -225,19 +246,23 @@ final class AsyncAwaitEndToEndTests: XCTestCase { request.method = .POST let sequence = AsyncSequenceByteBufferGenerator( - chunkSize: 4_194_304, // 4MB chunk - totalChunks: 768 // Total = 3GB + chunkSize: 4_194_304, // 4MB chunk + totalChunks: 768 // Total = 3GB ) request.body = .stream(sequence, length: .unknown) - let response: HTTPClientResponse = try await client.execute(request, deadline: .now() + .seconds(30), logger: logger) + let response: HTTPClientResponse = try await client.execute( + request, + deadline: .now() + .seconds(30), + logger: logger + ) XCTAssertEqual(response.headers["content-length"], []) var receivedBytes: Int64 = 0 for try await part in response.body { receivedBytes += Int64(part.readableBytes) } - XCTAssertEqual(receivedBytes, 3_221_225_472) // 3GB + XCTAssertEqual(receivedBytes, 3_221_225_472) // 3GB } func testPostWithAsyncSequenceOfByteBuffers() { @@ -249,19 +274,26 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/") request.method = .POST - request.body = .stream([ - ByteBuffer(string: "1"), - ByteBuffer(string: "2"), - ByteBuffer(string: "34"), - ].async, length: .unknown) + request.body = .stream( + [ + ByteBuffer(string: "1"), + ByteBuffer(string: "2"), + ByteBuffer(string: "34"), + ].async, + length: .unknown + ) - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response.headers["content-length"], []) - guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(upTo: 1024) - ) else { return } + guard + let body = await XCTAssertNoThrowWithResult( + try await response.body.collect(upTo: 1024) + ) + else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } } @@ -277,13 +309,17 @@ final class AsyncAwaitEndToEndTests: XCTestCase { request.method = .POST request.body = .stream("1234".utf8.async, length: .unknown) - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response.headers["content-length"], []) - guard let body = await XCTAssertNoThrowWithResult( - try await response.body.collect(upTo: 1024) - ) else { return } + guard + let body = await XCTAssertNoThrowWithResult( + try await response.body.collect(upTo: 1024) + ) + else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } } @@ -300,9 +336,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let streamWriter = AsyncSequenceWriter() request.body = .stream(streamWriter, length: .unknown) - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response.headers["content-length"], []) let fragments = [ @@ -313,16 +351,20 @@ final class AsyncAwaitEndToEndTests: XCTestCase { var bodyIterator = response.body.makeAsyncIterator() for expectedFragment in fragments { streamWriter.write(expectedFragment) - guard let actualFragment = await XCTAssertNoThrowWithResult( - try await bodyIterator.next() - ) else { return } + guard + let actualFragment = await XCTAssertNoThrowWithResult( + try await bodyIterator.next() + ) + else { return } XCTAssertEqual(expectedFragment, actualFragment) } streamWriter.end() - guard let lastResult = await XCTAssertNoThrowWithResult( - try await bodyIterator.next() - ) else { return } + guard + let lastResult = await XCTAssertNoThrowWithResult( + try await bodyIterator.next() + ) + else { return } XCTAssertEqual(lastResult, nil) } } @@ -339,9 +381,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let streamWriter = AsyncSequenceWriter() request.body = .stream(streamWriter, length: .unknown) - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response.headers["content-length"], []) let fragments = [ @@ -353,16 +397,20 @@ final class AsyncAwaitEndToEndTests: XCTestCase { var bodyIterator = response.body.makeAsyncIterator() for expectedFragment in fragments { streamWriter.write(expectedFragment) - guard let actualFragment = await XCTAssertNoThrowWithResult( - try await bodyIterator.next() - ) else { return } + guard + let actualFragment = await XCTAssertNoThrowWithResult( + try await bodyIterator.next() + ) + else { return } XCTAssertEqual(expectedFragment, actualFragment) } streamWriter.end() - guard let lastResult = await XCTAssertNoThrowWithResult( - try await bodyIterator.next() - ) else { return } + guard + let lastResult = await XCTAssertNoThrowWithResult( + try await bodyIterator.next() + ) + else { return } XCTAssertEqual(lastResult, nil) } } @@ -436,7 +484,10 @@ final class AsyncAwaitEndToEndTests: XCTestCase { // a race between deadline and connect timer can result in either error. // If closing happens really fast we might shutdown the pipeline before we fail the request. // If the pipeline is closed we may receive a `.remoteConnectionClosed`. - XCTAssertTrue([.deadlineExceeded, .connectTimeout, .remoteConnectionClosed].contains(error), "unexpected error \(error)") + XCTAssertTrue( + [.deadlineExceeded, .connectTimeout, .remoteConnectionClosed].contains(error), + "unexpected error \(error)" + ) } } } @@ -460,7 +511,10 @@ final class AsyncAwaitEndToEndTests: XCTestCase { // a race between deadline and connect timer can result in either error. // If closing happens really fast we might shutdown the pipeline before we fail the request. // If the pipeline is closed we may receive a `.remoteConnectionClosed`. - XCTAssertTrue([.deadlineExceeded, .connectTimeout, .remoteConnectionClosed].contains(error), "unexpected error \(error)") + XCTAssertTrue( + [.deadlineExceeded, .connectTimeout, .remoteConnectionClosed].contains(error), + "unexpected error \(error)" + ) } } } @@ -500,8 +554,10 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let url = "http://localhost:\(port)/get" #endif - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(timeout: .init(connect: .milliseconds(100), read: .milliseconds(150)))) + let httpClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(timeout: .init(connect: .milliseconds(100), read: .milliseconds(150))) + ) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) @@ -549,7 +605,8 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let localClient = HTTPClient(eventLoopGroupProvider: .singleton, configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } let request = HTTPClientRequest(url: "https://localhost:\(port)") - await XCTAssertThrowsError(try await localClient.execute(request, deadline: .now() + .seconds(2))) { error in + await XCTAssertThrowsError(try await localClient.execute(request, deadline: .now() + .seconds(2))) { + error in #if canImport(Network) guard let nwTLSError = error as? HTTPClient.NWTLSError else { XCTFail("could not cast \(error) of type \(type(of: error)) to \(HTTPClient.NWTLSError.self)") @@ -558,7 +615,8 @@ final class AsyncAwaitEndToEndTests: XCTestCase { XCTAssertEqual(nwTLSError.status, errSSLBadCert, "unexpected tls error: \(nwTLSError)") #else guard let sslError = error as? NIOSSLError, - case .handshakeFailed(.sslError) = sslError else { + case .handshakeFailed(.sslError) = sslError + else { XCTFail("unexpected error \(error)") return } @@ -619,7 +677,9 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let localClient = HTTPClient(eventLoopGroupProvider: .singleton, configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } let request = HTTPClientRequest(url: "https://example.com:\(bin.port)/echohostheader") - let response = await XCTAssertNoThrowWithResult(try await localClient.execute(request, deadline: .now() + .seconds(2))) + let response = await XCTAssertNoThrowWithResult( + try await localClient.execute(request, deadline: .now() + .seconds(2)) + ) XCTAssertEqual(response?.status, .ok) XCTAssertEqual(response?.version, .http2) var body = try await response?.body.collect(upTo: 1024) @@ -634,9 +694,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let client = makeDefaultHTTPClient() defer { XCTAssertNoThrow(try client.syncShutdown()) } let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) - let request = HTTPClientRequest(url: "") // invalid URL + let request = HTTPClientRequest(url: "") // invalid URL - await XCTAssertThrowsError(try await client.execute(request, deadline: .now() + .seconds(2), logger: logger)) { + await XCTAssertThrowsError( + try await client.execute(request, deadline: .now() + .seconds(2), logger: logger) + ) { XCTAssertEqual($0 as? HTTPClientError, .invalidURL) } } @@ -668,14 +730,21 @@ final class AsyncAwaitEndToEndTests: XCTestCase { defer { XCTAssertNoThrow(try client.syncShutdown()) } let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) var request = HTTPClientRequest(url: "https://127.0.0.1:\(bin.port)/redirect/target") - request.headers.replaceOrAdd(name: "X-Target-Redirect-URL", value: "https://localhost:\(bin.port)/echohostheader") + request.headers.replaceOrAdd( + name: "X-Target-Redirect-URL", + value: "https://localhost:\(bin.port)/echohostheader" + ) - guard let response = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { + return + } + guard let body = await XCTAssertNoThrowWithResult(try await response.body.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 } @@ -722,28 +791,39 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/") request.method = .POST - request.body = .stream([ - ByteBuffer(string: "1"), - ByteBuffer(string: "2"), - ByteBuffer(string: "34"), - ].async, length: .unknown) + request.body = .stream( + [ + ByteBuffer(string: "1"), + ByteBuffer(string: "2"), + ByteBuffer(string: "34"), + ].async, + length: .unknown + ) - guard let response1 = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response1 = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response1.headers["content-length"], []) - guard let body = await XCTAssertNoThrowWithResult( - try await response1.body.collect(upTo: 1024) - ) else { return } + guard + let body = await XCTAssertNoThrowWithResult( + try await response1.body.collect(upTo: 1024) + ) + else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) - guard let response2 = await XCTAssertNoThrowWithResult( - try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) - ) else { return } + guard + let response2 = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } XCTAssertEqual(response2.headers["content-length"], []) - guard let body = await XCTAssertNoThrowWithResult( - try await response2.body.collect(upTo: 1024) - ) else { return } + guard + let body = await XCTAssertNoThrowWithResult( + try await response2.body.collect(upTo: 1024) + ) + else { return } XCTAssertEqual(body, ByteBuffer(string: "1234")) } } @@ -782,9 +862,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { 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 { + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } @@ -801,7 +883,9 @@ final class AsyncAwaitEndToEndTests: XCTestCase { 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 + await XCTAssertThrowsError( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) { error in XCTAssertEqual(error as? HTTPClientError, .invalidHeaderFieldNames([forbiddenFieldName])) } } @@ -825,15 +909,18 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) // We reject all ASCII control characters except HTAB and tolerate everything else. - let weirdAllowedFieldValue = "!\" \t#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + 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 { + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } @@ -850,7 +937,9 @@ final class AsyncAwaitEndToEndTests: XCTestCase { 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 + await XCTAssertThrowsError( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) { error in XCTAssertEqual(error as? HTTPClientError, .invalidHeaderFieldValues([forbiddenFieldValue])) } } @@ -863,9 +952,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { 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 { + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } @@ -882,9 +973,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { 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 { + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request: request).get() + ) + else { return } @@ -901,9 +994,11 @@ final class AsyncAwaitEndToEndTests: XCTestCase { defer { XCTAssertNoThrow(try client.syncShutdown()) } let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) 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 } + guard + let response = await XCTAssertNoThrowWithResult( + try await client.execute(request, deadline: .now() + .seconds(10), logger: logger) + ) + else { return } await XCTAssertThrowsError( try await response.body.collect(upTo: 3) ) { diff --git a/Tests/AsyncHTTPClientTests/AsyncTestHelpers.swift b/Tests/AsyncHTTPClientTests/AsyncTestHelpers.swift index 147b24dca..cbab922a4 100644 --- a/Tests/AsyncHTTPClientTests/AsyncTestHelpers.swift +++ b/Tests/AsyncHTTPClientTests/AsyncTestHelpers.swift @@ -33,7 +33,7 @@ final class AsyncSequenceWriter: AsyncSequence, @unchecked Sendable { } func makeAsyncIterator() -> Iterator { - return Iterator(self) + Iterator(self) } private enum State { @@ -117,7 +117,9 @@ final class AsyncSequenceWriter: AsyncSequence, @unchecked Sendable { case .waiting: let state = self._state self.lock.unlock() - preconditionFailure("Expected that there is always only one concurrent call to next. Invalid state: \(state)") + preconditionFailure( + "Expected that there is always only one concurrent call to next. Invalid state: \(state)" + ) } } diff --git a/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests.swift b/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests.swift index 79c304fc2..962791334 100644 --- a/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests.swift +++ b/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests.swift @@ -14,9 +14,6 @@ import AsyncHTTPClient import Atomics -#if canImport(Network) -import Network -#endif import Logging import NIOConcurrencyHelpers import NIOCore @@ -29,6 +26,10 @@ import NIOTestUtils import NIOTransportServices import XCTest +#if canImport(Network) +import Network +#endif + final class ConnectionPoolSizeConfigValueIsRespectedTests: XCTestCaseHTTPClientTestsBaseClass { func testConnectionPoolSizeConfigValueIsRespected() { let numberOfRequestsPerThread = 1000 diff --git a/Tests/AsyncHTTPClientTests/EmbeddedChannel+HTTPConvenience.swift b/Tests/AsyncHTTPClientTests/EmbeddedChannel+HTTPConvenience.swift index 5e7a1a9bc..914d03612 100644 --- a/Tests/AsyncHTTPClientTests/EmbeddedChannel+HTTPConvenience.swift +++ b/Tests/AsyncHTTPClientTests/EmbeddedChannel+HTTPConvenience.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOEmbedded import NIOHTTP1 import NIOHTTP2 +@testable import AsyncHTTPClient + extension EmbeddedChannel { public func receiveHeadAndVerify(_ verify: (HTTPRequestHead) throws -> Void = { _ in }) throws { let part = try self.readOutbound(as: HTTPClientRequestPart.self) @@ -111,6 +112,6 @@ public struct HTTP1EmbeddedChannelError: Error, Hashable, CustomStringConvertibl } public var description: String { - return self.reason + self.reason } } diff --git a/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift b/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift index c91db94b3..53af0823d 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOEmbedded import NIOHTTP1 import XCTest +@testable import AsyncHTTPClient + class HTTP1ClientChannelHandlerTests: XCTestCase { func testResponseBackpressure() { let embedded = EmbeddedChannel() @@ -32,27 +33,35 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { let delegate = ResponseBackpressureDelegate(eventLoop: embedded.eventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } testUtils.connection.executeRequest(requestBag) - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .GET) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .GET) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + } + ) XCTAssertEqual(try embedded.readOutbound(as: HTTPClientRequestPart.self), .end(nil)) - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) XCTAssertEqual(testUtils.readEventHandler.readHitCounter, 0) embedded.read() @@ -113,22 +122,30 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { guard let testUtils = maybeTestUtils else { return XCTFail("Expected connection setup works") } var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream(contentLength: 100) { writer in - testWriter.start(writer: writer) - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream(contentLength: 100) { writer in + testWriter.start(writer: writer) + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } // the handler only writes once the channel is writable @@ -143,12 +160,14 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { testWriter.writabilityChanged(true) embedded.pipeline.fireChannelWritabilityChanged() - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .POST) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - XCTAssertEqual($0.headers.first(name: "content-length"), "100") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .POST) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + XCTAssertEqual($0.headers.first(name: "content-length"), "100") + } + ) // the next body write will be executed once we tick the el. before we make the channel // unwritable @@ -162,9 +181,11 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { embedded.embeddedEventLoop.run() - XCTAssertNoThrow(try embedded.receiveBodyAndVerify { - XCTAssertEqual($0.readableBytes, 2) - }) + XCTAssertNoThrow( + try embedded.receiveBodyAndVerify { + XCTAssertEqual($0.readableBytes, 2) + } + ) XCTAssertEqual(testWriter.written, index + 1) @@ -201,24 +222,28 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } testUtils.connection.executeRequest(requestBag) - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .GET) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .GET) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + } + ) XCTAssertNoThrow(try embedded.receiveEnd()) XCTAssertTrue(embedded.isActive) @@ -247,27 +272,35 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { let delegate = ResponseBackpressureDelegate(eventLoop: embedded.eventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } testUtils.connection.executeRequest(requestBag) - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .GET) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .GET) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + } + ) XCTAssertNoThrow(try embedded.receiveEnd()) - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) XCTAssertEqual(testUtils.readEventHandler.readHitCounter, 0) embedded.read() @@ -299,27 +332,35 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { let delegate = ResponseBackpressureDelegate(eventLoop: embedded.eventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } testUtils.connection.executeRequest(requestBag) - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .GET) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .GET) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + } + ) XCTAssertNoThrow(try embedded.receiveEnd()) - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) XCTAssertEqual(testUtils.readEventHandler.readHitCounter, 0) embedded.read() @@ -345,25 +386,33 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { guard let testUtils = maybeTestUtils else { return XCTFail("Expected connection setup works") } var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream(contentLength: 10) { writer in - // Advance time by more than the idle write timeout (that's 1 millisecond) to trigger the timeout. - embedded.embeddedEventLoop.advanceTime(by: .milliseconds(2)) - return testWriter.start(writer: writer) - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream(contentLength: 10) { writer in + // Advance time by more than the idle write timeout (that's 1 millisecond) to trigger the timeout. + embedded.embeddedEventLoop.advanceTime(by: .milliseconds(2)) + return testWriter.start(writer: writer) + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.isWritable = true @@ -383,27 +432,35 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { guard let testUtils = maybeTestUtils else { return XCTFail("Expected connection setup works") } var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream { _ in - // Advance time by more than the idle write timeout (that's 1 millisecond) to trigger the timeout. - let scheduled = embedded.embeddedEventLoop.flatScheduleTask(in: .milliseconds(2)) { - embedded.embeddedEventLoop.makeSucceededVoidFuture() - } - return scheduled.futureResult - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream { _ in + // Advance time by more than the idle write timeout (that's 1 millisecond) to trigger the timeout. + let scheduled = embedded.embeddedEventLoop.flatScheduleTask(in: .milliseconds(2)) { + embedded.embeddedEventLoop.makeSucceededVoidFuture() + } + return scheduled.futureResult + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleWriteTimeout: .milliseconds(5)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleWriteTimeout: .milliseconds(5)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.isWritable = true @@ -434,34 +491,42 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { guard let testUtils = maybeTestUtils else { return XCTFail("Expected connection setup works") } var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream(contentLength: 10) { writer in - embedded.isWritable = false - embedded.pipeline.fireChannelWritabilityChanged() - // This should not trigger any errors or timeouts, because the timer isn't running - // as the channel is not writable. - embedded.embeddedEventLoop.advanceTime(by: .milliseconds(20)) - - // Now that the channel will become writable, this should trigger a timeout. - embedded.isWritable = true - embedded.pipeline.fireChannelWritabilityChanged() - embedded.embeddedEventLoop.advanceTime(by: .milliseconds(2)) - - return testWriter.start(writer: writer) - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream(contentLength: 10) { writer in + embedded.isWritable = false + embedded.pipeline.fireChannelWritabilityChanged() + // This should not trigger any errors or timeouts, because the timer isn't running + // as the channel is not writable. + embedded.embeddedEventLoop.advanceTime(by: .milliseconds(20)) + + // Now that the channel will become writable, this should trigger a timeout. + embedded.isWritable = true + embedded.pipeline.fireChannelWritabilityChanged() + embedded.embeddedEventLoop.advanceTime(by: .milliseconds(2)) + + return testWriter.start(writer: writer) + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.isWritable = true @@ -482,22 +547,30 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { guard let testUtils = maybeTestUtils else { return XCTFail("Expected connection setup works") } var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream(contentLength: 2) { writer in - return testWriter.start(writer: writer, expectedErrors: [HTTPClientError.cancelled]) - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream(contentLength: 2) { writer in + testWriter.start(writer: writer, expectedErrors: [HTTPClientError.cancelled]) + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.isWritable = true @@ -528,27 +601,35 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { let delegate = ResponseBackpressureDelegate(eventLoop: embedded.eventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } testUtils.connection.executeRequest(requestBag) - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .GET) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .GET) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + } + ) XCTAssertNoThrow(try embedded.receiveEnd()) - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "50")])) + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "50")]) + ) XCTAssertEqual(testUtils.readEventHandler.readHitCounter, 0) embedded.read() @@ -599,7 +680,12 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { XCTAssertNoThrow(maybeTestUtils = try embedded.setupHTTP1Connection()) guard let testUtils = maybeTestUtils else { return XCTFail("Expected connection setup works") } - XCTAssertNoThrow(try embedded.pipeline.syncOperations.addHandler(FailWriteHandler(), position: .after(testUtils.readEventHandler))) + XCTAssertNoThrow( + try embedded.pipeline.syncOperations.addHandler( + FailWriteHandler(), + position: .after(testUtils.readEventHandler) + ) + ) let logger = Logger(label: "test") @@ -609,16 +695,20 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) - guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) + guard let requestBag = maybeRequestBag else { + return XCTFail("Expected to be able to create a request bag") + } embedded.isWritable = false XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait()) @@ -645,22 +735,30 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { guard let testUtils = maybeTestUtils else { return XCTFail("Expected connection setup works") } var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream(contentLength: 10) { writer in - testWriter.start(writer: writer) - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream(contentLength: 10) { writer in + testWriter.start(writer: writer) + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } XCTAssertNoThrow(try embedded.pipeline.addHandler(FailEndHandler(), position: .first).wait()) @@ -668,12 +766,14 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { // Execute the request and we'll receive the head. testWriter.writabilityChanged(true) testUtils.connection.executeRequest(requestBag) - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .POST) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - XCTAssertEqual($0.headers.first(name: "content-length"), "10") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .POST) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + XCTAssertEqual($0.headers.first(name: "content-length"), "10") + } + ) // We're going to immediately send the response head and end. let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) XCTAssertNoThrow(try embedded.writeInbound(HTTPClientResponsePart.head(responseHead))) @@ -689,9 +789,11 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { embedded.embeddedEventLoop.run() XCTAssertEqual(testWriter.written, 5) for _ in 0..<5 { - XCTAssertNoThrow(try embedded.receiveBodyAndVerify { - XCTAssertEqual($0.readableBytes, 2) - }) + XCTAssertNoThrow( + try embedded.receiveBodyAndVerify { + XCTAssertEqual($0.readableBytes, 2) + } + ) } embedded.embeddedEventLoop.run() @@ -722,10 +824,13 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { backgroundLogger: Logger(label: "no-op", factory: SwiftLogNoOpLogHandler.init), connectionIdLoggerMetadata: "test connection" ) - let channel = EmbeddedChannel(handlers: [ - ChangeWritabilityOnFlush(), - handler, - ], loop: eventLoop) + let channel = EmbeddedChannel( + handlers: [ + ChangeWritabilityOnFlush(), + handler, + ], + loop: eventLoop + ) try channel.connect(to: .init(ipAddress: "127.0.0.1", port: 80)).wait() let request = MockHTTPExecutableRequest() @@ -825,7 +930,10 @@ class ResponseBackpressureDelegate: HTTPClientResponseDelegate { return newPromise.futureResult case .waitingForRemote(var promiseBuffer): - assert(!promiseBuffer.isEmpty, "assert expected to be waiting if we have at least one promise in the buffer") + assert( + !promiseBuffer.isEmpty, + "assert expected to be waiting if we have at least one promise in the buffer" + ) let promise = self.eventLoop.makePromise(of: ByteBuffer?.self) promiseBuffer.append(promise) self.state = .waitingForRemote(promiseBuffer) @@ -864,7 +972,10 @@ class ResponseBackpressureDelegate: HTTPClientResponseDelegate { func didReceiveBodyPart(task: HTTPClient.Task, _ buffer: ByteBuffer) -> EventLoopFuture { switch self.state { case .waitingForRemote(var promiseBuffer): - assert(!promiseBuffer.isEmpty, "assert expected to be waiting if we have at least one promise in the buffer") + assert( + !promiseBuffer.isEmpty, + "assert expected to be waiting if we have at least one promise in the buffer" + ) let promise = promiseBuffer.removeFirst() if promiseBuffer.isEmpty { let newBackpressurePromise = self.eventLoop.makePromise(of: Void.self) @@ -883,7 +994,9 @@ class ResponseBackpressureDelegate: HTTPClientResponseDelegate { return promise.futureResult case .buffering(.some): - preconditionFailure("Did receive response part should not be called, before the previous promise was succeeded.") + preconditionFailure( + "Did receive response part should not be called, before the previous promise was succeeded." + ) case .done, .consuming: preconditionFailure("Invalid state: \(self.state)") @@ -893,8 +1006,8 @@ class ResponseBackpressureDelegate: HTTPClientResponseDelegate { func didFinishRequest(task: HTTPClient.Task) throws { switch self.state { case .waitingForRemote(let promiseBuffer): - promiseBuffer.forEach { - $0.succeed(.none) + for promise in promiseBuffer { + promise.succeed(.none) } self.state = .done @@ -905,7 +1018,9 @@ class ResponseBackpressureDelegate: HTTPClientResponseDelegate { preconditionFailure("Invalid state: \(self.state)") case .buffering(.some): - preconditionFailure("Did receive response part should not be called, before the previous promise was succeeded.") + preconditionFailure( + "Did receive response part should not be called, before the previous promise was succeeded." + ) } } } diff --git a/Tests/AsyncHTTPClientTests/HTTP1ConnectionStateMachineTests.swift b/Tests/AsyncHTTPClientTests/HTTP1ConnectionStateMachineTests.swift index e256aa49e..18831d32f 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ConnectionStateMachineTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ConnectionStateMachineTests.swift @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOHTTP1 import NIOHTTPCompression import XCTest +@testable import AsyncHTTPClient + class HTTP1ConnectionStateMachineTests: XCTestCase { func testPOSTRequestWithWriteAndReadBackpressure() { var state = HTTP1ConnectionStateMachine() @@ -27,7 +28,10 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(4)) XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .wait) XCTAssertEqual(state.writabilityChanged(writable: true), .sendRequestHead(requestHead, sendEnd: false)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false)) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false) + ) let part0 = IOData.byteBuffer(ByteBuffer(bytes: [0])) let part1 = IOData.byteBuffer(ByteBuffer(bytes: [1])) @@ -51,7 +55,10 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { XCTAssertEqual(state.requestStreamFinished(promise: nil), .sendRequestEnd(nil)) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let responseBody = ByteBuffer(bytes: [1, 2, 3, 4]) XCTAssertEqual(state.channelRead(.body(responseBody)), .wait) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.informConnectionIsIdle, .init([responseBody]))) @@ -66,10 +73,16 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata) XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, sendEnd: true)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true)) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["content-length": "12"]) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let part0 = ByteBuffer(bytes: 0...3) let part1 = ByteBuffer(bytes: 4...7) let part2 = ByteBuffer(bytes: 8...11) @@ -95,10 +108,16 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: true, body: .fixedSize(0)) let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata) XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, sendEnd: true)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true)) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let responseBody = ByteBuffer(bytes: [1, 2, 3, 4]) XCTAssertEqual(state.channelRead(.body(responseBody)), .wait) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, .init([responseBody]))) @@ -112,10 +131,16 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata) XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, sendEnd: true)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true)) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true) + ) let responseHead = HTTPResponseHead(version: .http1_0, status: .ok, headers: ["content-length": "4"]) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let responseBody = ByteBuffer(bytes: [1, 2, 3, 4]) XCTAssertEqual(state.channelRead(.body(responseBody)), .wait) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, .init([responseBody]))) @@ -129,10 +154,20 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata) XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, sendEnd: true)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true)) - - let responseHead = HTTPResponseHead(version: .http1_0, status: .ok, headers: ["content-length": "4", "connection": "keep-alive"]) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true) + ) + + let responseHead = HTTPResponseHead( + version: .http1_0, + status: .ok, + headers: ["content-length": "4", "connection": "keep-alive"] + ) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let responseBody = ByteBuffer(bytes: [1, 2, 3, 4]) XCTAssertEqual(state.channelRead(.body(responseBody)), .wait) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.informConnectionIsIdle, .init([responseBody]))) @@ -147,10 +182,16 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata) XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, sendEnd: true)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true)) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["connection": "close"]) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let responseBody = ByteBuffer(bytes: [1, 2, 3, 4]) XCTAssertEqual(state.channelRead(.body(responseBody)), .wait) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, .init([responseBody]))) @@ -191,13 +232,19 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(4)) XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .wait) XCTAssertEqual(state.writabilityChanged(writable: true), .sendRequestHead(requestHead, sendEnd: false)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false)) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false) + ) let part0 = IOData.byteBuffer(ByteBuffer(bytes: [0])) let part1 = IOData.byteBuffer(ByteBuffer(bytes: [1])) XCTAssertEqual(state.requestStreamPartReceived(part0, promise: nil), .sendBodyPart(part0, nil)) XCTAssertEqual(state.requestStreamPartReceived(part1, promise: nil), .sendBodyPart(part1, nil)) - XCTAssertEqual(state.requestCancelled(closeConnection: false), .failRequest(HTTPClientError.cancelled, .close(nil))) + XCTAssertEqual( + state.requestCancelled(closeConnection: false), + .failRequest(HTTPClientError.cancelled, .close(nil)) + ) } func testNewRequestAfterErrorHappened() { @@ -218,9 +265,17 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive) XCTAssertEqual(state.requestCancelled(closeConnection: false), .wait, "Should be ignored.") XCTAssertEqual(state.requestCancelled(closeConnection: true), .close, "Should lead to connection closure.") - XCTAssertEqual(state.requestCancelled(closeConnection: true), .wait, "Should be ignored. Connection is already closing") + XCTAssertEqual( + state.requestCancelled(closeConnection: true), + .wait, + "Should be ignored. Connection is already closing" + ) XCTAssertEqual(state.channelInactive(), .fireChannelInactive) - XCTAssertEqual(state.requestCancelled(closeConnection: true), .wait, "Should be ignored. Connection is already closed") + XCTAssertEqual( + state.requestCancelled(closeConnection: true), + .wait, + "Should be ignored. Connection is already closed" + ) } func testReadsAreForwardedIfConnectionIsClosing() { @@ -248,7 +303,10 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: ["content-length": "4"]) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(4)) XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .wait) - XCTAssertEqual(state.requestCancelled(closeConnection: false), .failRequest(HTTPClientError.cancelled, .informConnectionIsIdle)) + XCTAssertEqual( + state.requestCancelled(closeConnection: false), + .failRequest(HTTPClientError.cancelled, .informConnectionIsIdle) + ) } func testConnectionIsClosedIfErrorHappensWhileInRequest() { @@ -258,9 +316,15 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata) XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, sendEnd: true)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true)) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.channelRead(.body(ByteBuffer(string: "Hello world!\n"))), .wait) XCTAssertEqual(state.channelRead(.body(ByteBuffer(string: "Foo Bar!\n"))), .wait) let decompressionError = NIOHTTPDecompression.DecompressionError.limit @@ -274,9 +338,15 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata) XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, sendEnd: true)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true)) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .switchingProtocols) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, [])) } @@ -287,8 +357,14 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata) XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, sendEnd: true)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true)) - let responseHead = HTTPResponseHead(version: .http1_1, status: .init(statusCode: 103, reasonPhrase: "Early Hints")) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: false, startIdleTimer: true) + ) + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .init(statusCode: 103, reasonPhrase: "Early Hints") + ) XCTAssertEqual(state.channelRead(.head(responseHead)), .wait) XCTAssertEqual(state.channelInactive(), .failRequest(HTTPClientError.remoteConnectionClosed, .none)) } @@ -339,13 +415,19 @@ extension HTTP1ConnectionStateMachine.Action: Equatable { case (.resumeRequestBodyStream, .resumeRequestBodyStream): return true - case (.forwardResponseHead(let lhsHead, let lhsPauseRequestBodyStream), .forwardResponseHead(let rhsHead, let rhsPauseRequestBodyStream)): + case ( + .forwardResponseHead(let lhsHead, let lhsPauseRequestBodyStream), + .forwardResponseHead(let rhsHead, let rhsPauseRequestBodyStream) + ): return lhsHead == rhsHead && lhsPauseRequestBodyStream == rhsPauseRequestBodyStream case (.forwardResponseBodyParts(let lhsData), .forwardResponseBodyParts(let rhsData)): return lhsData == rhsData - case (.succeedRequest(let lhsFinalAction, let lhsFinalBuffer), .succeedRequest(let rhsFinalAction, let rhsFinalBuffer)): + case ( + .succeedRequest(let lhsFinalAction, let lhsFinalBuffer), + .succeedRequest(let rhsFinalAction, let rhsFinalBuffer) + ): return lhsFinalAction == rhsFinalAction && lhsFinalBuffer == rhsFinalBuffer case (.failRequest(_, let lhsFinalAction), .failRequest(_, let rhsFinalAction)): @@ -367,7 +449,10 @@ extension HTTP1ConnectionStateMachine.Action: Equatable { } extension HTTP1ConnectionStateMachine.Action.FinalSuccessfulStreamAction: Equatable { - public static func == (lhs: HTTP1ConnectionStateMachine.Action.FinalSuccessfulStreamAction, rhs: HTTP1ConnectionStateMachine.Action.FinalSuccessfulStreamAction) -> Bool { + public static func == ( + lhs: HTTP1ConnectionStateMachine.Action.FinalSuccessfulStreamAction, + rhs: HTTP1ConnectionStateMachine.Action.FinalSuccessfulStreamAction + ) -> Bool { switch (lhs, rhs) { case (.close, .close): return true @@ -382,7 +467,10 @@ extension HTTP1ConnectionStateMachine.Action.FinalSuccessfulStreamAction: Equata } extension HTTP1ConnectionStateMachine.Action.FinalFailedStreamAction: Equatable { - public static func == (lhs: HTTP1ConnectionStateMachine.Action.FinalFailedStreamAction, rhs: HTTP1ConnectionStateMachine.Action.FinalFailedStreamAction) -> Bool { + public static func == ( + lhs: HTTP1ConnectionStateMachine.Action.FinalFailedStreamAction, + rhs: HTTP1ConnectionStateMachine.Action.FinalFailedStreamAction + ) -> Bool { switch (lhs, rhs) { case (.close(let lhsPromise), .close(let rhsPromise)): return lhsPromise?.futureResult == rhsPromise?.futureResult diff --git a/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift b/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift index 5ea8bb77c..5f980bccb 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOConcurrencyHelpers import NIOCore @@ -23,6 +22,8 @@ import NIOPosix import NIOTestUtils import XCTest +@testable import AsyncHTTPClient + class HTTP1ConnectionTests: XCTestCase { func testCreateNewConnectionWithDecompression() { let embedded = EmbeddedChannel() @@ -31,16 +32,20 @@ class HTTP1ConnectionTests: XCTestCase { XCTAssertNoThrow(try embedded.connect(to: SocketAddress(ipAddress: "127.0.0.1", port: 3000)).wait()) var connection: HTTP1Connection? - XCTAssertNoThrow(connection = try HTTP1Connection.start( - channel: embedded, - connectionID: 0, - delegate: MockHTTP1ConnectionDelegate(), - decompression: .enabled(limit: .ratio(4)), - logger: logger - )) + XCTAssertNoThrow( + connection = try HTTP1Connection.start( + channel: embedded, + connectionID: 0, + delegate: MockHTTP1ConnectionDelegate(), + decompression: .enabled(limit: .ratio(4)), + logger: logger + ) + ) XCTAssertNotNil(try embedded.pipeline.syncOperations.handler(type: HTTPRequestEncoder.self)) - XCTAssertNotNil(try embedded.pipeline.syncOperations.handler(type: ByteToMessageHandler.self)) + XCTAssertNotNil( + try embedded.pipeline.syncOperations.handler(type: ByteToMessageHandler.self) + ) XCTAssertNotNil(try embedded.pipeline.syncOperations.handler(type: NIOHTTPResponseDecompressor.self)) XCTAssertNoThrow(try connection?.close().wait()) @@ -54,17 +59,22 @@ class HTTP1ConnectionTests: XCTestCase { XCTAssertNoThrow(try embedded.connect(to: SocketAddress(ipAddress: "127.0.0.1", port: 3000)).wait()) - XCTAssertNoThrow(try HTTP1Connection.start( - channel: embedded, - connectionID: 0, - delegate: MockHTTP1ConnectionDelegate(), - decompression: .disabled, - logger: logger - )) + XCTAssertNoThrow( + try HTTP1Connection.start( + channel: embedded, + connectionID: 0, + delegate: MockHTTP1ConnectionDelegate(), + decompression: .disabled, + logger: logger + ) + ) XCTAssertNotNil(try embedded.pipeline.syncOperations.handler(type: HTTPRequestEncoder.self)) - XCTAssertNotNil(try embedded.pipeline.syncOperations.handler(type: ByteToMessageHandler.self)) - XCTAssertThrowsError(try embedded.pipeline.syncOperations.handler(type: NIOHTTPResponseDecompressor.self)) { error in + XCTAssertNotNil( + try embedded.pipeline.syncOperations.handler(type: ByteToMessageHandler.self) + ) + XCTAssertThrowsError(try embedded.pipeline.syncOperations.handler(type: NIOHTTPResponseDecompressor.self)) { + error in XCTAssertEqual(error as? ChannelPipelineError, .notFound) } } @@ -78,13 +88,15 @@ class HTTP1ConnectionTests: XCTestCase { embedded.embeddedEventLoop.run() let logger = Logger(label: "test.http1.connection") - XCTAssertThrowsError(try HTTP1Connection.start( - channel: embedded, - connectionID: 0, - delegate: MockHTTP1ConnectionDelegate(), - decompression: .disabled, - logger: logger - )) + XCTAssertThrowsError( + try HTTP1Connection.start( + channel: embedded, + connectionID: 0, + delegate: MockHTTP1ConnectionDelegate(), + decompression: .disabled, + logger: logger + ) + ) } func testGETRequest() { @@ -113,30 +125,32 @@ class HTTP1ConnectionTests: XCTestCase { .wait() var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( - url: "http://localhost/hello/swift", - method: .POST, - body: .stream(contentLength: 4) { writer -> EventLoopFuture in - @Sendable func recursive(count: UInt8, promise: EventLoopPromise) { - guard count < 4 else { - return promise.succeed(()) - } + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/hello/swift", + method: .POST, + body: .stream(contentLength: 4) { writer -> EventLoopFuture in + @Sendable func recursive(count: UInt8, promise: EventLoopPromise) { + guard count < 4 else { + return promise.succeed(()) + } - writer.write(.byteBuffer(ByteBuffer(bytes: [count]))).whenComplete { result in - switch result { - case .failure(let error): - XCTFail("Unexpected error: \(error)") - case .success: - recursive(count: count + 1, promise: promise) + writer.write(.byteBuffer(ByteBuffer(bytes: [count]))).whenComplete { result in + switch result { + case .failure(let error): + XCTFail("Unexpected error: \(error)") + case .success: + recursive(count: count + 1, promise: promise) + } } } - } - let promise = clientEL.makePromise(of: Void.self) - recursive(count: 0, promise: promise) - return promise.futureResult - } - )) + let promise = clientEL.makePromise(of: Void.self) + recursive(count: 0, promise: promise) + return promise.futureResult + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to have a connection and a request") @@ -145,33 +159,39 @@ class HTTP1ConnectionTests: XCTestCase { let task = HTTPClient.Task(eventLoop: clientEL, logger: logger) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: clientEL), - task: task, - redirectHandler: nil, - connectionDeadline: .now() + .seconds(60), - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: request) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: clientEL), + task: task, + redirectHandler: nil, + connectionDeadline: .now() + .seconds(60), + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: request) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } connection.executeRequest(requestBag) - XCTAssertNoThrow(try server.receiveHeadAndVerify { head in - XCTAssertEqual(head.method, .POST) - XCTAssertEqual(head.uri, "/hello/swift") - XCTAssertEqual(head.headers["content-length"].first, "4") - }) + XCTAssertNoThrow( + try server.receiveHeadAndVerify { head in + XCTAssertEqual(head.method, .POST) + XCTAssertEqual(head.uri, "/hello/swift") + XCTAssertEqual(head.headers["content-length"].first, "4") + } + ) var received: UInt8 = 0 while received < 4 { - XCTAssertNoThrow(try server.receiveBodyAndVerify { body in - var body = body - while let read = body.readInteger(as: UInt8.self) { - XCTAssertEqual(received, read) - received += 1 + XCTAssertNoThrow( + try server.receiveBodyAndVerify { body in + var body = body + while let read = body.readInteger(as: UInt8.self) { + XCTAssertEqual(received, read) + received += 1 + } } - }) + ) } XCTAssertEqual(received, 4) XCTAssertNoThrow(try server.receiveEnd()) @@ -198,17 +218,23 @@ class HTTP1ConnectionTests: XCTestCase { var maybeChannel: Channel? - XCTAssertNoThrow(maybeChannel = try ClientBootstrap(group: eventLoop).connect(host: "localhost", port: httpBin.port).wait()) + XCTAssertNoThrow( + maybeChannel = try ClientBootstrap(group: eventLoop).connect(host: "localhost", port: httpBin.port).wait() + ) let connectionDelegate = MockConnectionDelegate() let logger = Logger(label: "test") var maybeConnection: HTTP1Connection? - XCTAssertNoThrow(maybeConnection = try eventLoop.submit { try HTTP1Connection.start( - channel: XCTUnwrap(maybeChannel), - connectionID: 0, - delegate: connectionDelegate, - decompression: .disabled, - logger: logger - ) }.wait()) + XCTAssertNoThrow( + maybeConnection = try eventLoop.submit { + try HTTP1Connection.start( + channel: XCTUnwrap(maybeChannel), + connectionID: 0, + delegate: connectionDelegate, + decompression: .disabled, + logger: logger + ) + }.wait() + ) guard let connection = maybeConnection else { return XCTFail("Expected to have a connection here") } var maybeRequest: HTTPClient.Request? @@ -217,15 +243,17 @@ class HTTP1ConnectionTests: XCTestCase { let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: eventLoopGroup.next()), - task: .init(eventLoop: eventLoopGroup.next(), logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: eventLoopGroup.next()), + task: .init(eventLoop: eventLoopGroup.next(), logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } connection.executeRequest(requestBag) @@ -248,21 +276,29 @@ class HTTP1ConnectionTests: XCTestCase { defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let closeOnRequest = (30...100).randomElement()! - let httpBin = HTTPBin(handlerFactory: { _ in SuddenlySendsCloseHeaderChannelHandler(closeOnRequest: closeOnRequest) }) + let httpBin = HTTPBin(handlerFactory: { _ in + SuddenlySendsCloseHeaderChannelHandler(closeOnRequest: closeOnRequest) + }) var maybeChannel: Channel? - XCTAssertNoThrow(maybeChannel = try ClientBootstrap(group: eventLoop).connect(host: "localhost", port: httpBin.port).wait()) + XCTAssertNoThrow( + maybeChannel = try ClientBootstrap(group: eventLoop).connect(host: "localhost", port: httpBin.port).wait() + ) let connectionDelegate = MockConnectionDelegate() let logger = Logger(label: "test") var maybeConnection: HTTP1Connection? - XCTAssertNoThrow(maybeConnection = try eventLoop.submit { try HTTP1Connection.start( - channel: XCTUnwrap(maybeChannel), - connectionID: 0, - delegate: connectionDelegate, - decompression: .disabled, - logger: logger - ) }.wait()) + XCTAssertNoThrow( + maybeConnection = try eventLoop.submit { + try HTTP1Connection.start( + channel: XCTUnwrap(maybeChannel), + connectionID: 0, + delegate: connectionDelegate, + decompression: .disabled, + logger: logger + ) + }.wait() + ) guard let connection = maybeConnection else { return XCTFail("Expected to have a connection here") } var counter = 0 @@ -275,16 +311,20 @@ class HTTP1ConnectionTests: XCTestCase { let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: eventLoopGroup.next()), - task: .init(eventLoop: eventLoopGroup.next(), logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) - guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: eventLoopGroup.next()), + task: .init(eventLoop: eventLoopGroup.next(), logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) + guard let requestBag = maybeRequestBag else { + return XCTFail("Expected to be able to create a request bag") + } connection.executeRequest(requestBag) @@ -293,7 +333,7 @@ class HTTP1ConnectionTests: XCTestCase { XCTAssertEqual(response?.status, .ok) if response?.headers.first(name: "connection") == "close" { - break // the loop + break // the loop } else { XCTAssertEqual(httpBin.activeConnections, 1) XCTAssertEqual(connectionDelegate.hitConnectionReleased, counter) @@ -306,8 +346,11 @@ class HTTP1ConnectionTests: XCTestCase { XCTAssertEqual(counter, closeOnRequest) XCTAssertEqual(connectionDelegate.hitConnectionClosed, 1) - XCTAssertEqual(connectionDelegate.hitConnectionReleased, counter - 1, - "If a close header is received connection release is not triggered.") + XCTAssertEqual( + connectionDelegate.hitConnectionReleased, + counter - 1, + "If a close header is received connection release is not triggered." + ) // we need to wait a small amount of time to see the connection close on the server try! eventLoop.scheduleTask(in: .milliseconds(200)) {}.futureResult.wait() @@ -324,17 +367,23 @@ class HTTP1ConnectionTests: XCTestCase { var maybeChannel: Channel? - XCTAssertNoThrow(maybeChannel = try ClientBootstrap(group: eventLoop).connect(host: "localhost", port: httpBin.port).wait()) + XCTAssertNoThrow( + maybeChannel = try ClientBootstrap(group: eventLoop).connect(host: "localhost", port: httpBin.port).wait() + ) let connectionDelegate = MockConnectionDelegate() let logger = Logger(label: "test") var maybeConnection: HTTP1Connection? - XCTAssertNoThrow(maybeConnection = try eventLoop.submit { try HTTP1Connection.start( - channel: XCTUnwrap(maybeChannel), - connectionID: 0, - delegate: connectionDelegate, - decompression: .disabled, - logger: logger - ) }.wait()) + XCTAssertNoThrow( + maybeConnection = try eventLoop.submit { + try HTTP1Connection.start( + channel: XCTUnwrap(maybeChannel), + connectionID: 0, + delegate: connectionDelegate, + decompression: .disabled, + logger: logger + ) + }.wait() + ) guard let connection = maybeConnection else { return XCTFail("Expected to have a connection here") } var maybeRequest: HTTPClient.Request? @@ -343,15 +392,17 @@ class HTTP1ConnectionTests: XCTestCase { let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: eventLoopGroup.next()), - task: .init(eventLoop: eventLoopGroup.next(), logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: eventLoopGroup.next()), + task: .init(eventLoop: eventLoopGroup.next(), logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } connection.executeRequest(requestBag) @@ -373,13 +424,15 @@ class HTTP1ConnectionTests: XCTestCase { var maybeConnection: HTTP1Connection? let connectionDelegate = MockConnectionDelegate() - XCTAssertNoThrow(maybeConnection = try HTTP1Connection.start( - channel: embedded, - connectionID: 0, - delegate: connectionDelegate, - decompression: .enabled(limit: .ratio(4)), - logger: logger - )) + XCTAssertNoThrow( + maybeConnection = try HTTP1Connection.start( + channel: embedded, + connectionID: 0, + delegate: connectionDelegate, + decompression: .enabled(limit: .ratio(4)), + logger: logger + ) + ) guard let connection = maybeConnection else { return XCTFail("Expected to have a connection at this point.") } var maybeRequest: HTTPClient.Request? @@ -388,38 +441,40 @@ class HTTP1ConnectionTests: XCTestCase { let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } connection.executeRequest(requestBag) - XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // head - XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // end + XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // head + XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // end let responseString = """ - HTTP/1.1 101 Switching Protocols\r\n\ - Upgrade: websocket\r\n\ - Sec-WebSocket-Accept: xAMUK7/Il9bLRFJrikq6mm8CNZI=\r\n\ - Connection: upgrade\r\n\ - date: Mon, 27 Sep 2021 17:53:14 GMT\r\n\ - \r\n\ - \r\nfoo bar baz - """ + HTTP/1.1 101 Switching Protocols\r\n\ + Upgrade: websocket\r\n\ + Sec-WebSocket-Accept: xAMUK7/Il9bLRFJrikq6mm8CNZI=\r\n\ + Connection: upgrade\r\n\ + date: Mon, 27 Sep 2021 17:53:14 GMT\r\n\ + \r\n\ + \r\nfoo bar baz + """ XCTAssertTrue(embedded.isActive) XCTAssertEqual(connectionDelegate.hitConnectionClosed, 0) XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) XCTAssertNoThrow(try embedded.writeInbound(ByteBuffer(string: responseString))) XCTAssertFalse(embedded.isActive) - (embedded.eventLoop as! EmbeddedEventLoop).run() // tick once to run futures. + (embedded.eventLoop as! EmbeddedEventLoop).run() // tick once to run futures. XCTAssertEqual(connectionDelegate.hitConnectionClosed, 1) XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) @@ -438,13 +493,15 @@ class HTTP1ConnectionTests: XCTestCase { var maybeConnection: HTTP1Connection? let connectionDelegate = MockConnectionDelegate() - XCTAssertNoThrow(maybeConnection = try HTTP1Connection.start( - channel: embedded, - connectionID: 0, - delegate: connectionDelegate, - decompression: .enabled(limit: .ratio(4)), - logger: logger - )) + XCTAssertNoThrow( + maybeConnection = try HTTP1Connection.start( + channel: embedded, + connectionID: 0, + delegate: connectionDelegate, + decompression: .enabled(limit: .ratio(4)), + logger: logger + ) + ) guard let connection = maybeConnection else { return XCTFail("Expected to have a connection at this point.") } var maybeRequest: HTTPClient.Request? @@ -453,28 +510,30 @@ class HTTP1ConnectionTests: XCTestCase { let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } connection.executeRequest(requestBag) - XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // head - XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // end + XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // head + XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // end let responseString = """ - HTTP/1.1 103 Early Hints\r\n\ - date: Mon, 27 Sep 2021 17:53:14 GMT\r\n\ - \r\n\ - \r\n - """ + HTTP/1.1 103 Early Hints\r\n\ + date: Mon, 27 Sep 2021 17:53:14 GMT\r\n\ + \r\n\ + \r\n + """ XCTAssertTrue(embedded.isActive) XCTAssertEqual(connectionDelegate.hitConnectionClosed, 0) @@ -484,7 +543,7 @@ class HTTP1ConnectionTests: XCTestCase { XCTAssertTrue(embedded.isActive, "The connection remains active after the informational response head") XCTAssertNoThrow(try embedded.close().wait(), "the connection was closed") - embedded.embeddedEventLoop.run() // tick once to run futures. + embedded.embeddedEventLoop.run() // tick once to run futures. XCTAssertEqual(connectionDelegate.hitConnectionClosed, 1) XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) @@ -500,20 +559,22 @@ class HTTP1ConnectionTests: XCTestCase { XCTAssertNoThrow(try embedded.connect(to: SocketAddress(ipAddress: "127.0.0.1", port: 0)).wait()) let connectionDelegate = MockConnectionDelegate() - XCTAssertNoThrow(try HTTP1Connection.start( - channel: embedded, - connectionID: 0, - delegate: connectionDelegate, - decompression: .enabled(limit: .ratio(4)), - logger: logger - )) + XCTAssertNoThrow( + try HTTP1Connection.start( + channel: embedded, + connectionID: 0, + delegate: connectionDelegate, + decompression: .enabled(limit: .ratio(4)), + logger: logger + ) + ) let responseString = """ - HTTP/1.1 200 OK\r\n\ - date: Mon, 27 Sep 2021 17:53:14 GMT\r\n\ - \r\n\ - \r\n - """ + HTTP/1.1 200 OK\r\n\ + date: Mon, 27 Sep 2021 17:53:14 GMT\r\n\ + \r\n\ + \r\n + """ XCTAssertEqual(connectionDelegate.hitConnectionClosed, 0) XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) @@ -522,7 +583,7 @@ class HTTP1ConnectionTests: XCTestCase { XCTAssertEqual($0 as? NIOHTTPDecoderError, .unsolicitedResponse) } XCTAssertFalse(embedded.isActive) - (embedded.eventLoop as! EmbeddedEventLoop).run() // tick once to run futures. + (embedded.eventLoop as! EmbeddedEventLoop).run() // tick once to run futures. XCTAssertEqual(connectionDelegate.hitConnectionClosed, 1) XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) } @@ -535,13 +596,15 @@ class HTTP1ConnectionTests: XCTestCase { var maybeConnection: HTTP1Connection? let connectionDelegate = MockConnectionDelegate() - XCTAssertNoThrow(maybeConnection = try HTTP1Connection.start( - channel: embedded, - connectionID: 0, - delegate: connectionDelegate, - decompression: .enabled(limit: .ratio(4)), - logger: logger - )) + XCTAssertNoThrow( + maybeConnection = try HTTP1Connection.start( + channel: embedded, + connectionID: 0, + delegate: connectionDelegate, + decompression: .enabled(limit: .ratio(4)), + logger: logger + ) + ) guard let connection = maybeConnection else { return XCTFail("Expected to have a connection at this point.") } var maybeRequest: HTTPClient.Request? @@ -550,32 +613,34 @@ class HTTP1ConnectionTests: XCTestCase { let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } connection.executeRequest(requestBag) let responseString = """ - HTTP/1.0 200 OK\r\n\ - HTTP/1.0 200 OK\r\n\r\n - """ + HTTP/1.0 200 OK\r\n\ + HTTP/1.0 200 OK\r\n\r\n + """ - XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // head - XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // end + XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // head + XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // end XCTAssertEqual(connectionDelegate.hitConnectionClosed, 0) XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) XCTAssertNoThrow(try embedded.writeInbound(ByteBuffer(string: responseString))) XCTAssertFalse(embedded.isActive) - (embedded.eventLoop as! EmbeddedEventLoop).run() // tick once to run futures. + (embedded.eventLoop as! EmbeddedEventLoop).run() // tick once to run futures. XCTAssertEqual(connectionDelegate.hitConnectionClosed, 1) XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) } @@ -606,7 +671,7 @@ class HTTP1ConnectionTests: XCTestCase { } var reads: Int { - return self.lock.withLock { + self.lock.withLock { self._reads } } @@ -618,7 +683,7 @@ class HTTP1ConnectionTests: XCTestCase { } func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) -> EventLoopFuture { - return task.futureResult.eventLoop.makeSucceededVoidFuture() + task.futureResult.eventLoop.makeSucceededVoidFuture() } func didReceiveBodyPart(task: HTTPClient.Task, _ buffer: ByteBuffer) -> EventLoopFuture { @@ -679,34 +744,42 @@ class HTTP1ConnectionTests: XCTestCase { defer { XCTAssertNoThrow(try httpBin.shutdown()) } var maybeChannel: Channel? - XCTAssertNoThrow(maybeChannel = try ClientBootstrap(group: eventLoopGroup) - .channelOption(ChannelOptions.maxMessagesPerRead, value: 1) - .channelOption(ChannelOptions.recvAllocator, value: FixedSizeRecvByteBufferAllocator(capacity: 1)) - .connect(host: "localhost", port: httpBin.port) - .wait()) + XCTAssertNoThrow( + maybeChannel = try ClientBootstrap(group: eventLoopGroup) + .channelOption(ChannelOptions.maxMessagesPerRead, value: 1) + .channelOption(ChannelOptions.recvAllocator, value: FixedSizeRecvByteBufferAllocator(capacity: 1)) + .connect(host: "localhost", port: httpBin.port) + .wait() + ) guard let channel = maybeChannel else { return XCTFail("Expected to have a channel at this point") } let connectionDelegate = MockConnectionDelegate() var maybeConnection: HTTP1Connection? - XCTAssertNoThrow(maybeConnection = try channel.eventLoop.submit { try HTTP1Connection.start( - channel: channel, - connectionID: 0, - delegate: connectionDelegate, - decompression: .disabled, - logger: logger - ) }.wait()) + XCTAssertNoThrow( + maybeConnection = try channel.eventLoop.submit { + try HTTP1Connection.start( + channel: channel, + connectionID: 0, + delegate: connectionDelegate, + decompression: .disabled, + logger: logger + ) + }.wait() + ) guard let connection = maybeConnection else { return XCTFail("Expected to have a connection at this point") } var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: HTTPClient.Request(url: "http://localhost:\(httpBin.port)/custom"), - eventLoopPreference: .delegate(on: requestEventLoop), - task: .init(eventLoop: requestEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: backpressureDelegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: HTTPClient.Request(url: "http://localhost:\(httpBin.port)/custom"), + eventLoopPreference: .delegate(on: requestEventLoop), + task: .init(eventLoop: requestEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: backpressureDelegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } backpressureDelegate.willExecuteOnChannel(connection.channel) @@ -764,7 +837,12 @@ class SuddenlySendsCloseHeaderChannelHandler: ChannelInboundHandler { break case .end: if self.closeOnRequest == self.counter { - context.write(self.wrapOutboundOut(.head(.init(version: .http1_1, status: .ok, headers: ["connection": "close"]))), promise: nil) + context.write( + self.wrapOutboundOut( + .head(.init(version: .http1_1, status: .ok, headers: ["connection": "close"])) + ), + promise: nil + ) context.write(self.wrapOutboundOut(.end(nil)), promise: nil) context.flush() self.counter += 1 diff --git a/Tests/AsyncHTTPClientTests/HTTP1ProxyConnectHandlerTests.swift b/Tests/AsyncHTTPClientTests/HTTP1ProxyConnectHandlerTests.swift index b3917173f..d75865da2 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ProxyConnectHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ProxyConnectHandlerTests.swift @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOEmbedded import NIOHTTP1 import XCTest +@testable import AsyncHTTPClient + class HTTP1ProxyConnectHandlerTests: XCTestCase { func testProxyConnectWithoutAuthorizationSuccess() { let embedded = EmbeddedChannel() diff --git a/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift index 2428199a4..1f5f1b4c0 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOEmbedded import NIOHTTP1 import XCTest +@testable import AsyncHTTPClient + class HTTP2ClientRequestHandlerTests: XCTestCase { func testResponseBackpressure() { let embedded = EmbeddedChannel() @@ -34,28 +35,36 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { let delegate = ResponseBackpressureDelegate(eventLoop: embedded.eventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.write(requestBag, promise: nil) XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait()) - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .GET) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .GET) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + } + ) XCTAssertEqual(try embedded.readOutbound(as: HTTPClientRequestPart.self), .end(nil)) - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) XCTAssertEqual(readEventHandler.readHitCounter, 0) embedded.read() @@ -115,22 +124,30 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { let testWriter = TestBackpressureWriter(eventLoop: embedded.eventLoop, parts: 50) var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream(contentLength: 100) { writer in - testWriter.start(writer: writer) - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream(contentLength: 100) { writer in + testWriter.start(writer: writer) + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.isWritable = false @@ -143,12 +160,14 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { testWriter.writabilityChanged(true) embedded.pipeline.fireChannelWritabilityChanged() - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .POST) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - XCTAssertEqual($0.headers.first(name: "content-length"), "100") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .POST) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + XCTAssertEqual($0.headers.first(name: "content-length"), "100") + } + ) // the next body write will be executed once we tick the el. before we make the channel // unwritable @@ -162,9 +181,11 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { embedded.embeddedEventLoop.run() - XCTAssertNoThrow(try embedded.receiveBodyAndVerify { - XCTAssertEqual($0.readableBytes, 2) - }) + XCTAssertNoThrow( + try embedded.receiveBodyAndVerify { + XCTAssertEqual($0.readableBytes, 2) + } + ) XCTAssertEqual(testWriter.written, index + 1) @@ -198,27 +219,35 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { let delegate = ResponseBackpressureDelegate(eventLoop: embedded.eventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.write(requestBag, promise: nil) - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .GET) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .GET) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + } + ) XCTAssertNoThrow(try embedded.receiveEnd()) - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) XCTAssertEqual(readEventHandler.readHitCounter, 0) embedded.read() @@ -248,27 +277,35 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { let delegate = ResponseBackpressureDelegate(eventLoop: embedded.eventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.write(requestBag, promise: nil) - XCTAssertNoThrow(try embedded.receiveHeadAndVerify { - XCTAssertEqual($0.method, .GET) - XCTAssertEqual($0.uri, "/") - XCTAssertEqual($0.headers.first(name: "host"), "localhost") - }) + XCTAssertNoThrow( + try embedded.receiveHeadAndVerify { + XCTAssertEqual($0.method, .GET) + XCTAssertEqual($0.uri, "/") + XCTAssertEqual($0.headers.first(name: "host"), "localhost") + } + ) XCTAssertNoThrow(try embedded.receiveEnd()) - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) XCTAssertEqual(readEventHandler.readHitCounter, 0) embedded.read() @@ -295,24 +332,32 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { let testWriter = TestBackpressureWriter(eventLoop: embedded.eventLoop, parts: 5) var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream(contentLength: 10) { writer in - // Advance time by more than the idle write timeout (that's 1 millisecond) to trigger the timeout. - embedded.embeddedEventLoop.advanceTime(by: .milliseconds(2)) - return testWriter.start(writer: writer) - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream(contentLength: 10) { writer in + // Advance time by more than the idle write timeout (that's 1 millisecond) to trigger the timeout. + embedded.embeddedEventLoop.advanceTime(by: .milliseconds(2)) + return testWriter.start(writer: writer) + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseBackpressureDelegate(eventLoop: embedded.eventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.isWritable = true @@ -335,34 +380,42 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { let testWriter = TestBackpressureWriter(eventLoop: embedded.eventLoop, parts: 5) var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream(contentLength: 10) { writer in - embedded.isWritable = false - embedded.pipeline.fireChannelWritabilityChanged() - // This should not trigger any errors or timeouts, because the timer isn't running - // as the channel is not writable. - embedded.embeddedEventLoop.advanceTime(by: .milliseconds(20)) - - // Now that the channel will become writable, this should trigger a timeout. - embedded.isWritable = true - embedded.pipeline.fireChannelWritabilityChanged() - embedded.embeddedEventLoop.advanceTime(by: .milliseconds(2)) - - return testWriter.start(writer: writer) - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream(contentLength: 10) { writer in + embedded.isWritable = false + embedded.pipeline.fireChannelWritabilityChanged() + // This should not trigger any errors or timeouts, because the timer isn't running + // as the channel is not writable. + embedded.embeddedEventLoop.advanceTime(by: .milliseconds(20)) + + // Now that the channel will become writable, this should trigger a timeout. + embedded.isWritable = true + embedded.pipeline.fireChannelWritabilityChanged() + embedded.embeddedEventLoop.advanceTime(by: .milliseconds(2)) + + return testWriter.start(writer: writer) + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.isWritable = true @@ -385,22 +438,30 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { let testWriter = TestBackpressureWriter(eventLoop: embedded.eventLoop, parts: 5) var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost/", method: .POST, body: .stream(contentLength: 2) { writer in - return testWriter.start(writer: writer, expectedErrors: [HTTPClientError.cancelled]) - })) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost/", + method: .POST, + body: .stream(contentLength: 2) { writer in + testWriter.start(writer: writer, expectedErrors: [HTTPClientError.cancelled]) + } + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } let delegate = ResponseBackpressureDelegate(eventLoop: embedded.eventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleWriteTimeout: .milliseconds(1)), + delegate: delegate + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } embedded.isWritable = true @@ -451,16 +512,20 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { let delegate = ResponseAccumulator(request: request) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embedded.eventLoop), - task: .init(eventLoop: embedded.eventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), - delegate: delegate - )) - guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), + delegate: delegate + ) + ) + guard let requestBag = maybeRequestBag else { + return XCTFail("Expected to be able to create a request bag") + } embedded.isWritable = false XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait()) @@ -494,10 +559,13 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { let handler = HTTP2ClientRequestHandler( eventLoop: eventLoop ) - let channel = EmbeddedChannel(handlers: [ - ChangeWritabilityOnFlush(), - handler, - ], loop: eventLoop) + let channel = EmbeddedChannel( + handlers: [ + ChangeWritabilityOnFlush(), + handler, + ], + loop: eventLoop + ) try channel.connect(to: .init(ipAddress: "127.0.0.1", port: 80)).wait() let request = MockHTTPExecutableRequest() diff --git a/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift index 889cd38b9..1d6c0c8f8 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift @@ -12,10 +12,7 @@ // //===----------------------------------------------------------------------===// -/* NOT @testable */ import AsyncHTTPClient // Tests that really need @testable go into HTTP2ClientInternalTests.swift -#if canImport(Network) -import Network -#endif +import AsyncHTTPClient // NOT @testable - tests that really need @testable go into HTTP2ClientInternalTests.swift import Logging import NIOCore import NIOHTTP1 @@ -23,6 +20,10 @@ import NIOPosix import NIOSSL import XCTest +#if canImport(Network) +import Network +#endif + class HTTP2ClientTests: XCTestCase { func makeDefaultHTTPClient( eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .singleton @@ -132,8 +133,8 @@ class HTTP2ClientTests: XCTestCase { let q = DispatchQueue(label: "worker \(w)") q.async(group: allDone) { func go() { - allWorkersReady.signal() // tell the driver we're ready - allWorkersGo.wait() // wait for the driver to let us go + allWorkersReady.signal() // tell the driver we're ready + allWorkersGo.wait() // wait for the driver to let us go for _ in 0..] = [] - XCTAssertNoThrow(results = try EventLoopFuture - .whenAllComplete(responses, on: clientGroup.next()) - .timeout(after: .seconds(2)) - .wait()) + XCTAssertNoThrow( + results = + try EventLoopFuture + .whenAllComplete(responses, on: clientGroup.next()) + .timeout(after: .seconds(2)) + .wait() + ) for result in results { switch result { @@ -397,7 +402,11 @@ class HTTP2ClientTests: XCTestCase { XCTAssertNoThrow(maybeRequest1 = try HTTPClient.Request(url: "https://localhost:\(bin.port)/get")) guard let request1 = maybeRequest1 else { return } - let task1 = client.execute(request: request1, delegate: ResponseAccumulator(request: request1), eventLoop: .delegateAndChannel(on: el1)) + let task1 = client.execute( + request: request1, + delegate: ResponseAccumulator(request: request1), + eventLoop: .delegateAndChannel(on: el1) + ) var response1: ResponseAccumulator.Response? XCTAssertNoThrow(response1 = try task1.wait()) @@ -408,15 +417,17 @@ class HTTP2ClientTests: XCTestCase { let serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try serverGroup.syncShutdownGracefully()) } var maybeServer: Channel? - XCTAssertNoThrow(maybeServer = try ServerBootstrap(group: serverGroup) - .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) - .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), value: 1) - .childChannelInitializer { channel in - channel.close() - } - .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) - .bind(host: "127.0.0.1", port: serverPort) - .wait()) + XCTAssertNoThrow( + maybeServer = try ServerBootstrap(group: serverGroup) + .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), value: 1) + .childChannelInitializer { channel in + channel.close() + } + .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .bind(host: "127.0.0.1", port: serverPort) + .wait() + ) // shutting down the old server closes all connections immediately XCTAssertNoThrow(try bin.shutdown()) // client is now in HTTP/2 state and the HTTPBin is closed @@ -427,7 +438,11 @@ class HTTP2ClientTests: XCTestCase { XCTAssertNoThrow(maybeRequest2 = try HTTPClient.Request(url: "https://localhost:\(serverPort)/")) guard let request2 = maybeRequest2 else { return } - let task2 = client.execute(request: request2, delegate: ResponseAccumulator(request: request2), eventLoop: .delegateAndChannel(on: el2)) + let task2 = client.execute( + request: request2, + delegate: ResponseAccumulator(request: request2), + eventLoop: .delegateAndChannel(on: el2) + ) XCTAssertThrowsError(try task2.wait()) { error in XCTAssertNil( error as? HTTPClientError, @@ -474,11 +489,17 @@ private final class SendHeaderAndWaitChannelHandler: ChannelInboundHandler { let requestPart = self.unwrapInboundIn(data) switch requestPart { case .head: - context.writeAndFlush(self.wrapOutboundOut(.head(HTTPResponseHead( - version: HTTPVersion(major: 1, minor: 1), - status: .ok - )) - ), promise: nil) + context.writeAndFlush( + self.wrapOutboundOut( + .head( + HTTPResponseHead( + version: HTTPVersion(major: 1, minor: 1), + status: .ok + ) + ) + ), + promise: nil + ) case .body, .end: return } diff --git a/Tests/AsyncHTTPClientTests/HTTP2ConnectionTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ConnectionTests.swift index 2e82fafba..acf81beac 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2ConnectionTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2ConnectionTests.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOConcurrencyHelpers import NIOCore @@ -24,6 +23,8 @@ import NIOSSL import NIOTestUtils import XCTest +@testable import AsyncHTTPClient + class HTTP2ConnectionTests: XCTestCase { func testCreateNewConnectionFailureClosedIO() { let embedded = EmbeddedChannel() @@ -34,14 +35,16 @@ class HTTP2ConnectionTests: XCTestCase { embedded.embeddedEventLoop.run() let logger = Logger(label: "test.http2.connection") - XCTAssertThrowsError(try HTTP2Connection.start( - channel: embedded, - connectionID: 0, - delegate: TestHTTP2ConnectionDelegate(), - decompression: .disabled, - maximumConnectionUses: nil, - logger: logger - ).wait()) + XCTAssertThrowsError( + try HTTP2Connection.start( + channel: embedded, + connectionID: 0, + delegate: TestHTTP2ConnectionDelegate(), + decompression: .disabled, + maximumConnectionUses: nil, + logger: logger + ).wait() + ) } func testConnectionToleratesShutdownEventsAfterAlreadyClosed() { @@ -80,11 +83,12 @@ class HTTP2ConnectionTests: XCTestCase { let connectionCreator = TestConnectionCreator() let delegate = TestHTTP2ConnectionDelegate() var maybeHTTP2Connection: HTTP2Connection? - XCTAssertNoThrow(maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( - to: httpBin.port, - delegate: delegate, - on: eventLoop - ) + XCTAssertNoThrow( + maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( + to: httpBin.port, + delegate: delegate, + on: eventLoop + ) ) guard let http2Connection = maybeHTTP2Connection else { return XCTFail("Expected to have an HTTP2 connection here.") @@ -93,15 +97,17 @@ class HTTP2ConnectionTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoop, logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .distantFuture, - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoop, logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .distantFuture, + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to have a request bag at this point") } @@ -136,11 +142,13 @@ class HTTP2ConnectionTests: XCTestCase { let connectionCreator = TestConnectionCreator() let delegate = TestHTTP2ConnectionDelegate() var maybeHTTP2Connection: HTTP2Connection? - XCTAssertNoThrow(maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( - to: httpBin.port, - delegate: delegate, - on: eventLoop - )) + XCTAssertNoThrow( + maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( + to: httpBin.port, + delegate: delegate, + on: eventLoop + ) + ) guard let http2Connection = maybeHTTP2Connection else { return XCTFail("Expected to have an HTTP2 connection here.") } @@ -154,15 +162,17 @@ class HTTP2ConnectionTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoop, logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .distantFuture, - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoop, logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .distantFuture, + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to have a request bag at this point") } @@ -200,11 +210,12 @@ class HTTP2ConnectionTests: XCTestCase { let connectionCreator = TestConnectionCreator() let delegate = TestHTTP2ConnectionDelegate() var maybeHTTP2Connection: HTTP2Connection? - XCTAssertNoThrow(maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( - to: httpBin.port, - delegate: delegate, - on: eventLoop - ) + XCTAssertNoThrow( + maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( + to: httpBin.port, + delegate: delegate, + on: eventLoop + ) ) guard let http2Connection = maybeHTTP2Connection else { return XCTFail("Expected to have an HTTP2 connection here.") @@ -216,15 +227,17 @@ class HTTP2ConnectionTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoop, logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .distantFuture, - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoop, logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .distantFuture, + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to have a request bag at this point") } @@ -292,11 +305,13 @@ class HTTP2ConnectionTests: XCTestCase { let connectionCreator = TestConnectionCreator() let delegate = TestHTTP2ConnectionDelegate() var maybeHTTP2Connection: HTTP2Connection? - XCTAssertNoThrow(maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( - to: httpBin.port, - delegate: delegate, - on: eventLoop - )) + XCTAssertNoThrow( + maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( + to: httpBin.port, + delegate: delegate, + on: eventLoop + ) + ) guard let http2Connection = maybeHTTP2Connection else { return XCTFail("Expected to have an HTTP2 connection here.") } @@ -304,15 +319,17 @@ class HTTP2ConnectionTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoop, logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .distantFuture, - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoop, logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .distantFuture, + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to have a request bag at this point") } @@ -321,7 +338,9 @@ class HTTP2ConnectionTests: XCTestCase { XCTAssertNoThrow(try serverReceivedRequestPromise.futureResult.wait()) var channelCount: Int? - XCTAssertNoThrow(channelCount = try eventLoop.submit { http2Connection.__forTesting_getStreamChannels().count }.wait()) + XCTAssertNoThrow( + channelCount = try eventLoop.submit { http2Connection.__forTesting_getStreamChannels().count }.wait() + ) XCTAssertEqual(channelCount, 1) triggerResponsePromise.succeed(()) @@ -331,7 +350,9 @@ class HTTP2ConnectionTests: XCTestCase { var retryCount = 0 let maxRetries = 1000 while retryCount < maxRetries { - XCTAssertNoThrow(channelCount = try eventLoop.submit { http2Connection.__forTesting_getStreamChannels().count }.wait()) + XCTAssertNoThrow( + channelCount = try eventLoop.submit { http2Connection.__forTesting_getStreamChannels().count }.wait() + ) if channelCount == 0 { break } diff --git a/Tests/AsyncHTTPClientTests/HTTP2IdleHandlerTests.swift b/Tests/AsyncHTTPClientTests/HTTP2IdleHandlerTests.swift index 611e31457..f2b56daa0 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2IdleHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2IdleHandlerTests.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOEmbedded import NIOHTTP2 import XCTest +@testable import AsyncHTTPClient + class HTTP2IdleHandlerTests: XCTestCase { func testReceiveSettingsWithMaxConcurrentStreamSetting() { let delegate = MockHTTP2IdleHandlerDelegate() @@ -26,7 +27,10 @@ class HTTP2IdleHandlerTests: XCTestCase { let embedded = EmbeddedChannel(handlers: [idleHandler]) XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait()) - let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)]))) + let settingsFrame = HTTP2Frame( + streamID: 0, + payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])) + ) XCTAssertEqual(delegate.maxStreams, nil) XCTAssertNoThrow(try embedded.writeInbound(settingsFrame)) XCTAssertEqual(delegate.maxStreams, 10) @@ -41,7 +45,11 @@ class HTTP2IdleHandlerTests: XCTestCase { let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([]))) XCTAssertEqual(delegate.maxStreams, nil) XCTAssertNoThrow(try embedded.writeInbound(settingsFrame)) - XCTAssertEqual(delegate.maxStreams, 100, "Expected to assume 100 maxConcurrentConnection, if no setting was present") + XCTAssertEqual( + delegate.maxStreams, + 100, + "Expected to assume 100 maxConcurrentConnection, if no setting was present" + ) } func testEmptySettingsDontOverwriteMaxConcurrentStreamSetting() { @@ -50,7 +58,10 @@ class HTTP2IdleHandlerTests: XCTestCase { let embedded = EmbeddedChannel(handlers: [idleHandler]) XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait()) - let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)]))) + let settingsFrame = HTTP2Frame( + streamID: 0, + payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])) + ) XCTAssertEqual(delegate.maxStreams, nil) XCTAssertNoThrow(try embedded.writeInbound(settingsFrame)) XCTAssertEqual(delegate.maxStreams, 10) @@ -66,12 +77,18 @@ class HTTP2IdleHandlerTests: XCTestCase { let embedded = EmbeddedChannel(handlers: [idleHandler]) XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait()) - let settingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)]))) + let settingsFrame = HTTP2Frame( + streamID: 0, + payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 10)])) + ) XCTAssertEqual(delegate.maxStreams, nil) XCTAssertNoThrow(try embedded.writeInbound(settingsFrame)) XCTAssertEqual(delegate.maxStreams, 10) - let emptySettings = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 20)]))) + let emptySettings = HTTP2Frame( + streamID: 0, + payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 20)])) + ) XCTAssertNoThrow(try embedded.writeInbound(emptySettings)) XCTAssertEqual(delegate.maxStreams, 20) } @@ -83,7 +100,10 @@ class HTTP2IdleHandlerTests: XCTestCase { XCTAssertNoThrow(try embedded.connect(to: .makeAddressResolvingHost("localhost", port: 0)).wait()) let randomStreamID = HTTP2StreamID((0.., - _ head: HTTPResponseHead) -> EventLoopFuture { + public func didReceiveHead( + task: HTTPClient.Task, + _ head: HTTPResponseHead + ) -> EventLoopFuture { self.eventLoop.assertInEventLoop() self.receivedMessages.append(.head(head)) return self.randoEL.makeSucceededFuture(()) } - func didReceiveBodyPart(task: HTTPClient.Task, - _ buffer: ByteBuffer) -> EventLoopFuture { + func didReceiveBodyPart( + task: HTTPClient.Task, + _ buffer: ByteBuffer + ) -> EventLoopFuture { self.eventLoop.assertInEventLoop() self.receivedMessages.append(.bodyPart(buffer)) return self.randoEL.makeSucceededFuture(()) @@ -250,22 +255,38 @@ class HTTPClientInternalTests: XCTestCase { } } - let request = try Request(url: "http://127.0.0.1:\(server.serverPort)/custom", - body: body) + let request = try Request( + url: "http://127.0.0.1:\(server.serverPort)/custom", + body: body + ) let delegate = Delegate(expectedEventLoop: delegateEL, randomOtherEventLoop: randoEL) - let future = httpClient.execute(request: request, - delegate: delegate, - eventLoop: .init(.testOnly_exact(channelOn: channelEL, - delegateOn: delegateEL))).futureResult - - XCTAssertNoThrow(try server.readInbound()) // .head - XCTAssertNoThrow(try server.readInbound()) // .body - XCTAssertNoThrow(try server.readInbound()) // .end + let future = httpClient.execute( + request: request, + delegate: delegate, + eventLoop: .init( + .testOnly_exact( + channelOn: channelEL, + delegateOn: delegateEL + ) + ) + ).futureResult + + XCTAssertNoThrow(try server.readInbound()) // .head + XCTAssertNoThrow(try server.readInbound()) // .body + XCTAssertNoThrow(try server.readInbound()) // .end // Send 3 parts, but only one should be received until the future is complete - XCTAssertNoThrow(try server.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), - status: .ok, - headers: HTTPHeaders([("Transfer-Encoding", "chunked")]))))) + XCTAssertNoThrow( + try server.writeOutbound( + .head( + .init( + version: .init(major: 1, minor: 1), + status: .ok, + headers: HTTPHeaders([("Transfer-Encoding", "chunked")]) + ) + ) + ) + ) let buffer = ByteBuffer(string: "1234") XCTAssertNoThrow(try server.writeOutbound(.body(.byteBuffer(buffer)))) XCTAssertNoThrow(try server.writeOutbound(.end(nil))) @@ -297,7 +318,7 @@ class HTTPClientInternalTests: XCTestCase { switch sentMessages.dropFirst(3).first { case .some(.sentRequest): - () // OK + () // OK default: XCTFail("wrong message") } @@ -335,7 +356,10 @@ class HTTPClientInternalTests: XCTestCase { let el = group.next() let req1 = client.execute(request: request, eventLoop: .delegate(on: el)) let req2 = client.execute(request: request, eventLoop: .delegateAndChannel(on: el)) - let req3 = client.execute(request: request, eventLoop: .init(.testOnly_exact(channelOn: el, delegateOn: el))) + let req3 = client.execute( + request: request, + eventLoop: .init(.testOnly_exact(channelOn: el, delegateOn: el)) + ) XCTAssert(req1.eventLoop === el) XCTAssert(req2.eventLoop === el) XCTAssert(req3.eventLoop === el) @@ -354,8 +378,8 @@ class HTTPClientInternalTests: XCTestCase { _ = httpClient.get(url: "http://localhost:\(server.serverPort)/wait") - XCTAssertNoThrow(try server.readInbound()) // .head - XCTAssertNoThrow(try server.readInbound()) // .end + XCTAssertNoThrow(try server.readInbound()) // .head + XCTAssertNoThrow(try server.readInbound()) // .end do { try httpClient.syncShutdown(requiresCleanClose: true) @@ -395,10 +419,16 @@ class HTTPClientInternalTests: XCTestCase { } } let request = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/post", method: .POST, body: body) - let response = httpClient.execute(request: request, - delegate: ResponseAccumulator(request: request), - eventLoop: HTTPClient.EventLoopPreference(.testOnly_exact(channelOn: el2, - delegateOn: el1))) + let response = httpClient.execute( + request: request, + delegate: ResponseAccumulator(request: request), + eventLoop: HTTPClient.EventLoopPreference( + .testOnly_exact( + channelOn: el2, + delegateOn: el1 + ) + ) + ) XCTAssert(el1 === response.eventLoop) XCTAssertNoThrow(try response.wait()) } @@ -419,7 +449,11 @@ class HTTPClientInternalTests: XCTestCase { let request = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)//get") let delegate = ResponseAccumulator(request: request) - let task = client.execute(request: request, delegate: delegate, eventLoop: .init(.testOnly_exact(channelOn: el1, delegateOn: el2))) + let task = client.execute( + request: request, + delegate: delegate, + eventLoop: .init(.testOnly_exact(channelOn: el1, delegateOn: el2)) + ) XCTAssertTrue(task.futureResult.eventLoop === el2) XCTAssertNoThrow(try task.wait()) } @@ -460,7 +494,11 @@ class HTTPClientInternalTests: XCTestCase { let request = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/get") let delegate = TestDelegate(expectedEL: el1) XCTAssertNoThrow(try httpBin.shutdown()) - let task = client.execute(request: request, delegate: delegate, eventLoop: .init(.testOnly_exact(channelOn: el2, delegateOn: el1))) + let task = client.execute( + request: request, + delegate: delegate, + eventLoop: .init(.testOnly_exact(channelOn: el2, delegateOn: el1)) + ) XCTAssertThrowsError(try task.wait()) XCTAssertTrue(delegate.receivedError) } @@ -493,10 +531,13 @@ class HTTPClientInternalTests: XCTestCase { let request6 = try Request(url: "https://127.0.0.1") XCTAssertEqual(request6.deconstructedURL.scheme, .https) - XCTAssertEqual(request6.deconstructedURL.connectionTarget, .ipAddress( - serialization: "127.0.0.1", - address: try! SocketAddress(ipAddress: "127.0.0.1", port: 443) - )) + XCTAssertEqual( + request6.deconstructedURL.connectionTarget, + .ipAddress( + serialization: "127.0.0.1", + address: try! SocketAddress(ipAddress: "127.0.0.1", port: 443) + ) + ) XCTAssertEqual(request6.deconstructedURL.uri, "/") let request7 = try Request(url: "https://0x7F.1:9999") @@ -506,18 +547,24 @@ class HTTPClientInternalTests: XCTestCase { let request8 = try Request(url: "http://[::1]") XCTAssertEqual(request8.deconstructedURL.scheme, .http) - XCTAssertEqual(request8.deconstructedURL.connectionTarget, .ipAddress( - serialization: "[::1]", - address: try! SocketAddress(ipAddress: "::1", port: 80) - )) + XCTAssertEqual( + request8.deconstructedURL.connectionTarget, + .ipAddress( + serialization: "[::1]", + address: try! SocketAddress(ipAddress: "::1", port: 80) + ) + ) XCTAssertEqual(request8.deconstructedURL.uri, "/") let request9 = try Request(url: "http://[763e:61d9::6ACA:3100:6274]:4242/foo/bar?baz") XCTAssertEqual(request9.deconstructedURL.scheme, .http) - XCTAssertEqual(request9.deconstructedURL.connectionTarget, .ipAddress( - serialization: "[763e:61d9::6ACA:3100:6274]", - address: try! SocketAddress(ipAddress: "763e:61d9::6aca:3100:6274", port: 4242) - )) + XCTAssertEqual( + request9.deconstructedURL.connectionTarget, + .ipAddress( + serialization: "[763e:61d9::6ACA:3100:6274]", + address: try! SocketAddress(ipAddress: "763e:61d9::6aca:3100:6274", port: 4242) + ) + ) XCTAssertEqual(request9.deconstructedURL.uri, "/foo/bar?baz") // Some systems have quirks in their implementations of 'ntop' which cause them to write @@ -526,18 +573,24 @@ class HTTPClientInternalTests: XCTestCase { // so the serialization must be kept verbatim as it was given in the request. let request10 = try Request(url: "http://[::c0a8:1]:4242/foo/bar?baz") XCTAssertEqual(request10.deconstructedURL.scheme, .http) - XCTAssertEqual(request10.deconstructedURL.connectionTarget, .ipAddress( - serialization: "[::c0a8:1]", - address: try! SocketAddress(ipAddress: "::c0a8:1", port: 4242) - )) + XCTAssertEqual( + request10.deconstructedURL.connectionTarget, + .ipAddress( + serialization: "[::c0a8:1]", + address: try! SocketAddress(ipAddress: "::c0a8:1", port: 4242) + ) + ) XCTAssertEqual(request10.deconstructedURL.uri, "/foo/bar?baz") let request11 = try Request(url: "http://[::192.168.0.1]:4242/foo/bar?baz") XCTAssertEqual(request11.deconstructedURL.scheme, .http) - XCTAssertEqual(request11.deconstructedURL.connectionTarget, .ipAddress( - serialization: "[::192.168.0.1]", - address: try! SocketAddress(ipAddress: "::192.168.0.1", port: 4242) - )) + XCTAssertEqual( + request11.deconstructedURL.connectionTarget, + .ipAddress( + serialization: "[::192.168.0.1]", + address: try! SocketAddress(ipAddress: "::192.168.0.1", port: 4242) + ) + ) XCTAssertEqual(request11.deconstructedURL.uri, "/foo/bar?baz") } @@ -566,7 +619,7 @@ class HTTPClientInternalTests: XCTestCase { } // Empty collection. do { - let elements: Array = [] + let elements: [Int] = [] XCTAssertTrue(elements.hasSuffix([])) XCTAssertFalse(elements.hasSuffix([0])) XCTAssertFalse(elements.hasSuffix([42])) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift index 3bbac632b..4c2d24dc4 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift @@ -12,10 +12,6 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient -#if canImport(Network) -import Network -#endif import NIOConcurrencyHelpers import NIOCore import NIOPosix @@ -23,6 +19,12 @@ import NIOSSL import NIOTransportServices import XCTest +@testable import AsyncHTTPClient + +#if canImport(Network) +import Network +#endif + class HTTPClientNIOTSTests: XCTestCase { var clientGroup: EventLoopGroup! @@ -57,8 +59,10 @@ class HTTPClientNIOTSTests: XCTestCase { let httpBin = HTTPBin(.http1_1(ssl: true)) let config = HTTPClient.Configuration() .enableFastFailureModeForTesting() - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: config) + let httpClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: config + ) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) @@ -69,8 +73,10 @@ class HTTPClientNIOTSTests: XCTestCase { _ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait() XCTFail("This should have failed") } catch let error as HTTPClient.NWTLSError { - XCTAssert(error.status == errSSLHandshakeFail || error.status == errSSLBadCert, - "unexpected NWTLSError with status \(error.status)") + XCTAssert( + error.status == errSSLHandshakeFail || error.status == errSSLBadCert, + "unexpected NWTLSError with status \(error.status)" + ) } catch { XCTFail("Error should have been NWTLSError not \(type(of: error))") } @@ -86,8 +92,10 @@ class HTTPClientNIOTSTests: XCTestCase { let config = HTTPClient.Configuration() .enableFastFailureModeForTesting() - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: config) + let httpClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: config + ) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -106,9 +114,15 @@ class HTTPClientNIOTSTests: XCTestCase { guard isTestingNIOTS() else { return } #if canImport(Network) let httpBin = HTTPBin(.http1_1(ssl: false)) - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(timeout: .init(connect: .milliseconds(100), - read: .milliseconds(100)))) + let httpClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init( + timeout: .init( + connect: .milliseconds(100), + read: .milliseconds(100) + ) + ) + ) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientRequestTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientRequestTests.swift index 05e22f2d2..a92d129a4 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientRequestTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientRequestTests.swift @@ -13,10 +13,11 @@ //===----------------------------------------------------------------------===// import Algorithms -@testable import AsyncHTTPClient import NIOCore import XCTest +@testable import AsyncHTTPClient + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) class HTTPClientRequestTests: XCTestCase { private typealias Request = HTTPClientRequest @@ -27,31 +28,40 @@ class HTTPClientRequestTests: XCTestCase { XCTAsyncTest { var request = Request(url: "https://example.com/get") request.headers = [ - "custom-header": "custom-header-value", + "custom-header": "custom-header-value" ] var preparedRequest: PreparedRequest? XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .https, - connectionTarget: .domain(name: "example.com", port: 443), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .GET, - uri: "/get", - headers: [ - "host": "example.com", - "custom-header": "custom-header-value", - ] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(0) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .https, + connectionTarget: .domain(name: "example.com", port: 443), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .GET, + uri: "/get", + headers: [ + "host": "example.com", + "custom-header": "custom-header-value", + ] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(0) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, ByteBuffer()) } @@ -76,22 +86,31 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .unix, - connectionTarget: .unixSocket(path: "/some_path"), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .GET, - uri: "/", - headers: ["custom-header": "custom-value"] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(0) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .unix, + connectionTarget: .unixSocket(path: "/some_path"), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .GET, + uri: "/", + headers: ["custom-header": "custom-value"] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(0) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, ByteBuffer()) } @@ -105,22 +124,31 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .httpUnix, - connectionTarget: .unixSocket(path: "/example/folder.sock"), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .GET, - uri: "/some_path", - headers: ["custom-header": "custom-value"] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(0) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .httpUnix, + connectionTarget: .unixSocket(path: "/example/folder.sock"), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .GET, + uri: "/some_path", + headers: ["custom-header": "custom-value"] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(0) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, ByteBuffer()) } @@ -134,22 +162,31 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .httpsUnix, - connectionTarget: .unixSocket(path: "/example/folder.sock"), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .GET, - uri: "/some_path", - headers: ["custom-header": "custom-value"] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(0) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .httpsUnix, + connectionTarget: .unixSocket(path: "/example/folder.sock"), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .GET, + uri: "/some_path", + headers: ["custom-header": "custom-value"] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(0) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, ByteBuffer()) } @@ -162,22 +199,31 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .https, - connectionTarget: .domain(name: "example.com", port: 443), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .GET, - uri: "/get", - headers: ["host": "example.com"] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(0) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .https, + connectionTarget: .domain(name: "example.com", port: 443), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .GET, + uri: "/get", + headers: ["host": "example.com"] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(0) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, ByteBuffer()) } @@ -191,25 +237,34 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .http, - connectionTarget: .domain(name: "example.com", port: 80), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .POST, - uri: "/post", - headers: [ - "host": "example.com", - "content-length": "0", - ] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(0) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .http, + connectionTarget: .domain(name: "example.com", port: 80), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .POST, + uri: "/post", + headers: [ + "host": "example.com", + "content-length": "0", + ] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(0) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, ByteBuffer()) @@ -225,25 +280,34 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .http, - connectionTarget: .domain(name: "example.com", port: 80), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .POST, - uri: "/post", - headers: [ - "host": "example.com", - "content-length": "0", - ] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(0) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .http, + connectionTarget: .domain(name: "example.com", port: 80), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .POST, + uri: "/post", + headers: [ + "host": "example.com", + "content-length": "0", + ] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(0) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, ByteBuffer()) @@ -259,25 +323,34 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .http, - connectionTarget: .domain(name: "example.com", port: 80), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .POST, - uri: "/post", - headers: [ - "host": "example.com", - "content-length": "9", - ] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(9) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .http, + connectionTarget: .domain(name: "example.com", port: 80), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .POST, + uri: "/post", + headers: [ + "host": "example.com", + "content-length": "9", + ] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(9) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, .init(string: "post body")) } @@ -293,25 +366,34 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .http, - connectionTarget: .domain(name: "example.com", port: 80), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .POST, - uri: "/post", - headers: [ - "host": "example.com", - "transfer-encoding": "chunked", - ] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .stream - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .http, + connectionTarget: .domain(name: "example.com", port: 80), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .POST, + uri: "/post", + headers: [ + "host": "example.com", + "transfer-encoding": "chunked", + ] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .stream + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, .init(string: "post body")) } @@ -328,25 +410,34 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .http, - connectionTarget: .domain(name: "example.com", port: 80), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .POST, - uri: "/post", - headers: [ - "host": "example.com", - "content-length": "9", - ] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(9) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .http, + connectionTarget: .domain(name: "example.com", port: 80), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .POST, + uri: "/post", + headers: [ + "host": "example.com", + "content-length": "9", + ] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(9) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, .init(string: "post body")) } @@ -362,25 +453,34 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .http, - connectionTarget: .domain(name: "example.com", port: 80), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .POST, - uri: "/post", - headers: [ - "host": "example.com", - "content-length": "9", - ] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(9) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .http, + connectionTarget: .domain(name: "example.com", port: 80), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .POST, + uri: "/post", + headers: [ + "host": "example.com", + "content-length": "9", + ] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(9) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, .init(string: "post body")) } @@ -401,25 +501,34 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .http, - connectionTarget: .domain(name: "example.com", port: 80), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .POST, - uri: "/post", - headers: [ - "host": "example.com", - "transfer-encoding": "chunked", - ] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .stream - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .http, + connectionTarget: .domain(name: "example.com", port: 80), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .POST, + uri: "/post", + headers: [ + "host": "example.com", + "transfer-encoding": "chunked", + ] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .stream + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, .init(string: "post body")) } @@ -440,25 +549,34 @@ class HTTPClientRequestTests: XCTestCase { XCTAssertNoThrow(preparedRequest = try PreparedRequest(request)) guard let preparedRequest = preparedRequest else { return } - XCTAssertEqual(preparedRequest.poolKey, .init( - scheme: .http, - connectionTarget: .domain(name: "example.com", port: 80), - tlsConfiguration: nil, - serverNameIndicatorOverride: nil - )) - XCTAssertEqual(preparedRequest.head, .init( - version: .http1_1, - method: .POST, - uri: "/post", - headers: [ - "host": "example.com", - "content-length": "9", - ] - )) - XCTAssertEqual(preparedRequest.requestFramingMetadata, .init( - connectionClose: false, - body: .fixedSize(9) - )) + XCTAssertEqual( + preparedRequest.poolKey, + .init( + scheme: .http, + connectionTarget: .domain(name: "example.com", port: 80), + tlsConfiguration: nil, + serverNameIndicatorOverride: nil + ) + ) + XCTAssertEqual( + preparedRequest.head, + .init( + version: .http1_1, + method: .POST, + uri: "/post", + headers: [ + "host": "example.com", + "content-length": "9", + ] + ) + ) + XCTAssertEqual( + preparedRequest.requestFramingMetadata, + .init( + connectionClose: false, + body: .fixedSize(9) + ) + ) guard let buffer = await XCTAssertNoThrowWithResult(try await preparedRequest.body.read()) else { return } XCTAssertEqual(buffer, .init(string: "post body")) } @@ -466,9 +584,9 @@ class HTTPClientRequestTests: XCTestCase { func testChunkingRandomAccessCollection() async throws { let body = try await HTTPClientRequest.Body.bytes( - Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + - Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) + - Array(repeating: 2, count: bagOfBytesToByteBufferConversionChunkSize) + Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + + Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) + + Array(repeating: 2, count: bagOfBytesToByteBufferConversionChunkSize) ).collect() let expectedChunks = [ @@ -482,11 +600,9 @@ class HTTPClientRequestTests: XCTestCase { func testChunkingCollection() async throws { let body = try await HTTPClientRequest.Body.bytes( - ( - String(repeating: "0", count: bagOfBytesToByteBufferConversionChunkSize) + - String(repeating: "1", count: bagOfBytesToByteBufferConversionChunkSize) + - String(repeating: "2", count: bagOfBytesToByteBufferConversionChunkSize) - ).utf8, + (String(repeating: "0", count: bagOfBytesToByteBufferConversionChunkSize) + + String(repeating: "1", count: bagOfBytesToByteBufferConversionChunkSize) + + String(repeating: "2", count: bagOfBytesToByteBufferConversionChunkSize)).utf8, length: .known(Int64(bagOfBytesToByteBufferConversionChunkSize * 3)) ).collect() @@ -503,8 +619,8 @@ class HTTPClientRequestTests: XCTestCase { let bagOfBytesToByteBufferConversionChunkSize = 8 let body = try await HTTPClientRequest.Body._bytes( AnySequence( - Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + - Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) + Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + + Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) ), length: .known(Int64(bagOfBytesToByteBufferConversionChunkSize * 3)), bagOfBytesToByteBufferConversionChunkSize: bagOfBytesToByteBufferConversionChunkSize, @@ -521,9 +637,9 @@ class HTTPClientRequestTests: XCTestCase { func testChunkingSequenceFastPath() async throws { func makeBytes() -> some Sequence & Sendable { - Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + - Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) + - Array(repeating: 2, count: bagOfBytesToByteBufferConversionChunkSize) + Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + + Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) + + Array(repeating: 2, count: bagOfBytesToByteBufferConversionChunkSize) } let body = try await HTTPClientRequest.Body.bytes( makeBytes(), @@ -534,7 +650,7 @@ class HTTPClientRequestTests: XCTestCase { firstChunk.writeImmutableBuffer(ByteBuffer(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize)) firstChunk.writeImmutableBuffer(ByteBuffer(repeating: 2, count: bagOfBytesToByteBufferConversionChunkSize)) let expectedChunks = [ - firstChunk, + firstChunk ] XCTAssertEqual(body, expectedChunks) @@ -544,9 +660,9 @@ class HTTPClientRequestTests: XCTestCase { let bagOfBytesToByteBufferConversionChunkSize = 8 let byteBufferMaxSize = 16 func makeBytes() -> some Sequence & Sendable { - Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + - Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) + - Array(repeating: 2, count: bagOfBytesToByteBufferConversionChunkSize) + Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + + Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) + + Array(repeating: 2, count: bagOfBytesToByteBufferConversionChunkSize) } let body = try await HTTPClientRequest.Body._bytes( makeBytes(), @@ -568,9 +684,9 @@ class HTTPClientRequestTests: XCTestCase { func testBodyStringChunking() throws { let body = try HTTPClient.Body.string( - String(repeating: "0", count: bagOfBytesToByteBufferConversionChunkSize) + - String(repeating: "1", count: bagOfBytesToByteBufferConversionChunkSize) + - String(repeating: "2", count: bagOfBytesToByteBufferConversionChunkSize) + String(repeating: "0", count: bagOfBytesToByteBufferConversionChunkSize) + + String(repeating: "1", count: bagOfBytesToByteBufferConversionChunkSize) + + String(repeating: "2", count: bagOfBytesToByteBufferConversionChunkSize) ).collect().wait() let expectedChunks = [ @@ -584,9 +700,9 @@ class HTTPClientRequestTests: XCTestCase { func testBodyChunkingRandomAccessCollection() throws { let body = try HTTPClient.Body.bytes( - Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + - Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) + - Array(repeating: 2, count: bagOfBytesToByteBufferConversionChunkSize) + Array(repeating: 0, count: bagOfBytesToByteBufferConversionChunkSize) + + Array(repeating: 1, count: bagOfBytesToByteBufferConversionChunkSize) + + Array(repeating: 2, count: bagOfBytesToByteBufferConversionChunkSize) ).collect().wait() let expectedChunks = [ @@ -642,7 +758,8 @@ extension Optional where Wrapped == HTTPClientRequest.Prepared.Body { case .sequence(let announcedLength, _, let generate): let buffer = generate(ByteBufferAllocator()) if case .known(let announcedLength) = announcedLength, - announcedLength != Int64(buffer.readableBytes) { + announcedLength != Int64(buffer.readableBytes) + { throw LengthMismatch(announcedLength: announcedLength, actualLength: Int64(buffer.readableBytes)) } return buffer @@ -652,8 +769,12 @@ extension Optional where Wrapped == HTTPClientRequest.Prepared.Body { accumulatedBuffer.writeBuffer(&buffer) } if case .known(let announcedLength) = announcedLength, - announcedLength != Int64(accumulatedBuffer.readableBytes) { - throw LengthMismatch(announcedLength: announcedLength, actualLength: Int64(accumulatedBuffer.readableBytes)) + announcedLength != Int64(accumulatedBuffer.readableBytes) + { + throw LengthMismatch( + announcedLength: announcedLength, + actualLength: Int64(accumulatedBuffer.readableBytes) + ) } return accumulatedBuffer } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift index 2c6c9afac..fd2b7ee4e 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientResponseTests.swift @@ -12,25 +12,38 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import XCTest +@testable import AsyncHTTPClient + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) final class HTTPClientResponseTests: XCTestCase { func testSimpleResponse() { - let response = HTTPClientResponse.expectedContentLength(requestMethod: .GET, headers: ["content-length": "1025"], status: .ok) + let response = HTTPClientResponse.expectedContentLength( + requestMethod: .GET, + headers: ["content-length": "1025"], + status: .ok + ) XCTAssertEqual(response, 1025) } func testSimpleResponseNotModified() { - let response = HTTPClientResponse.expectedContentLength(requestMethod: .GET, headers: ["content-length": "1025"], status: .notModified) + let response = HTTPClientResponse.expectedContentLength( + requestMethod: .GET, + headers: ["content-length": "1025"], + status: .notModified + ) XCTAssertEqual(response, 0) } func testSimpleResponseHeadRequestMethod() { - let response = HTTPClientResponse.expectedContentLength(requestMethod: .HEAD, headers: ["content-length": "1025"], status: .ok) + let response = HTTPClientResponse.expectedContentLength( + requestMethod: .HEAD, + headers: ["content-length": "1025"], + status: .ok + ) XCTAssertEqual(response, 0) } @@ -40,7 +53,11 @@ final class HTTPClientResponseTests: XCTestCase { } func testResponseInvalidInteger() { - let response = HTTPClientResponse.expectedContentLength(requestMethod: .GET, headers: ["content-length": "none"], status: .ok) + let response = HTTPClientResponse.expectedContentLength( + requestMethod: .GET, + headers: ["content-length": "none"], + status: .ok + ) XCTAssertEqual(response, nil) } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index e8d6976c5..ad9fcfe98 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Atomics import Foundation import Logging @@ -28,6 +27,9 @@ import NIOSSL import NIOTLS import NIOTransportServices import XCTest + +@testable import AsyncHTTPClient + #if canImport(xlocale) import xlocale #elseif canImport(locale_h) @@ -52,7 +54,8 @@ func isTestingNIOTS() -> Bool { func getDefaultEventLoopGroup(numberOfThreads: Int) -> EventLoopGroup { #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), - isTestingNIOTS() { + isTestingNIOTS() + { return NIOTSEventLoopGroup(loopCount: numberOfThreads, defaultQoS: .default) } #endif @@ -144,7 +147,7 @@ class CountingDelegate: HTTPClientResponseDelegate { } func didFinishRequest(task: HTTPClient.Task) throws -> Int { - return self.count + self.count } } @@ -219,8 +222,8 @@ enum TemporaryFileHelpers { } else { return "/tmp" } - #endif // os - #endif // targetEnvironment + #endif // os + #endif // targetEnvironment } private static func openTemporaryFile() -> (CInt, String) { @@ -240,8 +243,10 @@ enum TemporaryFileHelpers { /// /// If the temporary directory is too long to store a UNIX domain socket path, it will `chdir` into the temporary /// directory and return a short-enough path. The iOS simulator is known to have too long paths. - internal static func withTemporaryUnixDomainSocketPathName(directory: String = temporaryDirectory, - _ body: (String) throws -> T) throws -> T { + internal static func withTemporaryUnixDomainSocketPathName( + directory: String = temporaryDirectory, + _ body: (String) throws -> T + ) throws -> T { // this is racy but we're trying to create the shortest possible path so we can't add a directory... let (fd, path) = self.openTemporaryFile() close(fd) @@ -256,10 +261,14 @@ enum TemporaryFileHelpers { shortEnoughPath = path restoreSavedCWD = false } catch SocketAddressError.unixDomainSocketPathTooLong { - FileManager.default.changeCurrentDirectoryPath(URL(fileURLWithPath: path).deletingLastPathComponent().absoluteString) + FileManager.default.changeCurrentDirectoryPath( + URL(fileURLWithPath: path).deletingLastPathComponent().absoluteString + ) shortEnoughPath = URL(fileURLWithPath: path).lastPathComponent restoreSavedCWD = true - print("WARNING: Path '\(path)' could not be used as UNIX domain socket path, using chdir & '\(shortEnoughPath)'") + print( + "WARNING: Path '\(path)' could not be used as UNIX domain socket path, using chdir & '\(shortEnoughPath)'" + ) } defer { if FileManager.default.fileExists(atPath: path) { @@ -307,11 +316,11 @@ enum TemporaryFileHelpers { } internal static func fileSize(path: String) throws -> Int? { - return try FileManager.default.attributesOfItem(atPath: path)[.size] as? Int + try FileManager.default.attributesOfItem(atPath: path)[.size] as? Int } internal static func fileExists(path: String) -> Bool { - return FileManager.default.fileExists(atPath: path) + FileManager.default.fileExists(atPath: path) } } @@ -324,9 +333,11 @@ enum TestTLS { ) } -internal final class HTTPBin where +internal final class HTTPBin +where RequestHandler.InboundIn == HTTPServerRequestPart, - RequestHandler.OutboundOut == HTTPServerResponsePart { + RequestHandler.OutboundOut == HTTPServerResponsePart +{ enum BindTarget { case unixDomainSocket(String) case localhostIPv4RandomPort @@ -393,19 +404,19 @@ internal final class HTTPBin where private let activeConnCounterHandler: ConnectionsCountHandler var activeConnections: Int { - return self.activeConnCounterHandler.currentlyActiveConnections + self.activeConnCounterHandler.currentlyActiveConnections } var createdConnections: Int { - return self.activeConnCounterHandler.createdConnections + self.activeConnCounterHandler.createdConnections } var port: Int { - return Int(self.serverChannel.localAddress!.port!) + Int(self.serverChannel.localAddress!.port!) } var socketAddress: SocketAddress { - return self.serverChannel.localAddress! + self.serverChannel.localAddress! } var baseURL: String { @@ -464,7 +475,10 @@ internal final class HTTPBin where self.serverChannel = try! ServerBootstrap(group: self.group) .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) - .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), value: reusePort ? 1 : 0) + .serverChannelOption( + ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), + value: reusePort ? 1 : 0 + ) .serverChannelInitializer { channel in channel.pipeline.addHandler(self.activeConnCounterHandler) }.childChannelInitializer { channel in @@ -673,7 +687,11 @@ final class HTTPProxySimulator: ChannelInboundHandler, RemovableChannelHandler { init(promise: EventLoopPromise, expectedAuthorization: String?) { self.promise = promise self.expectedAuthorization = expectedAuthorization - self.head = HTTPResponseHead(version: .init(major: 1, minor: 1), status: .ok, headers: .init([("Content-Length", "0")])) + self.head = HTTPResponseHead( + version: .init(major: 1, minor: 1), + status: .ok, + headers: .init([("Content-Length", "0")]) + ) } func channelRead(context: ChannelHandlerContext, data: NIOAny) { @@ -687,7 +705,8 @@ final class HTTPProxySimulator: ChannelInboundHandler, RemovableChannelHandler { if let expectedAuthorization = self.expectedAuthorization { guard let authorization = head.headers["proxy-authorization"].first, - expectedAuthorization == authorization else { + expectedAuthorization == authorization + else { self.head.status = .proxyAuthenticationRequired return } @@ -712,7 +731,11 @@ internal struct HTTPResponseBuilder { var head: HTTPResponseHead var body: ByteBuffer? - init(_ version: HTTPVersion = HTTPVersion(major: 1, minor: 1), status: HTTPResponseStatus, headers: HTTPHeaders = HTTPHeaders()) { + init( + _ version: HTTPVersion = HTTPVersion(major: 1, minor: 1), + status: HTTPResponseStatus, + headers: HTTPHeaders = HTTPHeaders() + ) { self.head = HTTPResponseHead(version: version, status: status, headers: headers) } @@ -764,8 +787,10 @@ internal final class HTTPBinHandler: ChannelInboundHandler { for header in head.headers { let needle = "x-send-back-header-" if header.name.lowercased().starts(with: needle) { - self.responseHeaders.add(name: String(header.name.dropFirst(needle.count)), - value: header.value) + self.responseHeaders.add( + name: String(header.name.dropFirst(needle.count)), + value: header.value + ) } } } @@ -778,7 +803,12 @@ internal final class HTTPBinHandler: ChannelInboundHandler { headers = HTTPHeaders() } - context.write(wrapOutboundOut(.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok, headers: headers))), promise: nil) + context.write( + wrapOutboundOut( + .head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok, headers: headers)) + ), + promise: nil + ) for i in 0..<10 { let msg = "id: \(i)" var buf = context.channel.allocator.buffer(capacity: msg.count) @@ -793,7 +823,12 @@ internal final class HTTPBinHandler: ChannelInboundHandler { // This tests receiving chunks very fast: please do not insert delays here! let headers = HTTPHeaders([("Transfer-Encoding", "chunked")]) - context.write(self.wrapOutboundOut(.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok, headers: headers))), promise: nil) + context.write( + self.wrapOutboundOut( + .head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok, headers: headers)) + ), + promise: nil + ) for i in 0..<10 { let msg = "id: \(i)" var buf = context.channel.allocator.buffer(capacity: msg.count) @@ -808,7 +843,12 @@ internal final class HTTPBinHandler: ChannelInboundHandler { // This tests receiving a lot of tiny chunks: they must all be sent in a single flush or the test doesn't work. let headers = HTTPHeaders([("Transfer-Encoding", "chunked")]) - context.write(self.wrapOutboundOut(.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok, headers: headers))), promise: nil) + context.write( + self.wrapOutboundOut( + .head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok, headers: headers)) + ), + promise: nil + ) let message = ByteBuffer(integer: UInt8(ascii: "a")) // This number (10k) is load-bearing and a bit magic: it has been experimentally verified as being sufficient to blow the stack @@ -928,9 +968,12 @@ internal final class HTTPBinHandler: ChannelInboundHandler { context.close(promise: nil) return case "/custom": - context.writeAndFlush(wrapOutboundOut(.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok))), promise: nil) + context.writeAndFlush( + wrapOutboundOut(.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok))), + promise: nil + ) return - case "/events/10/1": // TODO: parse path + case "/events/10/1": // TODO: parse path self.writeEvents(context: context) return case "/events/10/content-length": @@ -954,10 +997,20 @@ internal final class HTTPBinHandler: ChannelInboundHandler { 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) + 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.write( + wrapOutboundOut( + .head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .notFound)) + ), + promise: nil + ) context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) return } @@ -976,18 +1029,26 @@ internal final class HTTPBinHandler: ChannelInboundHandler { response.head.headers.add(contentsOf: self.responseHeaders) context.write(wrapOutboundOut(.head(response.head)), promise: nil) if let body = response.body { - let requestInfo = RequestInfo(data: String(buffer: body), - requestNumber: self.requestId, - connectionNumber: self.connectionID) - let responseBody = try! JSONEncoder().encodeAsByteBuffer(requestInfo, - allocator: context.channel.allocator) + let requestInfo = RequestInfo( + data: String(buffer: body), + requestNumber: self.requestId, + connectionNumber: self.connectionID + ) + let responseBody = try! JSONEncoder().encodeAsByteBuffer( + requestInfo, + allocator: context.channel.allocator + ) context.write(wrapOutboundOut(.body(.byteBuffer(responseBody))), promise: nil) } else { - let requestInfo = RequestInfo(data: "", - requestNumber: self.requestId, - connectionNumber: self.connectionID) - let responseBody = try! JSONEncoder().encodeAsByteBuffer(requestInfo, - allocator: context.channel.allocator) + let requestInfo = RequestInfo( + data: "", + requestNumber: self.requestId, + connectionNumber: self.connectionID + ) + let responseBody = try! JSONEncoder().encodeAsByteBuffer( + requestInfo, + allocator: context.channel.allocator + ) context.write(wrapOutboundOut(.body(.byteBuffer(responseBody))), promise: nil) } context.eventLoop.scheduleTask(in: self.delay) { @@ -1000,8 +1061,9 @@ internal final class HTTPBinHandler: ChannelInboundHandler { self.isServingRequest = false switch result { case .success: - if self.responseHeaders[canonicalForm: "X-Close-Connection"].contains("true") || - self.shouldClose { + if self.responseHeaders[canonicalForm: "X-Close-Connection"].contains("true") + || self.shouldClose + { context.close(promise: nil) } case .failure(let error): @@ -1170,7 +1232,7 @@ struct CollectEverythingLogHandler: LogHandler { var allEntries: [Entry] { get { - return self.lock.withLock { self.logs } + self.lock.withLock { self.logs } } set { self.lock.withLock { self.logs = newValue } @@ -1179,9 +1241,13 @@ struct CollectEverythingLogHandler: LogHandler { func append(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?) { self.lock.withLock { - self.logs.append(Entry(level: level, - message: message.description, - metadata: metadata?.mapValues { $0.description } ?? [:])) + self.logs.append( + Entry( + level: level, + message: message.description, + metadata: metadata?.mapValues { $0.description } ?? [:] + ) + ) } } } @@ -1190,16 +1256,20 @@ struct CollectEverythingLogHandler: LogHandler { self.logStore = logStore } - func log(level: Logger.Level, - message: Logger.Message, - metadata: Logger.Metadata?, - file: String, function: String, line: UInt) { + func log( + level: Logger.Level, + message: Logger.Message, + metadata: Logger.Metadata?, + file: String, + function: String, + line: UInt + ) { self.logStore.append(level: level, message: message, metadata: self.metadata.merging(metadata ?? [:]) { $1 }) } subscript(metadataKey key: String) -> Logger.Metadata.Value? { get { - return self.metadata[key] + self.metadata[key] } set { self.metadata[key] = newValue @@ -1355,7 +1425,10 @@ class HTTPEchoHandler: ChannelInboundHandler { let request = self.unwrapInboundIn(data) switch request { case .head(let requestHead): - context.writeAndFlush(self.wrapOutboundOut(.head(.init(version: .http1_1, status: .ok, headers: requestHead.headers))), promise: nil) + context.writeAndFlush( + self.wrapOutboundOut(.head(.init(version: .http1_1, status: .ok, headers: requestHead.headers))), + promise: nil + ) case .body(let bytes): context.writeAndFlush(self.wrapOutboundOut(.body(.byteBuffer(bytes))), promise: nil) case .end: @@ -1374,7 +1447,10 @@ final class HTTPEchoHeaders: ChannelInboundHandler { let request = self.unwrapInboundIn(data) switch request { case .head(let requestHead): - context.writeAndFlush(self.wrapOutboundOut(.head(.init(version: .http1_1, status: .ok, headers: requestHead.headers))), promise: nil) + context.writeAndFlush( + self.wrapOutboundOut(.head(.init(version: .http1_1, status: .ok, headers: requestHead.headers))), + promise: nil + ) case .body: break case .end: @@ -1410,7 +1486,10 @@ final class HTTP200DelayedHandler: ChannelInboundHandler { self.pendingBodyParts = pendingBodyParts - 1 } else { self.pendingBodyParts = nil - context.writeAndFlush(self.wrapOutboundOut(.head(.init(version: .http1_1, status: .ok))), promise: nil) + context.writeAndFlush( + self.wrapOutboundOut(.head(.init(version: .http1_1, status: .ok))), + promise: nil + ) context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) } } @@ -1421,51 +1500,51 @@ final class HTTP200DelayedHandler: ChannelInboundHandler { } private let cert = """ ------BEGIN CERTIFICATE----- -MIICmDCCAYACCQCPC8JDqMh1zzANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJ1 -czAgFw0xODEwMzExNTU1MjJaGA8yMTE4MTAwNzE1NTUyMlowDTELMAkGA1UEBhMC -dXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiC+TGmbSP/nWWN1tj -yNfnWCU5ATjtIOfdtP6ycx8JSeqkvyNXG21kNUn14jTTU8BglGL2hfVpCbMisUdb -d3LpP8unSsvlOWwORFOViSy4YljSNM/FNoMtavuITA/sEELYgjWkz2o/uHPZHud9 -+JQwGJgqIlMa3mr2IaaUZlWN3D1u88bzJYhpt3YyxRy9+OEoOKy36KdWwhKzV3S8 -kXb0Y1GbAo68jJ9RfzeLy290mIs9qG2y1CNXWO6sxf6B//LaalizZiCfzYAVKcNR -9oNYsEJc5KB/+DsAGTzR7mL+oiU4h/vwVb2GTDat5C+PFGi6j1ujxYTRPO538ljg -dslnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFYhA7sw8odOsRO8/DUklBOjPnmn -a078oSumgPXXw6AgcoAJv/Qthjo6CCEtrjYfcA9jaBw9/Tii7mDmqDRS5c9ZPL8+ -NEPdHjFCFBOEvlL6uHOgw0Z9Wz+5yCXnJ8oNUEgc3H2NbbzJF6sMBXSPtFS2NOK8 -OsAI9OodMrDd6+lwljrmFoCCkJHDEfE637IcsbgFKkzhO/oNCRK6OrudG4teDahz -Au4LoEYwT730QKC/VQxxEVZobjn9/sTrq9CZlbPYHxX4fz6e00sX7H9i49vk9zQ5 -5qCm9ljhrQPSa42Q62PPE2BEEGSP2KBm0J+H3vlvCD6+SNc/nMZjrRmgjrI= ------END CERTIFICATE----- -""" + -----BEGIN CERTIFICATE----- + MIICmDCCAYACCQCPC8JDqMh1zzANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJ1 + czAgFw0xODEwMzExNTU1MjJaGA8yMTE4MTAwNzE1NTUyMlowDTELMAkGA1UEBhMC + dXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiC+TGmbSP/nWWN1tj + yNfnWCU5ATjtIOfdtP6ycx8JSeqkvyNXG21kNUn14jTTU8BglGL2hfVpCbMisUdb + d3LpP8unSsvlOWwORFOViSy4YljSNM/FNoMtavuITA/sEELYgjWkz2o/uHPZHud9 + +JQwGJgqIlMa3mr2IaaUZlWN3D1u88bzJYhpt3YyxRy9+OEoOKy36KdWwhKzV3S8 + kXb0Y1GbAo68jJ9RfzeLy290mIs9qG2y1CNXWO6sxf6B//LaalizZiCfzYAVKcNR + 9oNYsEJc5KB/+DsAGTzR7mL+oiU4h/vwVb2GTDat5C+PFGi6j1ujxYTRPO538ljg + dslnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFYhA7sw8odOsRO8/DUklBOjPnmn + a078oSumgPXXw6AgcoAJv/Qthjo6CCEtrjYfcA9jaBw9/Tii7mDmqDRS5c9ZPL8+ + NEPdHjFCFBOEvlL6uHOgw0Z9Wz+5yCXnJ8oNUEgc3H2NbbzJF6sMBXSPtFS2NOK8 + OsAI9OodMrDd6+lwljrmFoCCkJHDEfE637IcsbgFKkzhO/oNCRK6OrudG4teDahz + Au4LoEYwT730QKC/VQxxEVZobjn9/sTrq9CZlbPYHxX4fz6e00sX7H9i49vk9zQ5 + 5qCm9ljhrQPSa42Q62PPE2BEEGSP2KBm0J+H3vlvCD6+SNc/nMZjrRmgjrI= + -----END CERTIFICATE----- + """ private let key = """ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDiC+TGmbSP/nWW -N1tjyNfnWCU5ATjtIOfdtP6ycx8JSeqkvyNXG21kNUn14jTTU8BglGL2hfVpCbMi -sUdbd3LpP8unSsvlOWwORFOViSy4YljSNM/FNoMtavuITA/sEELYgjWkz2o/uHPZ -Hud9+JQwGJgqIlMa3mr2IaaUZlWN3D1u88bzJYhpt3YyxRy9+OEoOKy36KdWwhKz -V3S8kXb0Y1GbAo68jJ9RfzeLy290mIs9qG2y1CNXWO6sxf6B//LaalizZiCfzYAV -KcNR9oNYsEJc5KB/+DsAGTzR7mL+oiU4h/vwVb2GTDat5C+PFGi6j1ujxYTRPO53 -8ljgdslnAgMBAAECggEBANZNWFNAnYJ2R5xmVuo/GxFk68Ujd4i4TZpPYbhkk+QG -g8I0w5htlEQQkVHfZx2CpTvq8feuAH/YhlA5qeD5WaPwq26q5qsmyV6tQGDgb9lO -w85l6ySZDbwdVOJe2il/MSB6MclSKvTGNm59chJnfHYsmvY3HHq4qsc2F+tRKYMW -pY75LgEbaTUV69J3cbC1wAeVjv0q/krND+YkhYpTxNZhbazK/FHOCvY+zFu9fg0L -zpwbn5fb6wIvqG7tXp7koa3QMn64AXmO/fb5mBd8G2vBGYnxwb7Egwdg/3Dw+BXu -ynQLP7ixWsE2KNfR9Ce1i3YvEo6QDTv2340I3dntxkECgYEA9vdaL4PGyvEbpim4 -kqz1vuug8Iq0nTVDo6jmgH1o+XdcIbW3imXtgi5zUJpj4oDD7/4aufiJZjG64i/v -phe11xeUvh5QNNOzeMymVDoJut97F97KKKTv7bG8Rpon/WzH2I0SoAkECCwmdWAJ -H3nvOCnXEkpbCqmIUvHVURPRDn8CgYEA6lCk3EzFQlbXs3Sj5op61R3Mscx7/35A -eGv5axzbENHt1so+s3Zvyyi1bo4VBcwnKVCvQjmTuLiqrc9VfX8XdbiTUNnEr2u3 -992Ja6DEJTZ9gy5WiviwYnwU2HpjwOVNBb17T0NLoRHkDZ6iXj7NZgwizOki5p3j -/hS0pObSIRkCgYEAiEdOGNIarHoHy9VR6H5QzR2xHYssx2NRA8p8B4MsnhxjVqaz -tUcxnJiNQXkwjRiJBrGthdnD2ASxH4dcMsb6rMpyZcbMc5ouewZS8j9khx4zCqUB -4RPC4eMmBb+jOZEBZlnSYUUYWHokbrij0B61BsTvzUQCoQuUElEoaSkKP3kCgYEA -mwdqXHvK076jjo9w1drvtEu4IDc8H2oH++TsrEr2QiWzaDZ9z71f8BnqGNCW5jQS -AQrqOjXgIArGmqMgXB0Xh4LsrUS4Fpx9ptiD0JsYy8pGtuGUzvQFt9OC80ve7kSI -dnDMwj+zLUmqCrzXjuWcfpUu/UaPGeiDbZuDfcteYhkCgYBLyL5JY7Qd4gVQIhFX -7Sv3sNJN3KZCQHEzut7IwojaxgpuxiFvgsoXXuYolVCQp32oWbYcE2Yke+hOKsTE -sCMAWZiSGN2Nrfea730IYAXkUm8bpEd3VxDXEEv13nxVeQof+JGMdlkldFGaBRDU -oYQsPj00S3/GA9WDapwe81Wl2A== ------END PRIVATE KEY----- -""" + -----BEGIN PRIVATE KEY----- + MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDiC+TGmbSP/nWW + N1tjyNfnWCU5ATjtIOfdtP6ycx8JSeqkvyNXG21kNUn14jTTU8BglGL2hfVpCbMi + sUdbd3LpP8unSsvlOWwORFOViSy4YljSNM/FNoMtavuITA/sEELYgjWkz2o/uHPZ + Hud9+JQwGJgqIlMa3mr2IaaUZlWN3D1u88bzJYhpt3YyxRy9+OEoOKy36KdWwhKz + V3S8kXb0Y1GbAo68jJ9RfzeLy290mIs9qG2y1CNXWO6sxf6B//LaalizZiCfzYAV + KcNR9oNYsEJc5KB/+DsAGTzR7mL+oiU4h/vwVb2GTDat5C+PFGi6j1ujxYTRPO53 + 8ljgdslnAgMBAAECggEBANZNWFNAnYJ2R5xmVuo/GxFk68Ujd4i4TZpPYbhkk+QG + g8I0w5htlEQQkVHfZx2CpTvq8feuAH/YhlA5qeD5WaPwq26q5qsmyV6tQGDgb9lO + w85l6ySZDbwdVOJe2il/MSB6MclSKvTGNm59chJnfHYsmvY3HHq4qsc2F+tRKYMW + pY75LgEbaTUV69J3cbC1wAeVjv0q/krND+YkhYpTxNZhbazK/FHOCvY+zFu9fg0L + zpwbn5fb6wIvqG7tXp7koa3QMn64AXmO/fb5mBd8G2vBGYnxwb7Egwdg/3Dw+BXu + ynQLP7ixWsE2KNfR9Ce1i3YvEo6QDTv2340I3dntxkECgYEA9vdaL4PGyvEbpim4 + kqz1vuug8Iq0nTVDo6jmgH1o+XdcIbW3imXtgi5zUJpj4oDD7/4aufiJZjG64i/v + phe11xeUvh5QNNOzeMymVDoJut97F97KKKTv7bG8Rpon/WzH2I0SoAkECCwmdWAJ + H3nvOCnXEkpbCqmIUvHVURPRDn8CgYEA6lCk3EzFQlbXs3Sj5op61R3Mscx7/35A + eGv5axzbENHt1so+s3Zvyyi1bo4VBcwnKVCvQjmTuLiqrc9VfX8XdbiTUNnEr2u3 + 992Ja6DEJTZ9gy5WiviwYnwU2HpjwOVNBb17T0NLoRHkDZ6iXj7NZgwizOki5p3j + /hS0pObSIRkCgYEAiEdOGNIarHoHy9VR6H5QzR2xHYssx2NRA8p8B4MsnhxjVqaz + tUcxnJiNQXkwjRiJBrGthdnD2ASxH4dcMsb6rMpyZcbMc5ouewZS8j9khx4zCqUB + 4RPC4eMmBb+jOZEBZlnSYUUYWHokbrij0B61BsTvzUQCoQuUElEoaSkKP3kCgYEA + mwdqXHvK076jjo9w1drvtEu4IDc8H2oH++TsrEr2QiWzaDZ9z71f8BnqGNCW5jQS + AQrqOjXgIArGmqMgXB0Xh4LsrUS4Fpx9ptiD0JsYy8pGtuGUzvQFt9OC80ve7kSI + dnDMwj+zLUmqCrzXjuWcfpUu/UaPGeiDbZuDfcteYhkCgYBLyL5JY7Qd4gVQIhFX + 7Sv3sNJN3KZCQHEzut7IwojaxgpuxiFvgsoXXuYolVCQp32oWbYcE2Yke+hOKsTE + sCMAWZiSGN2Nrfea730IYAXkUm8bpEd3VxDXEEv13nxVeQof+JGMdlkldFGaBRDU + oYQsPj00S3/GA9WDapwe81Wl2A== + -----END PRIVATE KEY----- + """ diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index cf3578ed1..8f76b693b 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -12,11 +12,8 @@ // //===----------------------------------------------------------------------===// -/* NOT @testable */ import AsyncHTTPClient // Tests that need @testable go into HTTPClientInternalTests.swift +import AsyncHTTPClient // NOT @testable - tests that need @testable go into HTTPClientInternalTests.swift import Atomics -#if canImport(Network) -import Network -#endif import Logging import NIOConcurrencyHelpers import NIOCore @@ -30,6 +27,10 @@ import NIOTestUtils import NIOTransportServices import XCTest +#if canImport(Network) +import Network +#endif + final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testRequestURI() throws { let request1 = try Request(url: "https://someserver.com:8888/some/path?foo=bar") @@ -122,7 +123,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertEqual(url.scheme, "http+unix") XCTAssertEqual(url.host, "/tmp/file with spacesと漢字") XCTAssertEqual(url.path, "/file/path") - XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path") + XCTAssertEqual( + url.absoluteString, + "http+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path" + ) } let url5 = URL(httpsURLWithSocketPath: "/tmp/file") @@ -158,7 +162,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertEqual(url.scheme, "https+unix") XCTAssertEqual(url.host, "/tmp/file with spacesと漢字") XCTAssertEqual(url.path, "/file/path") - XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path") + XCTAssertEqual( + url.absoluteString, + "https+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path" + ) } } @@ -171,55 +178,116 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testConvenienceExecuteMethods() throws { - XCTAssertEqual(["GET"[...]], - try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["POST"[...]], - try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["PATCH"[...]], - try self.defaultClient.patch(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["PUT"[...]], - try self.defaultClient.put(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["DELETE"[...]], - try self.defaultClient.delete(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["GET"[...]], - try self.defaultClient.execute(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["CHECKOUT"[...]], - try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) + XCTAssertEqual( + ["GET"[...]], + try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["POST"[...]], + try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["PATCH"[...]], + try self.defaultClient.patch(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["PUT"[...]], + try self.defaultClient.put(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["DELETE"[...]], + try self.defaultClient.delete(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["GET"[...]], + try self.defaultClient.execute(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["CHECKOUT"[...]], + try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) } func testConvenienceExecuteMethodsOverSocket() throws { - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) - defer { - XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) - } + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) + defer { + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } - XCTAssertEqual(["GET"[...]], - try self.defaultClient.execute(socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["GET"[...]], - try self.defaultClient.execute(.GET, socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["POST"[...]], - try self.defaultClient.execute(.POST, socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - }) + XCTAssertEqual( + ["GET"[...]], + try self.defaultClient.execute(socketPath: path, urlPath: "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["GET"[...]], + try self.defaultClient.execute(.GET, socketPath: path, urlPath: "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["POST"[...]], + try self.defaultClient.execute(.POST, socketPath: path, urlPath: "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + } + ) } func testConvenienceExecuteMethodsOverSecureSocket() throws { - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true, compress: false), bindTarget: .unixDomainSocket(path)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none)) - defer { - XCTAssertNoThrow(try localClient.syncShutdown()) - XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) - } + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let localSocketPathHTTPBin = HTTPBin( + .http1_1(ssl: true, compress: false), + bindTarget: .unixDomainSocket(path) + ) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none) + ) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } - XCTAssertEqual(["GET"[...]], - try localClient.execute(secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["GET"[...]], - try localClient.execute(.GET, secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - XCTAssertEqual(["POST"[...]], - try localClient.execute(.POST, secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) - }) + XCTAssertEqual( + ["GET"[...]], + try localClient.execute(secureSocketPath: path, urlPath: "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["GET"[...]], + try localClient.execute(.GET, secureSocketPath: path, urlPath: "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + XCTAssertEqual( + ["POST"[...]], + try localClient.execute(.POST, secureSocketPath: path, urlPath: "echo-method").wait().headers[ + canonicalForm: "X-Method-Used" + ] + ) + } + ) } func testGet() throws { @@ -235,7 +303,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testPost() throws { - let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .string("1234")).wait() + let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .string("1234")) + .wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) @@ -247,7 +316,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let bodyData = Array("hello, world!").lazy.map { $0.uppercased().first!.asciiValue! } let erasedData = AnyRandomAccessCollection(bodyData) - let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .bytes(erasedData)).wait() + let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .bytes(erasedData)) + .wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) @@ -258,7 +328,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testPostWithFoundationDataBody() throws { let bodyData = Data("hello, world!".utf8) - let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .data(bodyData)).wait() + let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .data(bodyData)) + .wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) @@ -268,8 +339,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testGetHttps() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) @@ -281,8 +354,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testGetHttpsWithIP() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) @@ -300,8 +375,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertNoThrow(try group.syncShutdownGracefully()) } let localHTTPBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(group), - configuration: HTTPClient.Configuration(certificateVerification: .none)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(group), + configuration: HTTPClient.Configuration(certificateVerification: .none) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) @@ -314,8 +391,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testGetHttpsWithIPv6() throws { try XCTSkipUnless(canBindIPv6Loopback, "Requires IPv6") let localHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .localhostIPv6RandomPort) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) @@ -334,8 +413,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertNoThrow(try group.syncShutdownGracefully()) } let localHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .localhostIPv6RandomPort) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(group), - configuration: HTTPClient.Configuration(certificateVerification: .none)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(group), + configuration: HTTPClient.Configuration(certificateVerification: .none) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) @@ -347,14 +428,20 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testPostHttps() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - let request = try Request(url: "https://localhost:\(localHTTPBin.port)/post", method: .POST, body: .string("1234")) + let request = try Request( + url: "https://localhost:\(localHTTPBin.port)/post", + method: .POST, + body: .string("1234") + ) let response = try localClient.execute(request: request).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } @@ -366,8 +453,13 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testHttpRedirect() throws { let httpsBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration( + certificateVerification: .none, + redirectConfiguration: .follow(max: 10, allowCycles: true) + ) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) @@ -377,102 +469,189 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { var response = try localClient.get(url: self.defaultHTTPBinURLPrefix + "redirect/302").wait() XCTAssertEqual(response.status, .ok) - response = try localClient.get(url: self.defaultHTTPBinURLPrefix + "redirect/https?port=\(httpsBin.port)").wait() + response = try localClient.get(url: self.defaultHTTPBinURLPrefix + "redirect/https?port=\(httpsBin.port)") + .wait() XCTAssertEqual(response.status, .ok) - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { httpSocketPath in - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { httpsSocketPath in - let socketHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(httpSocketPath)) - let socketHTTPSBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(httpsSocketPath)) - defer { - XCTAssertNoThrow(try socketHTTPBin.shutdown()) - XCTAssertNoThrow(try socketHTTPSBin.shutdown()) - } - - // From HTTP or HTTPS to HTTP+UNIX should fail to redirect - var targetURL = "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" - var request = try Request(url: self.defaultHTTPBinURLPrefix + "redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - var response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .found) - XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - - request = try Request(url: "https://localhost:\(httpsBin.port)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .found) - XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - - // From HTTP or HTTPS to HTTPS+UNIX should also fail to redirect - targetURL = "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" - request = try Request(url: self.defaultHTTPBinURLPrefix + "redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .found) - XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - - request = try Request(url: "https://localhost:\(httpsBin.port)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .found) - XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - - // ... while HTTP+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed - targetURL = self.defaultHTTPBinURLPrefix + "ok" - request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .ok) - - targetURL = "https://localhost:\(httpsBin.port)/ok" - request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .ok) - - targetURL = "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" - request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .ok) - - targetURL = "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" - request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .ok) - - // ... and HTTPS+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed - targetURL = self.defaultHTTPBinURLPrefix + "ok" - request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .ok) - - targetURL = "https://localhost:\(httpsBin.port)/ok" - request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .ok) - - targetURL = "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" - request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .ok) - - targetURL = "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" - request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { httpSocketPath in + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { httpsSocketPath in + let socketHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(httpSocketPath)) + let socketHTTPSBin = HTTPBin( + .http1_1(ssl: true), + bindTarget: .unixDomainSocket(httpsSocketPath) + ) + defer { + XCTAssertNoThrow(try socketHTTPBin.shutdown()) + XCTAssertNoThrow(try socketHTTPSBin.shutdown()) + } - response = try localClient.execute(request: request).wait() - XCTAssertEqual(response.status, .ok) - }) - }) + // From HTTP or HTTPS to HTTP+UNIX should fail to redirect + var targetURL = + "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" + var request = try Request( + url: self.defaultHTTPBinURLPrefix + "redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + var response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .found) + XCTAssertEqual(response.headers.first(name: "Location"), targetURL) + + request = try Request( + url: "https://localhost:\(httpsBin.port)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .found) + XCTAssertEqual(response.headers.first(name: "Location"), targetURL) + + // From HTTP or HTTPS to HTTPS+UNIX should also fail to redirect + targetURL = + "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" + request = try Request( + url: self.defaultHTTPBinURLPrefix + "redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .found) + XCTAssertEqual(response.headers.first(name: "Location"), targetURL) + + request = try Request( + url: "https://localhost:\(httpsBin.port)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .found) + XCTAssertEqual(response.headers.first(name: "Location"), targetURL) + + // ... while HTTP+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed + targetURL = self.defaultHTTPBinURLPrefix + "ok" + request = try Request( + url: + "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .ok) + + targetURL = "https://localhost:\(httpsBin.port)/ok" + request = try Request( + url: + "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .ok) + + targetURL = + "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" + request = try Request( + url: + "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .ok) + + targetURL = + "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" + request = try Request( + url: + "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .ok) + + // ... and HTTPS+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed + targetURL = self.defaultHTTPBinURLPrefix + "ok" + request = try Request( + url: + "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .ok) + + targetURL = "https://localhost:\(httpsBin.port)/ok" + request = try Request( + url: + "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .ok) + + targetURL = + "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" + request = try Request( + url: + "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .ok) + + targetURL = + "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" + request = try Request( + url: + "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", + method: .GET, + headers: ["X-Target-Redirect-URL": targetURL], + body: nil + ) + + response = try localClient.execute(request: request).wait() + XCTAssertEqual(response.status, .ok) + } + ) + } + ) } func testHttpHostRedirect() { - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration( + certificateVerification: .none, + redirectConfiguration: .follow(max: 10, allowCycles: true) + ) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) @@ -500,8 +679,14 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testLeadingSlashRelativeURL() throws { - let noLeadingSlashURL = URL(string: "percent%2Fencoded/hello", relativeTo: URL(string: self.defaultHTTPBinURLPrefix)!)! - let withLeadingSlashURL = URL(string: "/percent%2Fencoded/hello", relativeTo: URL(string: self.defaultHTTPBinURLPrefix)!)! + let noLeadingSlashURL = URL( + string: "percent%2Fencoded/hello", + relativeTo: URL(string: self.defaultHTTPBinURLPrefix)! + )! + let withLeadingSlashURL = URL( + string: "/percent%2Fencoded/hello", + relativeTo: URL(string: self.defaultHTTPBinURLPrefix)! + )! let noLeadingSlashURLRequest = try HTTPClient.Request(url: noLeadingSlashURL, method: .GET) let withLeadingSlashURLRequest = try HTTPClient.Request(url: withLeadingSlashURL, method: .GET) @@ -518,7 +703,12 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { var headers = HTTPHeaders() headers.add(name: "Content-Length", value: "12") - let request = try Request(url: self.defaultHTTPBinURLPrefix + "post", method: .POST, headers: headers, body: .byteBuffer(body)) + let request = try Request( + url: self.defaultHTTPBinURLPrefix + "post", + method: .POST, + headers: headers, + body: .byteBuffer(body) + ) let response = try self.defaultClient.execute(request: request).wait() // if the library adds another content length header we'll get a bad request error. XCTAssertEqual(.ok, response.status) @@ -563,9 +753,12 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let progress = try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in - let delegate = try FileDownloadDelegate(path: path, reportHead: { - XCTAssertEqual($0.status, .notFound) - }) + let delegate = try FileDownloadDelegate( + path: path, + reportHead: { + XCTAssertEqual($0.status, .notFound) + } + ) let progress = try self.defaultClient.execute( request: request, @@ -587,12 +780,16 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { struct CustomError: Equatable, Error {} try TemporaryFileHelpers.withTemporaryFilePath { path in - let delegate = try FileDownloadDelegate(path: path, reportHead: { task, head in - XCTAssertEqual(head.status, .ok) - task.fail(reason: CustomError()) - }, reportProgress: { _, _ in - XCTFail("should never be called") - }) + let delegate = try FileDownloadDelegate( + path: path, + reportHead: { task, head in + XCTAssertEqual(head.status, .ok) + task.fail(reason: CustomError()) + }, + reportProgress: { _, _ in + XCTFail("should never be called") + } + ) XCTAssertThrowsError( try self.defaultClient.execute( request: request, @@ -614,8 +811,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testReadTimeout() { - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(read: .milliseconds(150)))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(read: .milliseconds(150))) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) @@ -627,8 +826,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testWriteTimeout() throws { - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(write: .nanoseconds(10)))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(write: .nanoseconds(10))) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) @@ -636,19 +837,21 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // Create a request that writes a chunk, then waits longer than the configured write timeout, // and then writes again. This should trigger a write timeout error. - let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "post", - method: .POST, - headers: ["transfer-encoding": "chunked"], - body: .stream { streamWriter in - _ = streamWriter.write(.byteBuffer(.init())) - - let promise = self.clientGroup.next().makePromise(of: Void.self) - self.clientGroup.next().scheduleTask(in: .milliseconds(3)) { - streamWriter.write(.byteBuffer(.init())).cascade(to: promise) - } + let request = try HTTPClient.Request( + url: self.defaultHTTPBinURLPrefix + "post", + method: .POST, + headers: ["transfer-encoding": "chunked"], + body: .stream { streamWriter in + _ = streamWriter.write(.byteBuffer(.init())) + + let promise = self.clientGroup.next().makePromise(of: Void.self) + self.clientGroup.next().scheduleTask(in: .milliseconds(3)) { + streamWriter.write(.byteBuffer(.init())).cascade(to: promise) + } - return promise.futureResult - }) + return promise.futureResult + } + ) XCTAssertThrowsError(try localClient.execute(request: request).wait()) { XCTAssertEqual($0 as? HTTPClientError, .writeTimeout) @@ -684,8 +887,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let url = "http://localhost:\(port)/get" #endif - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(timeout: .init(connect: .milliseconds(100), read: .milliseconds(150)))) + let httpClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(timeout: .init(connect: .milliseconds(100), read: .milliseconds(150))) + ) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) @@ -697,7 +902,12 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testDeadline() { - XCTAssertThrowsError(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "wait", deadline: .now() + .milliseconds(150)).wait()) { + XCTAssertThrowsError( + try self.defaultClient.get( + url: self.defaultHTTPBinURLPrefix + "wait", + deadline: .now() + .milliseconds(150) + ).wait() + ) { XCTAssertEqual($0 as? HTTPClientError, .deadlineExceeded) } } @@ -783,7 +993,13 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let localHTTPBin = HTTPBin(proxy: .simulate(authorization: "Basic YWxhZGRpbjpvcGVuc2VzYW1l")) let localClient = HTTPClient( eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(proxy: .server(host: "localhost", port: localHTTPBin.port, authorization: .basic(username: "aladdin", password: "opensesame"))) + configuration: .init( + proxy: .server( + host: "localhost", + port: localHTTPBin.port, + authorization: .basic(username: "aladdin", password: "opensesame") + ) + ) ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) @@ -837,8 +1053,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testEventLoopArgument() throws { - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(redirectConfiguration: .follow(max: 10, allowCycles: true))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(redirectConfiguration: .follow(max: 10, allowCycles: true)) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } @@ -859,26 +1077,33 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func didFinishRequest(task: HTTPClient.Task) throws -> Bool { - return self.result + self.result } } let eventLoop = self.clientGroup.next() let delegate = EventLoopValidatingDelegate(eventLoop: eventLoop) var request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get") - var response = try localClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: eventLoop)).wait() + var response = try localClient.execute( + request: request, + delegate: delegate, + eventLoop: .delegate(on: eventLoop) + ).wait() XCTAssertEqual(true, response) // redirect request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "redirect/302") - response = try localClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: eventLoop)).wait() + response = try localClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: eventLoop)) + .wait() XCTAssertEqual(true, response) } func testDecompression() throws { let localHTTPBin = HTTPBin(.http1_1(compress: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(decompression: .enabled(limit: .none))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(decompression: .enabled(limit: .none)) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) @@ -887,7 +1112,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { var body = "" for _ in 1...1000 { - body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + body += + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." } for algorithm in [nil, "gzip", "deflate"] { @@ -929,7 +1155,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { var body = "" for _ in 1...1000 { - body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + body += + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." } for algorithm: String? in [nil] { @@ -957,7 +1184,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testDecompressionLimit() throws { let localHTTPBin = HTTPBin(.http1_1(compress: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(decompression: .enabled(limit: .ratio(1)))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(decompression: .enabled(limit: .ratio(1))) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) @@ -975,30 +1205,47 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testLoopDetectionRedirectLimit() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 5, allowCycles: false))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration( + certificateVerification: .none, + redirectConfiguration: .follow(max: 5, allowCycles: false) + ) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in + XCTAssertThrowsError( + try localClient.get(url: "https://localhost:\(localHTTPBin.port)/redirect/infinite1").wait(), + "Should fail with redirect limit" + ) { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectCycleDetected) } } func testCountRedirectLimit() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration( + certificateVerification: .none, + redirectConfiguration: .follow(max: 10, allowCycles: true) + ) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/redirect/infinite1").timeout(after: .seconds(10)).wait()) { error in + XCTAssertThrowsError( + try localClient.get(url: "https://localhost:\(localHTTPBin.port)/redirect/infinite1").timeout( + after: .seconds(10) + ).wait() + ) { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectLimitReached) } } @@ -1016,13 +1263,15 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { defer { XCTAssertNoThrow(try localClient.syncShutdown()) } var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( - url: "https://localhost:\(localHTTPBin.port)/redirect/target", - method: .GET, - headers: [ - "X-Target-Redirect-URL": "/redirect/target", - ] - )) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "https://localhost:\(localHTTPBin.port)/redirect/target", + method: .GET, + headers: [ + "X-Target-Redirect-URL": "/redirect/target" + ] + ) + ) guard let request = maybeRequest else { return } XCTAssertThrowsError( @@ -1042,8 +1291,12 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func channelRead(context: ChannelHandlerContext, data: NIOAny) { if case .end = self.unwrapInboundIn(data) { - let responseHead = HTTPServerResponsePart.head(.init(version: .init(major: 1, minor: 1), - status: .ok)) + let responseHead = HTTPServerResponsePart.head( + .init( + version: .init(major: 1, minor: 1), + status: .ok + ) + ) context.write(self.wrapOutboundOut(responseHead), promise: nil) context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) } @@ -1056,18 +1309,22 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } var server: Channel? - XCTAssertNoThrow(server = try ServerBootstrap(group: group) - .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1) - .serverChannelOption(ChannelOptions.backlog, value: .init(numberOfParallelWorkers)) - .childChannelInitializer { channel in - channel.pipeline.configureHTTPServerPipeline(withPipeliningAssistance: false, - withServerUpgrade: nil, - withErrorHandling: false).flatMap { - channel.pipeline.addHandler(HTTPServer()) + XCTAssertNoThrow( + server = try ServerBootstrap(group: group) + .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1) + .serverChannelOption(ChannelOptions.backlog, value: .init(numberOfParallelWorkers)) + .childChannelInitializer { channel in + channel.pipeline.configureHTTPServerPipeline( + withPipeliningAssistance: false, + withServerUpgrade: nil, + withErrorHandling: false + ).flatMap { + channel.pipeline.addHandler(HTTPServer()) + } } - } - .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) - .wait()) + .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) + .wait() + ) defer { XCTAssertNoThrow(try server?.close().wait()) } @@ -1100,18 +1357,28 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } let result = self.defaultClient.get(url: "http://localhost:\(web.serverPort)/foo") - XCTAssertNoThrow(try web.receiveHeadAndVerify { received in - let expected = HTTPRequestHead( - version: .http1_1, - method: .GET, - uri: "/foo", - headers: ["Host": "localhost:\(web.serverPort)"] - ) - XCTAssertEqual(expected, received) - }) + XCTAssertNoThrow( + try web.receiveHeadAndVerify { received in + let expected = HTTPRequestHead( + version: .http1_1, + method: .GET, + uri: "/foo", + headers: ["Host": "localhost:\(web.serverPort)"] + ) + XCTAssertEqual(expected, received) + } + ) XCTAssertNoThrow(try web.receiveEnd()) - XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), - status: .internalServerError)))) + XCTAssertNoThrow( + try web.writeOutbound( + .head( + .init( + version: .init(major: 1, minor: 1), + status: .internalServerError + ) + ) + ) + ) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) var response: HTTPClient.Response? @@ -1126,18 +1393,31 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertNoThrow(try web.stop()) } let result = self.defaultClient.get(url: "http://localhost:\(web.serverPort)/foo") - XCTAssertNoThrow(try web.receiveHeadAndVerify { - XCTAssertEqual($0, HTTPRequestHead( - version: .init(major: 1, minor: 1), - method: .GET, - uri: "/foo", - headers: HTTPHeaders([("Host", "localhost:\(web.serverPort)")]) - )) - }) + XCTAssertNoThrow( + try web.receiveHeadAndVerify { + XCTAssertEqual( + $0, + HTTPRequestHead( + version: .init(major: 1, minor: 1), + method: .GET, + uri: "/foo", + headers: HTTPHeaders([("Host", "localhost:\(web.serverPort)")]) + ) + ) + } + ) XCTAssertNoThrow(try web.receiveEnd()) - XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 0), - status: .internalServerError)))) + XCTAssertNoThrow( + try web.writeOutbound( + .head( + .init( + version: .init(major: 1, minor: 0), + status: .internalServerError + ) + ) + ) + ) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) var response: HTTPClient.Response? @@ -1150,15 +1430,17 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let web = NIOHTTP1TestServer(group: self.serverGroup) let result = self.defaultClient.get(url: "http://localhost:\(web.serverPort)/foo") - XCTAssertNoThrow(try web.receiveHeadAndVerify { received in - let expected = HTTPRequestHead( - version: .http1_1, - method: .GET, - uri: "/foo", - headers: ["Host": "localhost:\(web.serverPort)"] - ) - XCTAssertEqual(expected, received) - }) + XCTAssertNoThrow( + try web.receiveHeadAndVerify { received in + let expected = HTTPRequestHead( + version: .http1_1, + method: .GET, + uri: "/foo", + headers: ["Host": "localhost:\(web.serverPort)"] + ) + XCTAssertEqual(expected, received) + } + ) XCTAssertNoThrow(try web.receiveEnd()) XCTAssertNoThrow(try web.stop()) @@ -1176,19 +1458,29 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { for _ in 0..<10 { let result = self.defaultClient.get(url: "http://localhost:\(web.serverPort)/foo") - XCTAssertNoThrow(try web.receiveHeadAndVerify { received in - let expected = HTTPRequestHead( - version: .http1_1, - method: .GET, - uri: "/foo", - headers: ["Host": "localhost:\(web.serverPort)"] - ) - XCTAssertEqual(expected, received) - }) + XCTAssertNoThrow( + try web.receiveHeadAndVerify { received in + let expected = HTTPRequestHead( + version: .http1_1, + method: .GET, + uri: "/foo", + headers: ["Host": "localhost:\(web.serverPort)"] + ) + XCTAssertEqual(expected, received) + } + ) XCTAssertNoThrow(try web.receiveEnd()) - XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 0), - status: .ok, - headers: HTTPHeaders([("connection", "close")]))))) + XCTAssertNoThrow( + try web.writeOutbound( + .head( + .init( + version: .init(major: 1, minor: 0), + status: .ok, + headers: HTTPHeaders([("connection", "close")]) + ) + ) + ) + ) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) var response: HTTPClient.Response? @@ -1207,20 +1499,34 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { for i in 0..<10 { let result = self.defaultClient.get(url: "http://localhost:\(web.serverPort)/foo") - XCTAssertNoThrow(try web.receiveHeadAndVerify { received in - let expected = HTTPRequestHead( - version: .http1_1, - method: .GET, - uri: "/foo", - headers: ["Host": "localhost:\(web.serverPort)"] - ) - XCTAssertEqual(expected, received) - }) + XCTAssertNoThrow( + try web.receiveHeadAndVerify { received in + let expected = HTTPRequestHead( + version: .http1_1, + method: .GET, + uri: "/foo", + headers: ["Host": "localhost:\(web.serverPort)"] + ) + XCTAssertEqual(expected, received) + } + ) XCTAssertNoThrow(try web.receiveEnd()) - XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 0), - status: .ok, - headers: HTTPHeaders([("connection", - i % 2 == 0 ? "close" : "keep-alive")]))))) + XCTAssertNoThrow( + try web.writeOutbound( + .head( + .init( + version: .init(major: 1, minor: 0), + status: .ok, + headers: HTTPHeaders([ + ( + "connection", + i % 2 == 0 ? "close" : "keep-alive" + ) + ]) + ) + ) + ) + ) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) var response: HTTPClient.Response? @@ -1231,8 +1537,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testStressGetHttpsSSLError() throws { - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration().enableFastFailureModeForTesting()) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration().enableFastFailureModeForTesting() + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } @@ -1242,7 +1550,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { localClient.execute(request: request, delegate: TestHTTPDelegate()) } - let results = try EventLoopFuture.whenAllComplete(tasks.map { $0.futureResult }, on: localClient.eventLoopGroup.next()).wait() + let results = try EventLoopFuture.whenAllComplete( + tasks.map { $0.futureResult }, + on: localClient.eventLoopGroup.next() + ).wait() for result in results { switch result { @@ -1259,9 +1570,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // We're speaking TLS to a plain text server. This will cause the handshake to fail but given // that the bytes "HTTP/1.1" aren't the start of a valid TLS packet, we can also get // errSSLPeerProtocolVersion because the first bytes contain the version. - XCTAssert(clientError.status == errSSLHandshakeFail || - clientError.status == errSSLPeerProtocolVersion, - "unexpected NWTLSError with status \(clientError.status)") + XCTAssert( + clientError.status == errSSLHandshakeFail || clientError.status == errSSLPeerProtocolVersion, + "unexpected NWTLSError with status \(clientError.status)" + ) #endif } else { guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else { @@ -1306,7 +1618,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertEqual(nwTLSError.status, errSSLBadCert, "unexpected tls error: \(nwTLSError)") #else guard let sslError = error as? NIOSSLError, - case .handshakeFailed(.sslError) = sslError else { + case .handshakeFailed(.sslError) = sslError + else { XCTFail("unexpected error \(error)") return } @@ -1339,7 +1652,9 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(port)", deadline: .now() + .seconds(2)).wait()) { error in + XCTAssertThrowsError( + try localClient.get(url: "https://localhost:\(port)", deadline: .now() + .seconds(2)).wait() + ) { error in #if canImport(Network) guard let nwTLSError = error as? HTTPClient.NWTLSError else { XCTFail("could not cast \(error) of type \(type(of: error)) to \(HTTPClient.NWTLSError.self)") @@ -1348,7 +1663,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertEqual(nwTLSError.status, errSSLBadCert, "unexpected tls error: \(nwTLSError)") #else guard let sslError = error as? NIOSSLError, - case .handshakeFailed(.sslError) = sslError else { + case .handshakeFailed(.sslError) = sslError + else { XCTFail("unexpected error \(error)") return } @@ -1379,13 +1695,17 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let requestCount = 200 var futureResults = [EventLoopFuture]() for _ in 1...requestCount { - let req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", - method: .GET, - headers: ["X-internal-delay": "5", "Connection": "close"]) + let req = try HTTPClient.Request( + url: self.defaultHTTPBinURLPrefix + "get", + method: .GET, + headers: ["X-internal-delay": "5", "Connection": "close"] + ) futureResults.append(self.defaultClient.execute(request: req)) } - XCTAssertNoThrow(try EventLoopFuture.andAllComplete(futureResults, on: eventLoop) - .timeout(after: .seconds(10)).wait()) + XCTAssertNoThrow( + try EventLoopFuture.andAllComplete(futureResults, on: eventLoop) + .timeout(after: .seconds(10)).wait() + ) } func testManyConcurrentRequestsWork() { @@ -1402,8 +1722,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let q = DispatchQueue(label: "worker \(w)") q.async(group: allDone) { func go() { - allWorkersReady.signal() // tell the driver we're ready - allWorkersGo.wait() // wait for the driver to let us go + allWorkersReady.signal() // tell the driver we're ready + allWorkersGo.wait() // wait for the driver to let us go for _ in 0..]() for i in 1...100 { - let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .GET, headers: ["X-internal-delay": "10"]) + let request = try HTTPClient.Request( + url: self.defaultHTTPBinURLPrefix + "get", + method: .GET, + headers: ["X-internal-delay": "10"] + ) let preference: HTTPClient.EventLoopPreference if i <= 50 { preference = .delegateAndChannel(on: first) @@ -1640,15 +1986,18 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let seenError = DispatchGroup() seenError.enter() var maybeSecondRequest: EventLoopFuture? - XCTAssertNoThrow(maybeSecondRequest = try el.submit { - let neverSucceedingRequest = localClient.get(url: url) - let secondRequest = neverSucceedingRequest.flatMapError { error in - XCTAssertEqual(.cancelled, error as? HTTPClientError) - seenError.leave() - return localClient.get(url: url) // <== this is the main part, during the error callout, we call back in - } - return secondRequest - }.wait()) + XCTAssertNoThrow( + maybeSecondRequest = try el.submit { + let neverSucceedingRequest = localClient.get(url: url) + let secondRequest = neverSucceedingRequest.flatMapError { error in + XCTAssertEqual(.cancelled, error as? HTTPClientError) + seenError.leave() + // v this is the main part, during the error callout, we call back in + return localClient.get(url: url) + } + return secondRequest + }.wait() + ) guard let secondRequest = maybeSecondRequest else { XCTFail("couldn't get request future") @@ -1674,13 +2023,15 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) } - XCTAssertEqual(.ok, - try el.flatSubmit { () -> EventLoopFuture in - localClient.get(url: url).flatMap { firstResponse in - XCTAssertEqual(.ok, firstResponse.status) - return localClient.get(url: url) // <== interesting bit here - } - }.wait().status) + XCTAssertEqual( + .ok, + try el.flatSubmit { () -> EventLoopFuture in + localClient.get(url: url).flatMap { firstResponse in + XCTAssertEqual(.ok, firstResponse.status) + return localClient.get(url: url) // <== interesting bit here + } + }.wait().status + ) } func testMakeSecondRequestWhilstFirstIsOngoing() { @@ -1697,11 +2048,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let url = "http://127.0.0.1:\(web.serverPort)" let firstRequest = client.get(url: url) - XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) // first request: .head + XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) // first request: .head // Now, the first request is ongoing but not complete, let's start a second one let secondRequest = client.get(url: url) - XCTAssertEqual(.end(nil), try web.readInbound()) // first request: .end + XCTAssertEqual(.end(nil), try web.readInbound()) // first request: .end XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) @@ -1709,8 +2060,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertEqual(.ok, try firstRequest.wait().status) // Okay, first request done successfully, let's do the second one too. - XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) // first request: .head - XCTAssertEqual(.end(nil), try web.readInbound()) // first request: .end + XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) // first request: .head + XCTAssertEqual(.end(nil), try web.readInbound()) // first request: .end XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .created)))) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) @@ -1721,15 +2072,19 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // This tests just connecting to a URL where the whole URL is the UNIX domain socket path like // unix:///this/is/my/socket.sock // We don't really have a path component, so we'll have to use "/" - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let localHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) - defer { - XCTAssertNoThrow(try localHTTPBin.shutdown()) + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let localHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) + defer { + XCTAssertNoThrow(try localHTTPBin.shutdown()) + } + let target = "unix://\(path)" + XCTAssertEqual( + ["Yes"[...]], + try self.defaultClient.get(url: target).wait().headers[canonicalForm: "X-Is-This-Slash"] + ) } - let target = "unix://\(path)" - XCTAssertEqual(["Yes"[...]], - try self.defaultClient.get(url: target).wait().headers[canonicalForm: "X-Is-This-Slash"]) - }) + ) } func testUDSSocketAndPath() { @@ -1737,56 +2092,73 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // // 1. a "base path" which is the path to the UNIX domain socket // 2. an actual path which is the normal path in a regular URL like https://example.com/this/is/the/path - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let localHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) - defer { - XCTAssertNoThrow(try localHTTPBin.shutdown()) - } - guard let target = URL(string: "/echo-uri", relativeTo: URL(string: "unix://\(path)")), - let request = try? Request(url: target) else { - XCTFail("couldn't build URL for request") - return + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let localHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) + defer { + XCTAssertNoThrow(try localHTTPBin.shutdown()) + } + guard let target = URL(string: "/echo-uri", relativeTo: URL(string: "unix://\(path)")), + let request = try? Request(url: target) + else { + XCTFail("couldn't build URL for request") + return + } + XCTAssertEqual( + ["/echo-uri"[...]], + try self.defaultClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"] + ) } - XCTAssertEqual(["/echo-uri"[...]], - try self.defaultClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"]) - }) + ) } func testHTTPPlusUNIX() { // Here, we're testing a URL where the UNIX domain socket is encoded as the host name - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let localHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) - defer { - XCTAssertNoThrow(try localHTTPBin.shutdown()) - } - guard let target = URL(httpURLWithSocketPath: path, uri: "/echo-uri"), - let request = try? Request(url: target) else { - XCTFail("couldn't build URL for request") - return + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let localHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) + defer { + XCTAssertNoThrow(try localHTTPBin.shutdown()) + } + guard let target = URL(httpURLWithSocketPath: path, uri: "/echo-uri"), + let request = try? Request(url: target) + else { + XCTFail("couldn't build URL for request") + return + } + XCTAssertEqual( + ["/echo-uri"[...]], + try self.defaultClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"] + ) } - XCTAssertEqual(["/echo-uri"[...]], - try self.defaultClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"]) - }) + ) } func testHTTPSPlusUNIX() { // Here, we're testing a URL where the UNIX domain socket is encoded as the host name - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let localHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none)) - defer { - XCTAssertNoThrow(try localClient.syncShutdown()) - XCTAssertNoThrow(try localHTTPBin.shutdown()) - } - guard let target = URL(httpsURLWithSocketPath: path, uri: "/echo-uri"), - let request = try? Request(url: target) else { - XCTFail("couldn't build URL for request") - return + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let localHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none) + ) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localHTTPBin.shutdown()) + } + guard let target = URL(httpsURLWithSocketPath: path, uri: "/echo-uri"), + let request = try? Request(url: target) + else { + XCTFail("couldn't build URL for request") + return + } + XCTAssertEqual( + ["/echo-uri"[...]], + try localClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"] + ) } - XCTAssertEqual(["/echo-uri"[...]], - try localClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"]) - }) + ) } func testUseExistingConnectionOnDifferentEL() throws { @@ -1800,13 +2172,20 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let eventLoops = (1...threadCount).map { _ in elg.next() } let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get") - let closingRequest = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", headers: ["Connection": "close"]) + let closingRequest = try HTTPClient.Request( + url: self.defaultHTTPBinURLPrefix + "get", + headers: ["Connection": "close"] + ) for (index, el) in eventLoops.enumerated() { if index.isMultiple(of: 2) { - XCTAssertNoThrow(try localClient.execute(request: request, eventLoop: .delegateAndChannel(on: el)).wait()) + XCTAssertNoThrow( + try localClient.execute(request: request, eventLoop: .delegateAndChannel(on: el)).wait() + ) } else { - XCTAssertNoThrow(try localClient.execute(request: request, eventLoop: .delegateAndChannel(on: el)).wait()) + XCTAssertNoThrow( + try localClient.execute(request: request, eventLoop: .delegateAndChannel(on: el)).wait() + ) XCTAssertNoThrow(try localClient.execute(request: closingRequest, eventLoop: .indifferent).wait()) } } @@ -1839,8 +2218,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let last = self.requestNumber.loadThenWrappingIncrement(ordering: .relaxed) switch last { case 0, 2: - context.write(self.wrapOutboundOut(.head(.init(version: .init(major: 1, minor: 1), status: .ok))), - promise: nil) + context.write( + self.wrapOutboundOut(.head(.init(version: .init(major: 1, minor: 1), status: .ok))), + promise: nil + ) context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) case 1: context.close(promise: nil) @@ -1853,20 +2234,24 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let requestNumber = ManagedAtomic(0) let connectionNumber = ManagedAtomic(0) - let sharedStateServerHandler = ServerThatAcceptsThenRejects(requestNumber: requestNumber, - connectionNumber: connectionNumber) + let sharedStateServerHandler = ServerThatAcceptsThenRejects( + requestNumber: requestNumber, + connectionNumber: connectionNumber + ) var maybeServer: Channel? - XCTAssertNoThrow(maybeServer = try ServerBootstrap(group: self.serverGroup) - .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1) - .childChannelInitializer { channel in - channel.pipeline.configureHTTPServerPipeline().flatMap { - // We're deliberately adding a handler which is shared between multiple channels. This is normally - // very verboten but this handler is specially crafted to tolerate this. - channel.pipeline.addHandler(sharedStateServerHandler) + XCTAssertNoThrow( + maybeServer = try ServerBootstrap(group: self.serverGroup) + .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1) + .childChannelInitializer { channel in + channel.pipeline.configureHTTPServerPipeline().flatMap { + // We're deliberately adding a handler which is shared between multiple channels. This is normally + // very verboten but this handler is specially crafted to tolerate this. + channel.pipeline.addHandler(sharedStateServerHandler) + } } - } - .bind(host: "127.0.0.1", port: 0) - .wait()) + .bind(host: "127.0.0.1", port: 0) + .wait() + ) guard let server = maybeServer else { XCTFail("couldn't create server") return @@ -1902,8 +2287,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { maximumAllowedIdleTimeInConnectionPool: .milliseconds(100) ) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: configuration) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: configuration + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } @@ -1926,7 +2313,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testAvoidLeakingTLSHandshakeCompletionPromise() { - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(timeout: .init(connect: .milliseconds(100)))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(timeout: .init(connect: .milliseconds(100))) + ) let localHTTPBin = HTTPBin() let port = localHTTPBin.port XCTAssertNoThrow(try localHTTPBin.shutdown()) @@ -1973,9 +2363,13 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testValidationErrorsAreSurfaced() throws { - let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .TRACE, body: .stream { _ in - self.defaultClient.eventLoopGroup.next().makeSucceededFuture(()) - }) + let request = try HTTPClient.Request( + url: self.defaultHTTPBinURLPrefix + "get", + method: .TRACE, + body: .stream { _ in + self.defaultClient.eventLoopGroup.next().makeSucceededFuture(()) + } + ) let runningRequest = self.defaultClient.execute(request: request) XCTAssertThrowsError(try runningRequest.wait()) { error in XCTAssertEqual(HTTPClientError.traceRequestWithBody, error as? HTTPClientError) @@ -1993,9 +2387,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { private var bodyPartsSeenSoFar = 0 private var atEnd = false - init(headPromise: EventLoopPromise, - bodyPromises: [EventLoopPromise], - endPromise: EventLoopPromise) { + init( + headPromise: EventLoopPromise, + bodyPromises: [EventLoopPromise], + endPromise: EventLoopPromise + ) { self.headPromise = headPromise self.bodyPromises = bodyPromises self.endPromise = endPromise @@ -2011,8 +2407,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { self.bodyPartsSeenSoFar += 1 self.bodyPromises.dropFirst(myNumber).first?.succeed(bytes) ?? XCTFail("ouch, too many chunks") case .end: - context.write(self.wrapOutboundOut(.head(.init(version: .init(major: 1, minor: 1), status: .ok))), - promise: nil) + context.write( + self.wrapOutboundOut(.head(.init(version: .init(major: 1, minor: 1), status: .ok))), + promise: nil + ) context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: self.endPromise) self.atEnd = true } @@ -2025,8 +2423,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { struct NotFulfilledError: Error {} self.headPromise.fail(NotFulfilledError()) - self.bodyPromises.forEach { - $0.fail(NotFulfilledError()) + for promise in self.bodyPromises { + promise.fail(NotFulfilledError()) } self.endPromise.fail(NotFulfilledError()) } @@ -2047,12 +2445,16 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let streamWriterPromise = group.next().makePromise(of: HTTPClient.Body.StreamWriter.self) func makeServer() -> Channel? { - return try? ServerBootstrap(group: group) + try? ServerBootstrap(group: group) .childChannelInitializer { channel in channel.pipeline.configureHTTPServerPipeline().flatMap { - channel.pipeline.addHandler(HTTPServer(headPromise: headPromise, - bodyPromises: bodyPromises, - endPromise: endPromise)) + channel.pipeline.addHandler( + HTTPServer( + headPromise: headPromise, + bodyPromises: bodyPromises, + endPromise: endPromise + ) + ) } } .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1) @@ -2065,13 +2467,15 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { return nil } - return try? HTTPClient.Request(url: "http://\(localAddress.ipAddress!):\(localAddress.port!)", - method: .POST, - headers: ["transfer-encoding": "chunked"], - body: .stream { streamWriter in - streamWriterPromise.succeed(streamWriter) - return sentOffAllBodyPartsPromise.futureResult - }) + return try? HTTPClient.Request( + url: "http://\(localAddress.ipAddress!):\(localAddress.port!)", + method: .POST, + headers: ["transfer-encoding": "chunked"], + body: .stream { streamWriter in + streamWriterPromise.succeed(streamWriter) + return sentOffAllBodyPartsPromise.futureResult + } + ) } guard let server = makeServer(), let request = makeRequest(server: server) else { @@ -2103,35 +2507,45 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testUploadStreamingCallinToleratedFromOtsideEL() throws { - let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .POST, body: .stream(contentLength: 4) { writer in - let promise = self.defaultClient.eventLoopGroup.next().makePromise(of: Void.self) - // We have to toleare callins from any thread - DispatchQueue(label: "upload-streaming").async { - writer.write(.byteBuffer(ByteBuffer(string: "1234"))).whenComplete { _ in - promise.succeed(()) + let request = try HTTPClient.Request( + url: self.defaultHTTPBinURLPrefix + "get", + method: .POST, + body: .stream(contentLength: 4) { writer in + let promise = self.defaultClient.eventLoopGroup.next().makePromise(of: Void.self) + // We have to toleare callins from any thread + DispatchQueue(label: "upload-streaming").async { + writer.write(.byteBuffer(ByteBuffer(string: "1234"))).whenComplete { _ in + promise.succeed(()) + } } + return promise.futureResult } - return promise.futureResult - }) + ) XCTAssertNoThrow(try self.defaultClient.execute(request: request).wait()) } func testWeHandleUsSendingACloseHeaderCorrectly() { - guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", - method: .GET, - headers: ["connection": "close"]), + guard + let req1 = try? Request( + url: self.defaultHTTPBinURLPrefix + "stats", + method: .GET, + headers: ["connection": "close"] + ), let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) + else { XCTFail("request 1 didn't work") return } guard let statsBytes2 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, - let stats2 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes2) else { + let stats2 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes2) + else { XCTFail("request 2 didn't work") return } guard let statsBytes3 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, - let stats3 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes3) else { + let stats3 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes3) + else { XCTFail("request 3 didn't work") return } @@ -2147,21 +2561,27 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testWeHandleUsReceivingACloseHeaderCorrectly() { - guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", - method: .GET, - headers: ["X-Send-Back-Header-Connection": "close"]), + guard + let req1 = try? Request( + url: self.defaultHTTPBinURLPrefix + "stats", + method: .GET, + headers: ["X-Send-Back-Header-Connection": "close"] + ), let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) + else { XCTFail("request 1 didn't work") return } guard let statsBytes2 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, - let stats2 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes2) else { + let stats2 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes2) + else { XCTFail("request 2 didn't work") return } guard let statsBytes3 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, - let stats3 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes3) else { + let stats3 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes3) + else { XCTFail("request 3 didn't work") return } @@ -2178,22 +2598,32 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testWeHandleUsSendingACloseHeaderAmongstOtherConnectionHeadersCorrectly() { for closeHeader in [("connection", "close"), ("CoNneCTION", "ClOSe")] { - guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", - method: .GET, - headers: ["X-Send-Back-Header-\(closeHeader.0)": - "foo,\(closeHeader.1),bar"]), + guard + let req1 = try? Request( + url: self.defaultHTTPBinURLPrefix + "stats", + method: .GET, + headers: [ + "X-Send-Back-Header-\(closeHeader.0)": + "foo,\(closeHeader.1),bar" + ] + ), let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) + else { XCTFail("request 1 didn't work") return } - guard let statsBytes2 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, - let stats2 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes2) else { + guard + let statsBytes2 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, + let stats2 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes2) + else { XCTFail("request 2 didn't work") return } - guard let statsBytes3 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, - let stats3 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes3) else { + guard + let statsBytes3 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, + let stats3 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes3) + else { XCTFail("request 3 didn't work") return } @@ -2210,22 +2640,32 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testWeHandleUsReceivingACloseHeaderAmongstOtherConnectionHeadersCorrectly() { for closeHeader in [("connection", "close"), ("CoNneCTION", "ClOSe")] { - guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", - method: .GET, - headers: ["X-Send-Back-Header-\(closeHeader.0)": - "foo,\(closeHeader.1),bar"]), + guard + let req1 = try? Request( + url: self.defaultHTTPBinURLPrefix + "stats", + method: .GET, + headers: [ + "X-Send-Back-Header-\(closeHeader.0)": + "foo,\(closeHeader.1),bar" + ] + ), let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) + else { XCTFail("request 1 didn't work") return } - guard let statsBytes2 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, - let stats2 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes2) else { + guard + let statsBytes2 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, + let stats2 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes2) + else { XCTFail("request 2 didn't work") return } - guard let statsBytes3 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, - let stats3 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes3) else { + guard + let statsBytes3 = try? self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "stats").wait().body, + let stats3 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes3) + else { XCTFail("request 3 didn't work") return } @@ -2243,28 +2683,35 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testLoggingCorrectlyAttachesRequestInformationEvenAfterDuringRedirect() { let logStore = CollectEverythingLogHandler.LogStore() - var logger = Logger(label: "\(#function)", factory: { _ in - CollectEverythingLogHandler(logStore: logStore) - }) + var logger = Logger( + label: "\(#function)", + factory: { _ in + CollectEverythingLogHandler(logStore: logStore) + } + ) logger.logLevel = .trace logger[metadataKey: "custom-request-id"] = "abcd" var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( - url: "http://localhost:\(self.defaultHTTPBin.port)/redirect/target", - method: .GET, - headers: [ - "X-Target-Redirect-URL": "/get", - ] - )) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "http://localhost:\(self.defaultHTTPBin.port)/redirect/target", + method: .GET, + headers: [ + "X-Target-Redirect-URL": "/get" + ] + ) + ) guard let request = maybeRequest else { return } - XCTAssertNoThrow(try self.defaultClient.execute( - request: request, - eventLoop: .indifferent, - deadline: nil, - logger: logger - ).wait()) + XCTAssertNoThrow( + try self.defaultClient.execute( + request: request, + eventLoop: .indifferent, + deadline: nil, + logger: logger + ).wait() + ) let logs = logStore.allEntries XCTAssertTrue(logs.allSatisfy { $0.metadata["custom-request-id"] == "abcd" }) @@ -2283,51 +2730,70 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertGreaterThan(secondRequestLogs.count, 0) XCTAssertTrue(secondRequestLogs.allSatisfy { $0.metadata["ahc-request-id"] == lastRequestID }) - logs.forEach { print($0) } + for log in logs { print(log) } } func testLoggingCorrectlyAttachesRequestInformation() { let logStore = CollectEverythingLogHandler.LogStore() - var loggerYolo001 = Logger(label: "\(#function)", factory: { _ in - CollectEverythingLogHandler(logStore: logStore) - }) + var loggerYolo001 = Logger( + label: "\(#function)", + factory: { _ in + CollectEverythingLogHandler(logStore: logStore) + } + ) loggerYolo001.logLevel = .trace loggerYolo001[metadataKey: "yolo-request-id"] = "yolo-001" - var loggerACME002 = Logger(label: "\(#function)", factory: { _ in - CollectEverythingLogHandler(logStore: logStore) - }) + var loggerACME002 = Logger( + label: "\(#function)", + factory: { _ in + CollectEverythingLogHandler(logStore: logStore) + } + ) loggerACME002.logLevel = .trace loggerACME002[metadataKey: "acme-request-id"] = "acme-002" guard let request1 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get"), - let request2 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "stats"), - let request3 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "ok") else { + let request2 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "stats"), + let request3 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "ok") + else { XCTFail("bad stuff, can't even make request structures") return } // === Request 1 (Yolo001) - XCTAssertNoThrow(try self.defaultClient.execute(request: request1, - eventLoop: .indifferent, - deadline: nil, - logger: loggerYolo001).wait()) + XCTAssertNoThrow( + try self.defaultClient.execute( + request: request1, + eventLoop: .indifferent, + deadline: nil, + logger: loggerYolo001 + ).wait() + ) let logsAfterReq1 = logStore.allEntries logStore.allEntries = [] // === Request 2 (Yolo001) - XCTAssertNoThrow(try self.defaultClient.execute(request: request2, - eventLoop: .indifferent, - deadline: nil, - logger: loggerYolo001).wait()) + XCTAssertNoThrow( + try self.defaultClient.execute( + request: request2, + eventLoop: .indifferent, + deadline: nil, + logger: loggerYolo001 + ).wait() + ) let logsAfterReq2 = logStore.allEntries logStore.allEntries = [] // === Request 3 (ACME002) - XCTAssertNoThrow(try self.defaultClient.execute(request: request3, - eventLoop: .indifferent, - deadline: nil, - logger: loggerACME002).wait()) + XCTAssertNoThrow( + try self.defaultClient.execute( + request: request3, + eventLoop: .indifferent, + deadline: nil, + logger: loggerACME002 + ).wait() + ) let logsAfterReq3 = logStore.allEntries logStore.allEntries = [] @@ -2336,176 +2802,238 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertGreaterThan(logsAfterReq2.count, 0) XCTAssertGreaterThan(logsAfterReq3.count, 0) - XCTAssert(logsAfterReq1.allSatisfy { entry in - if let httpRequestMetadata = entry.metadata["ahc-request-id"], - let yoloRequestID = entry.metadata["yolo-request-id"] { - XCTAssertNil(entry.metadata["acme-request-id"]) - XCTAssertEqual("yolo-001", yoloRequestID) - XCTAssertNotNil(Int(httpRequestMetadata)) - return true - } else { - XCTFail("log message doesn't contain the right IDs: \(entry)") - return false - } - }) - XCTAssert(logsAfterReq1.contains { entry in - // Since a new connection must be created first we expect that the request is queued - // and log message describing this is emitted. - entry.message == "Request was queued (waiting for a connection to become available)" - && entry.level == .debug - }) - XCTAssert(logsAfterReq1.contains { entry in - // After the new connection was created we expect a log message that describes that the - // request was scheduled on a connection. The connection id must be set from here on. - entry.message == "Request was scheduled on connection" - && entry.level == .debug - && entry.metadata["ahc-connection-id"] != nil - }) - - XCTAssert(logsAfterReq2.allSatisfy { entry in - if let httpRequestMetadata = entry.metadata["ahc-request-id"], - let yoloRequestID = entry.metadata["yolo-request-id"] { - XCTAssertNil(entry.metadata["acme-request-id"]) - XCTAssertEqual("yolo-001", yoloRequestID) - XCTAssertNotNil(Int(httpRequestMetadata)) - return true - } else { - XCTFail("log message doesn't contain the right IDs: \(entry)") - return false - } - }) - XCTAssertFalse(logsAfterReq2.contains { entry in - entry.message == "Request was queued (waiting for a connection to become available)" - }) - XCTAssert(logsAfterReq2.contains { entry in - entry.message == "Request was scheduled on connection" - && entry.level == .debug - && entry.metadata["ahc-connection-id"] != nil - }) - - XCTAssert(logsAfterReq3.allSatisfy { entry in - if let httpRequestMetadata = entry.metadata["ahc-request-id"], - let acmeRequestID = entry.metadata["acme-request-id"] { - XCTAssertNil(entry.metadata["yolo-request-id"]) - XCTAssertEqual("acme-002", acmeRequestID) - XCTAssertNotNil(Int(httpRequestMetadata)) - return true - } else { - XCTFail("log message doesn't contain the right IDs: \(entry)") - return false + XCTAssert( + logsAfterReq1.allSatisfy { entry in + if let httpRequestMetadata = entry.metadata["ahc-request-id"], + let yoloRequestID = entry.metadata["yolo-request-id"] + { + XCTAssertNil(entry.metadata["acme-request-id"]) + XCTAssertEqual("yolo-001", yoloRequestID) + XCTAssertNotNil(Int(httpRequestMetadata)) + return true + } else { + XCTFail("log message doesn't contain the right IDs: \(entry)") + return false + } } - }) - XCTAssertFalse(logsAfterReq3.contains { entry in - entry.message == "Request was queued (waiting for a connection to become available)" - }) - XCTAssert(logsAfterReq3.contains { entry in - entry.message == "Request was scheduled on connection" - && entry.level == .debug - && entry.metadata["ahc-connection-id"] != nil - }) + ) + XCTAssert( + logsAfterReq1.contains { entry in + // Since a new connection must be created first we expect that the request is queued + // and log message describing this is emitted. + entry.message == "Request was queued (waiting for a connection to become available)" + && entry.level == .debug + } + ) + XCTAssert( + logsAfterReq1.contains { entry in + // After the new connection was created we expect a log message that describes that the + // request was scheduled on a connection. The connection id must be set from here on. + entry.message == "Request was scheduled on connection" + && entry.level == .debug + && entry.metadata["ahc-connection-id"] != nil + } + ) + + XCTAssert( + logsAfterReq2.allSatisfy { entry in + if let httpRequestMetadata = entry.metadata["ahc-request-id"], + let yoloRequestID = entry.metadata["yolo-request-id"] + { + XCTAssertNil(entry.metadata["acme-request-id"]) + XCTAssertEqual("yolo-001", yoloRequestID) + XCTAssertNotNil(Int(httpRequestMetadata)) + return true + } else { + XCTFail("log message doesn't contain the right IDs: \(entry)") + return false + } + } + ) + XCTAssertFalse( + logsAfterReq2.contains { entry in + entry.message == "Request was queued (waiting for a connection to become available)" + } + ) + XCTAssert( + logsAfterReq2.contains { entry in + entry.message == "Request was scheduled on connection" + && entry.level == .debug + && entry.metadata["ahc-connection-id"] != nil + } + ) + + XCTAssert( + logsAfterReq3.allSatisfy { entry in + if let httpRequestMetadata = entry.metadata["ahc-request-id"], + let acmeRequestID = entry.metadata["acme-request-id"] + { + XCTAssertNil(entry.metadata["yolo-request-id"]) + XCTAssertEqual("acme-002", acmeRequestID) + XCTAssertNotNil(Int(httpRequestMetadata)) + return true + } else { + XCTFail("log message doesn't contain the right IDs: \(entry)") + return false + } + } + ) + XCTAssertFalse( + logsAfterReq3.contains { entry in + entry.message == "Request was queued (waiting for a connection to become available)" + } + ) + XCTAssert( + logsAfterReq3.contains { entry in + entry.message == "Request was scheduled on connection" + && entry.level == .debug + && entry.metadata["ahc-connection-id"] != nil + } + ) } func testNothingIsLoggedAtInfoOrHigher() { let logStore = CollectEverythingLogHandler.LogStore() - var logger = Logger(label: "\(#function)", factory: { _ in - CollectEverythingLogHandler(logStore: logStore) - }) + var logger = Logger( + label: "\(#function)", + factory: { _ in + CollectEverythingLogHandler(logStore: logStore) + } + ) logger.logLevel = .info guard let request1 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get"), - let request2 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "stats") else { + let request2 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "stats") + else { XCTFail("bad stuff, can't even make request structures") return } // === Request 1 - XCTAssertNoThrow(try self.defaultClient.execute(request: request1, - eventLoop: .indifferent, - deadline: nil, - logger: logger).wait()) + XCTAssertNoThrow( + try self.defaultClient.execute( + request: request1, + eventLoop: .indifferent, + deadline: nil, + logger: logger + ).wait() + ) XCTAssertEqual(0, logStore.allEntries.count) // === Request 2 - XCTAssertNoThrow(try self.defaultClient.execute(request: request2, - eventLoop: .indifferent, - deadline: nil, - logger: logger).wait()) + XCTAssertNoThrow( + try self.defaultClient.execute( + request: request2, + eventLoop: .indifferent, + deadline: nil, + logger: logger + ).wait() + ) XCTAssertEqual(0, logStore.allEntries.count) // === Synthesized Request - XCTAssertNoThrow(try self.defaultClient.execute(.GET, - url: self.defaultHTTPBinURLPrefix + "get", - body: nil, - deadline: nil, - logger: logger).wait()) + XCTAssertNoThrow( + try self.defaultClient.execute( + .GET, + url: self.defaultHTTPBinURLPrefix + "get", + body: nil, + deadline: nil, + logger: logger + ).wait() + ) XCTAssertEqual(0, logStore.allEntries.count) XCTAssertEqual(0, self.backgroundLogStore.allEntries.filter { $0.level >= .info }.count) // === Synthesized Socket Path Request - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let backgroundLogStore = CollectEverythingLogHandler.LogStore() - var backgroundLogger = Logger(label: "\(#function)", factory: { _ in - CollectEverythingLogHandler(logStore: backgroundLogStore) - }) - backgroundLogger.logLevel = .trace - - let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - backgroundActivityLogger: backgroundLogger) - defer { - XCTAssertNoThrow(try localClient.syncShutdown()) - XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) - } + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let backgroundLogStore = CollectEverythingLogHandler.LogStore() + var backgroundLogger = Logger( + label: "\(#function)", + factory: { _ in + CollectEverythingLogHandler(logStore: backgroundLogStore) + } + ) + backgroundLogger.logLevel = .trace - XCTAssertNoThrow(try localClient.execute(.GET, - socketPath: path, - urlPath: "get", - body: nil, - deadline: nil, - logger: logger).wait()) - XCTAssertEqual(0, logStore.allEntries.count) + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + backgroundActivityLogger: backgroundLogger + ) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } - XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .info }.count) - }) + XCTAssertNoThrow( + try localClient.execute( + .GET, + socketPath: path, + urlPath: "get", + body: nil, + deadline: nil, + logger: logger + ).wait() + ) + XCTAssertEqual(0, logStore.allEntries.count) - // === Synthesized Secure Socket Path Request - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let backgroundLogStore = CollectEverythingLogHandler.LogStore() - var backgroundLogger = Logger(label: "\(#function)", factory: { _ in - CollectEverythingLogHandler(logStore: backgroundLogStore) - }) - backgroundLogger.logLevel = .trace - - let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none), - backgroundActivityLogger: backgroundLogger) - defer { - XCTAssertNoThrow(try localClient.syncShutdown()) - XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .info }.count) } + ) + + // === Synthesized Secure Socket Path Request + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let backgroundLogStore = CollectEverythingLogHandler.LogStore() + var backgroundLogger = Logger( + label: "\(#function)", + factory: { _ in + CollectEverythingLogHandler(logStore: backgroundLogStore) + } + ) + backgroundLogger.logLevel = .trace - XCTAssertNoThrow(try localClient.execute(.GET, - secureSocketPath: path, - urlPath: "get", - body: nil, - deadline: nil, - logger: logger).wait()) - XCTAssertEqual(0, logStore.allEntries.count) + let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none), + backgroundActivityLogger: backgroundLogger + ) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } - XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .info }.count) - }) + XCTAssertNoThrow( + try localClient.execute( + .GET, + secureSocketPath: path, + urlPath: "get", + body: nil, + deadline: nil, + logger: logger + ).wait() + ) + XCTAssertEqual(0, logStore.allEntries.count) + + XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .info }.count) + } + ) } func testAllMethodsLog() { func checkExpectationsWithLogger(type: String, _ body: (Logger, String) throws -> T) throws -> T { let logStore = CollectEverythingLogHandler.LogStore() - var logger = Logger(label: "\(#function)", factory: { _ in - CollectEverythingLogHandler(logStore: logStore) - }) + var logger = Logger( + label: "\(#function)", + factory: { _ in + CollectEverythingLogHandler(logStore: logStore) + } + ) logger.logLevel = .trace logger[metadataKey: "req"] = "yo-\(type)" @@ -2513,86 +3041,125 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let result = try body(logger, url) XCTAssertGreaterThan(logStore.allEntries.count, 0) - logStore.allEntries.forEach { entry in + for entry in logStore.allEntries { XCTAssertEqual("yo-\(type)", entry.metadata["req"] ?? "n/a") XCTAssertNotNil(Int(entry.metadata["ahc-request-id"] ?? "n/a")) } return result } - XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in - try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() - }.status) + XCTAssertEqual( + .notFound, + try checkExpectationsWithLogger(type: "GET") { logger, url in + try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() + }.status + ) - XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "PUT") { logger, url in - try self.defaultClient.put(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() - }.status) + XCTAssertEqual( + .notFound, + try checkExpectationsWithLogger(type: "PUT") { logger, url in + try self.defaultClient.put(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() + }.status + ) - XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "POST") { logger, url in - try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() - }.status) + XCTAssertEqual( + .notFound, + try checkExpectationsWithLogger(type: "POST") { logger, url in + try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() + }.status + ) - XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "DELETE") { logger, url in - try self.defaultClient.delete(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() - }.status) + XCTAssertEqual( + .notFound, + try checkExpectationsWithLogger(type: "DELETE") { logger, url in + try self.defaultClient.delete(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() + }.status + ) - XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "PATCH") { logger, url in - try self.defaultClient.patch(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() - }.status) + XCTAssertEqual( + .notFound, + try checkExpectationsWithLogger(type: "PATCH") { logger, url in + try self.defaultClient.patch(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() + }.status + ) - XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "CHECKOUT") { logger, url in - try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() - }.status) + XCTAssertEqual( + .notFound, + try checkExpectationsWithLogger(type: "CHECKOUT") { logger, url in + try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + url, logger: logger) + .wait() + }.status + ) // No background activity expected here. XCTAssertEqual(0, self.backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let backgroundLogStore = CollectEverythingLogHandler.LogStore() - var backgroundLogger = Logger(label: "\(#function)", factory: { _ in - CollectEverythingLogHandler(logStore: backgroundLogStore) - }) - backgroundLogger.logLevel = .trace + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let backgroundLogStore = CollectEverythingLogHandler.LogStore() + var backgroundLogger = Logger( + label: "\(#function)", + factory: { _ in + CollectEverythingLogHandler(logStore: backgroundLogStore) + } + ) + backgroundLogger.logLevel = .trace - let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - backgroundActivityLogger: backgroundLogger) - defer { - XCTAssertNoThrow(try localClient.syncShutdown()) - XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) - } + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + backgroundActivityLogger: backgroundLogger + ) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } - XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in - try localClient.execute(socketPath: path, urlPath: url, logger: logger).wait() - }.status) + XCTAssertEqual( + .notFound, + try checkExpectationsWithLogger(type: "GET") { logger, url in + try localClient.execute(socketPath: path, urlPath: url, logger: logger).wait() + }.status + ) - // No background activity expected here. - XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) - }) + // No background activity expected here. + XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) + } + ) - XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in - let backgroundLogStore = CollectEverythingLogHandler.LogStore() - var backgroundLogger = Logger(label: "\(#function)", factory: { _ in - CollectEverythingLogHandler(logStore: backgroundLogStore) - }) - backgroundLogger.logLevel = .trace + XCTAssertNoThrow( + try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in + let backgroundLogStore = CollectEverythingLogHandler.LogStore() + var backgroundLogger = Logger( + label: "\(#function)", + factory: { _ in + CollectEverythingLogHandler(logStore: backgroundLogStore) + } + ) + backgroundLogger.logLevel = .trace - let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none), - backgroundActivityLogger: backgroundLogger) - defer { - XCTAssertNoThrow(try localClient.syncShutdown()) - XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) - } + let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none), + backgroundActivityLogger: backgroundLogger + ) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) + } - XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in - try localClient.execute(secureSocketPath: path, urlPath: url, logger: logger).wait() - }.status) + XCTAssertEqual( + .notFound, + try checkExpectationsWithLogger(type: "GET") { logger, url in + try localClient.execute(secureSocketPath: path, urlPath: url, logger: logger).wait() + }.status + ) - // No background activity expected here. - XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) - }) + // No background activity expected here. + XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) + } + ) } func testClosingIdleConnectionsInPoolLogsInTheBackground() { @@ -2601,16 +3168,19 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertNoThrow(try self.defaultClient.syncShutdown()) XCTAssertGreaterThanOrEqual(self.backgroundLogStore.allEntries.count, 0) - XCTAssert(self.backgroundLogStore.allEntries.contains { entry in - entry.message == "Shutting down connection pool" - }) - XCTAssert(self.backgroundLogStore.allEntries.allSatisfy { entry in - entry.metadata["ahc-request-id"] == nil && - entry.metadata["ahc-request"] == nil && - entry.metadata["ahc-pool-key"] != nil - }) + XCTAssert( + self.backgroundLogStore.allEntries.contains { entry in + entry.message == "Shutting down connection pool" + } + ) + XCTAssert( + self.backgroundLogStore.allEntries.allSatisfy { entry in + entry.metadata["ahc-request-id"] == nil && entry.metadata["ahc-request"] == nil + && entry.metadata["ahc-pool-key"] != nil + } + ) - self.defaultClient = nil // so it doesn't get shut down again. + self.defaultClient = nil // so it doesn't get shut down again. } func testUploadStreamingNoLength() throws { @@ -2635,8 +3205,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTFail("Unexpected part") } - XCTAssertNoThrow(try server.readInbound()) // .body - XCTAssertNoThrow(try server.readInbound()) // .end + XCTAssertNoThrow(try server.readInbound()) // .body + XCTAssertNoThrow(try server.readInbound()) // .end XCTAssertNoThrow(try server.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) XCTAssertNoThrow(try server.writeOutbound(.end(nil))) @@ -2654,8 +3224,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } } - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(timeout: .init(connect: .milliseconds(10)))) + let httpClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(timeout: .init(connect: .milliseconds(10))) + ) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) @@ -2681,11 +3253,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func didReceiveHead(task: HTTPClient.Task, _: HTTPResponseHead) -> EventLoopFuture { - return self.eventLoop.makeSucceededFuture(()) + self.eventLoop.makeSucceededFuture(()) } func didReceiveBodyPart(task: HTTPClient.Task, _: ByteBuffer) -> EventLoopFuture { - return self.eventLoop.makeSucceededFuture(()) + self.eventLoop.makeSucceededFuture(()) } func didFinishRequest(task: HTTPClient.Task) throws {} @@ -2708,8 +3280,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let request = try HTTPClient.Request(url: "http://localhost:\(httpServer.serverPort)/") let future = httpClient.execute(request: request, delegate: delegate) - XCTAssertNoThrow(try httpServer.readInbound()) // .head - XCTAssertNoThrow(try httpServer.readInbound()) // .end + XCTAssertNoThrow(try httpServer.readInbound()) // .head + XCTAssertNoThrow(try httpServer.readInbound()) // .end XCTAssertNoThrow(try httpServer.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) XCTAssertNoThrow(try httpServer.writeOutbound(.body(.byteBuffer(ByteBuffer(string: "1234"))))) @@ -2721,15 +3293,20 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testContentLengthTooLongFails() throws { let url = self.defaultHTTPBinURLPrefix + "post" XCTAssertThrowsError( - try self.defaultClient.execute(request: - Request(url: url, + try self.defaultClient.execute( + request: + Request( + url: url, body: .stream(contentLength: 10) { streamWriter in let promise = self.defaultClient.eventLoopGroup.next().makePromise(of: Void.self) DispatchQueue(label: "content-length-test").async { streamWriter.write(.byteBuffer(ByteBuffer(string: "1"))).cascade(to: promise) } return promise.futureResult - })).wait()) { error in + } + ) + ).wait() + ) { error in XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) } // Quickly try another request and check that it works. @@ -2751,11 +3328,16 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { let url = self.defaultHTTPBinURLPrefix + "post" let tooLong = "XBAD BAD BAD NOT HTTP/1.1\r\n\r\n" XCTAssertThrowsError( - try self.defaultClient.execute(request: - Request(url: url, + try self.defaultClient.execute( + request: + Request( + url: url, body: .stream(contentLength: 1) { streamWriter in streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong))) - })).wait()) { error in + } + ) + ).wait() + ) { error in XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) } // Quickly try another request and check that it works. If we by accident wrote some extra bytes into the @@ -2829,7 +3411,9 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // We specify a deadline of 2 ms co that request will be timed out before all chunks are writtent, // we need to verify that second error on write after timeout does not lead to double-release. - XCTAssertThrowsError(try self.defaultClient.execute(request: request, deadline: .now() + .milliseconds(2)).wait()) + XCTAssertThrowsError( + try self.defaultClient.execute(request: request, deadline: .now() + .milliseconds(2)).wait() + ) } func testSSLHandshakeErrorPropagation() throws { @@ -3041,10 +3625,12 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { var request = try Request(url: httpBin.baseURL) request.body = .byteBuffer(body) - XCTAssertThrowsError(try self.defaultClient.execute( - request: request, - delegate: ResponseAccumulator(request: request, maxBodySize: 10) - ).wait()) { error in + XCTAssertThrowsError( + try self.defaultClient.execute( + request: request, + delegate: ResponseAccumulator(request: request, maxBodySize: 10) + ).wait() + ) { error in XCTAssertTrue(error is ResponseAccumulator.ResponseTooBigError, "unexpected error \(error)") } } @@ -3091,10 +3677,12 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { request.body = .stream { writer in writer.write(.byteBuffer(body)) } - XCTAssertThrowsError(try self.defaultClient.execute( - request: request, - delegate: ResponseAccumulator(request: request, maxBodySize: 10) - ).wait()) { error in + XCTAssertThrowsError( + try self.defaultClient.execute( + request: request, + delegate: ResponseAccumulator(request: request, maxBodySize: 10) + ).wait() + ) { error in XCTAssertTrue(error is ResponseAccumulator.ResponseTooBigError, "unexpected error \(error)") } } @@ -3120,7 +3708,9 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // In this test, we test that a request can continue to stream its body after the response head and end // was received where the end is a 200. func testBiDirectionalStreamingEarly200() { - let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTP200DelayedHandler(bodyPartsBeforeResponse: 1) } + let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in + HTTP200DelayedHandler(bodyPartsBeforeResponse: 1) + } defer { XCTAssertNoThrow(try httpBin.shutdown()) } let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) @@ -3174,7 +3764,9 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // This test is identical to the one above, except that we send another request immediately after. This is a regression // test for https://github.com/swift-server/async-http-client/issues/595. func testBiDirectionalStreamingEarly200DoesntPreventUsFromSendingMoreRequests() { - let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTP200DelayedHandler(bodyPartsBeforeResponse: 1) } + let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in + HTTP200DelayedHandler(bodyPartsBeforeResponse: 1) + } defer { XCTAssertNoThrow(try httpBin.shutdown()) } let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) @@ -3232,7 +3824,9 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let onClosePromise = eventLoopGroup.next().makePromise(of: Void.self) - let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in ExpectClosureServerHandler(onClosePromise: onClosePromise) } + let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in + ExpectClosureServerHandler(onClosePromise: onClosePromise) + } defer { XCTAssertNoThrow(try httpBin.shutdown()) } let writeEL = eventLoopGroup.next() @@ -3290,8 +3884,10 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { tlsConfig.maximumTLSVersion = .tlsv12 tlsConfig.certificateVerification = .none let localHTTPBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(tlsConfiguration: tlsConfig)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(tlsConfiguration: tlsConfig) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) @@ -3366,12 +3962,16 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testRequestSpecificTLS() throws { - let configuration = HTTPClient.Configuration(tlsConfiguration: nil, - timeout: .init(), - decompression: .disabled) + let configuration = HTTPClient.Configuration( + tlsConfiguration: nil, + timeout: .init(), + decompression: .disabled + ) let localHTTPBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: configuration) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: configuration + ) let decoder = JSONDecoder() defer { @@ -3382,7 +3982,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // First two requests use identical TLS configurations. var tlsConfig = TLSConfiguration.makeClientConfiguration() tlsConfig.certificateVerification = .none - let firstRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: tlsConfig) + let firstRequest = try HTTPClient.Request( + url: "https://localhost:\(localHTTPBin.port)/get", + method: .GET, + tlsConfiguration: tlsConfig + ) let firstResponse = try localClient.execute(request: firstRequest).wait() guard let firstBody = firstResponse.body else { XCTFail("No request body found") @@ -3390,7 +3994,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } let firstConnectionNumber = try decoder.decode(RequestInfo.self, from: firstBody).connectionNumber - let secondRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: tlsConfig) + let secondRequest = try HTTPClient.Request( + url: "https://localhost:\(localHTTPBin.port)/get", + method: .GET, + tlsConfiguration: tlsConfig + ) let secondResponse = try localClient.execute(request: secondRequest).wait() guard let secondBody = secondResponse.body else { XCTFail("No request body found") @@ -3402,7 +4010,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { var tlsConfig2 = TLSConfiguration.makeClientConfiguration() tlsConfig2.certificateVerification = .none tlsConfig2.maximumTLSVersion = .tlsv1 - let thirdRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: tlsConfig2) + let thirdRequest = try HTTPClient.Request( + url: "https://localhost:\(localHTTPBin.port)/get", + method: .GET, + tlsConfiguration: tlsConfig2 + ) let thirdResponse = try localClient.execute(request: thirdRequest).wait() guard let thirdBody = thirdResponse.body else { XCTFail("No request body found") @@ -3413,8 +4025,16 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { XCTAssertEqual(firstResponse.status, .ok) XCTAssertEqual(secondResponse.status, .ok) XCTAssertEqual(thirdResponse.status, .ok) - XCTAssertEqual(firstConnectionNumber, secondConnectionNumber, "Identical TLS configurations did not use the same connection") - XCTAssertNotEqual(thirdConnectionNumber, firstConnectionNumber, "Different TLS configurations did not use different connections.") + XCTAssertEqual( + firstConnectionNumber, + secondConnectionNumber, + "Identical TLS configurations did not use the same connection" + ) + XCTAssertNotEqual( + thirdConnectionNumber, + firstConnectionNumber, + "Different TLS configurations did not use different connections." + ) } func testRequestWithHeaderTransferEncodingIdentityDoesNotFail() { @@ -3439,7 +4059,9 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testMassiveDownload() { var response: HTTPClient.Response? - XCTAssertNoThrow(response = try self.defaultClient.get(url: "\(self.defaultHTTPBinURLPrefix)mega-chunked").wait()) + XCTAssertNoThrow( + response = try self.defaultClient.get(url: "\(self.defaultHTTPBinURLPrefix)mega-chunked").wait() + ) XCTAssertEqual(.ok, response?.status) XCTAssertEqual(response?.version, .http1_1) @@ -3466,11 +4088,13 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } func testMassiveHeaderHTTP2() throws { - let bin = HTTPBin(.http2(settings: [ - .init(parameter: .maxConcurrentStreams, value: 100), - .init(parameter: .maxHeaderListSize, value: 1024 * 256), - .init(parameter: .maxFrameSize, value: 1024 * 256), - ])) + let bin = HTTPBin( + .http2(settings: [ + .init(parameter: .maxConcurrentStreams, value: 100), + .init(parameter: .maxHeaderListSize, value: 1024 * 256), + .init(parameter: .maxFrameSize, value: 1024 * 256), + ]) + ) defer { XCTAssertNoThrow(try bin.shutdown()) } let client = HTTPClient( @@ -3604,7 +4228,9 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { } let response = try client.get(url: self.defaultHTTPBinURLPrefix + "get").wait() XCTAssertEqual(.ok, response.status) - } catch let error as IOError where error.errnoCode == EINVAL || error.errnoCode == EPROTONOSUPPORT || error.errnoCode == ENOPROTOOPT { + } catch let error as IOError + where error.errnoCode == EINVAL || error.errnoCode == EPROTONOSUPPORT || error.errnoCode == ENOPROTOOPT + { // some old Linux kernels don't support MPTCP, skip this test in this case // see https://www.mptcp.dev/implementation.html for details about each type // of error @@ -3643,18 +4269,18 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // ! is safe, assigned above request.tlsConfiguration!.certificateVerification = .none - let response1 = try await client.execute(request, timeout: /* infinity */ .hours(99)) + let response1 = try await client.execute(request, timeout: .hours(99)) // 99h ~= infinity XCTAssertEqual(.ok, response1.status) // For the second request, we reset the TLS config request.tlsConfiguration = nil do { - let response2 = try await client.execute(request, timeout: /* infinity */ .hours(99)) + let response2 = try await client.execute(request, timeout: .hours(99)) // 99h ~= infinity XCTFail("shouldn't succeed, self-signed cert: \(response2)") } catch { switch error as? NIOSSLError { case .some(.handshakeFailed(_)): - () // ok + () // ok default: XCTFail("unexpected error: \(error)") } @@ -3665,7 +4291,7 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { // ! is safe, assigned above request.tlsConfiguration!.certificateVerification = .none - let response3 = try await client.execute(request, timeout: /* infinity */ .hours(99)) + let response3 = try await client.execute(request, timeout: .hours(99)) // 99h ~= infinity XCTAssertEqual(.ok, response3.status) } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientUncleanSSLConnectionShutdownTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientUncleanSSLConnectionShutdownTests.swift index 854d9092c..b63eb7cba 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientUncleanSSLConnectionShutdownTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientUncleanSSLConnectionShutdownTests.swift @@ -155,7 +155,8 @@ final class HTTPClientUncleanSSLConnectionShutdownTests: XCTestCase { ) defer { XCTAssertNoThrow(try client.syncShutdown()) } - XCTAssertThrowsError(try client.get(url: "https://localhost:\(httpBin.port)/transferencodingtruncated").wait()) { + XCTAssertThrowsError(try client.get(url: "https://localhost:\(httpBin.port)/transferencodingtruncated").wait()) + { XCTAssertEqual($0 as? HTTPParserError, .invalidEOFState) } } @@ -184,7 +185,7 @@ final class HTTPBinForSSLUncleanShutdown { let serverChannel: Channel var port: Int { - return Int(self.serverChannel.localAddress!.port!) + Int(self.serverChannel.localAddress!.port!) } init() { @@ -231,61 +232,61 @@ private final class HTTPBinForSSLUncleanShutdownHandler: ChannelInboundHandler { switch req.uri { case "/nocontentlength": response = """ - HTTP/1.1 200 OK\r\n\ - Connection: close\r\n\ - \r\n\ - foo - """ + HTTP/1.1 200 OK\r\n\ + Connection: close\r\n\ + \r\n\ + foo + """ case "/nocontent": response = """ - HTTP/1.1 204 OK\r\n\ - Connection: close\r\n\ - \r\n - """ + HTTP/1.1 204 OK\r\n\ + Connection: close\r\n\ + \r\n + """ case "/noresponse": response = nil case "/wrongcontentlength": response = """ - HTTP/1.1 200 OK\r\n\ - Connection: close\r\n\ - Content-Length: 6\r\n\ - \r\n\ - foo - """ + HTTP/1.1 200 OK\r\n\ + Connection: close\r\n\ + Content-Length: 6\r\n\ + \r\n\ + foo + """ case "/transferencoding": response = """ - HTTP/1.1 200 OK\r\n\ - Connection: close\r\n\ - Transfer-Encoding: chunked\r\n\ - \r\n\ - 3\r\n\ - foo\r\n\ - 0\r\n\ - \r\n - """ + HTTP/1.1 200 OK\r\n\ + Connection: close\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 3\r\n\ + foo\r\n\ + 0\r\n\ + \r\n + """ case "/transferencodingtruncated": response = """ - HTTP/1.1 200 OK\r\n\ - Connection: close\r\n\ - Transfer-Encoding: chunked\r\n\ - \r\n\ - 12\r\n\ - foo - """ + HTTP/1.1 200 OK\r\n\ + Connection: close\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 12\r\n\ + foo + """ default: response = """ - HTTP/1.1 404 OK\r\n\ - Connection: close\r\n\ - Content-Length: 9\r\n\ - \r\n\ - Not Found - """ + HTTP/1.1 404 OK\r\n\ + Connection: close\r\n\ + Content-Length: 9\r\n\ + \r\n\ + Not Found + """ } if let response = response { diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+FactoryTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+FactoryTests.swift index 476584972..d9dbd4cb1 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+FactoryTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+FactoryTests.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOPosix @@ -20,18 +19,22 @@ import NIOSOCKS import NIOSSL import XCTest +@testable import AsyncHTTPClient + class HTTPConnectionPool_FactoryTests: XCTestCase { func testConnectionCreationTimesoutIfDeadlineIsInThePast() { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } var server: Channel? - XCTAssertNoThrow(server = try ServerBootstrap(group: group) - .childChannelInitializer { channel in - channel.pipeline.addHandler(NeverrespondServerHandler()) - } - .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) - .wait()) + XCTAssertNoThrow( + server = try ServerBootstrap(group: group) + .childChannelInitializer { channel in + channel.pipeline.addHandler(NeverrespondServerHandler()) + } + .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) + .wait() + ) defer { XCTAssertNoThrow(try server?.close().wait()) } @@ -45,13 +48,14 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { sslContextCache: .init() ) - XCTAssertThrowsError(try factory.makeChannel( - requester: ExplodingRequester(), - connectionID: 1, - deadline: .now() - .seconds(1), - eventLoop: group.next(), - logger: .init(label: "test") - ).wait() + XCTAssertThrowsError( + try factory.makeChannel( + requester: ExplodingRequester(), + connectionID: 1, + deadline: .now() - .seconds(1), + eventLoop: group.next(), + logger: .init(label: "test") + ).wait() ) { XCTAssertEqual($0 as? HTTPClientError, .connectTimeout) } @@ -62,12 +66,14 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } var server: Channel? - XCTAssertNoThrow(server = try ServerBootstrap(group: group) - .childChannelInitializer { channel in - channel.pipeline.addHandler(NeverrespondServerHandler()) - } - .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) - .wait()) + XCTAssertNoThrow( + server = try ServerBootstrap(group: group) + .childChannelInitializer { channel in + channel.pipeline.addHandler(NeverrespondServerHandler()) + } + .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) + .wait() + ) defer { XCTAssertNoThrow(try server?.close().wait()) } @@ -82,13 +88,14 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { sslContextCache: .init() ) - XCTAssertThrowsError(try factory.makeChannel( - requester: ExplodingRequester(), - connectionID: 1, - deadline: .now() + .seconds(1), - eventLoop: group.next(), - logger: .init(label: "test") - ).wait() + XCTAssertThrowsError( + try factory.makeChannel( + requester: ExplodingRequester(), + connectionID: 1, + deadline: .now() + .seconds(1), + eventLoop: group.next(), + logger: .init(label: "test") + ).wait() ) { XCTAssertEqual($0 as? HTTPClientError, .socksHandshakeTimeout) } @@ -99,12 +106,14 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } var server: Channel? - XCTAssertNoThrow(server = try ServerBootstrap(group: group) - .childChannelInitializer { channel in - channel.pipeline.addHandler(NeverrespondServerHandler()) - } - .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) - .wait()) + XCTAssertNoThrow( + server = try ServerBootstrap(group: group) + .childChannelInitializer { channel in + channel.pipeline.addHandler(NeverrespondServerHandler()) + } + .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) + .wait() + ) defer { XCTAssertNoThrow(try server?.close().wait()) } @@ -119,13 +128,14 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { sslContextCache: .init() ) - XCTAssertThrowsError(try factory.makeChannel( - requester: ExplodingRequester(), - connectionID: 1, - deadline: .now() + .seconds(1), - eventLoop: group.next(), - logger: .init(label: "test") - ).wait() + XCTAssertThrowsError( + try factory.makeChannel( + requester: ExplodingRequester(), + connectionID: 1, + deadline: .now() + .seconds(1), + eventLoop: group.next(), + logger: .init(label: "test") + ).wait() ) { XCTAssertEqual($0 as? HTTPClientError, .httpProxyHandshakeTimeout) } @@ -136,12 +146,14 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } var server: Channel? - XCTAssertNoThrow(server = try ServerBootstrap(group: group) - .childChannelInitializer { channel in - channel.pipeline.addHandler(NeverrespondServerHandler()) - } - .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) - .wait()) + XCTAssertNoThrow( + server = try ServerBootstrap(group: group) + .childChannelInitializer { channel in + channel.pipeline.addHandler(NeverrespondServerHandler()) + } + .bind(to: .init(ipAddress: "127.0.0.1", port: 0)) + .wait() + ) defer { XCTAssertNoThrow(try server?.close().wait()) } @@ -158,13 +170,14 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { sslContextCache: .init() ) - XCTAssertThrowsError(try factory.makeChannel( - requester: ExplodingRequester(), - connectionID: 1, - deadline: .now() + .seconds(1), - eventLoop: group.next(), - logger: .init(label: "test") - ).wait() + XCTAssertThrowsError( + try factory.makeChannel( + requester: ExplodingRequester(), + connectionID: 1, + deadline: .now() + .seconds(1), + eventLoop: group.next(), + logger: .init(label: "test") + ).wait() ) { XCTAssertEqual($0 as? HTTPClientError, .tlsHandshakeTimeout) } diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1ConnectionsTest.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1ConnectionsTest.swift index dfeaf1d9c..914990048 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1ConnectionsTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1ConnectionsTest.swift @@ -12,15 +12,20 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOEmbedded import XCTest +@testable import AsyncHTTPClient + class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testCreatingConnections() { let elg = EmbeddedEventLoopGroup(loops: 4) - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) let el1 = elg.next() let el2 = elg.next() @@ -52,7 +57,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testCreatingConnectionAndFailing() { let elg = EmbeddedEventLoopGroup(loops: 4) - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) let el1 = elg.next() let el2 = elg.next() @@ -103,7 +112,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { let el3 = elg.next() let el4 = elg.next() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) for el in [el1, el2, el3, el4] { XCTAssertEqual(connections.startingGeneralPurposeConnections, 0) @@ -130,7 +143,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { let el4 = elg.next() let el5 = elg.next() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) for el in [el1, el2, el3, el4] { XCTAssertEqual(connections.startingGeneralPurposeConnections, 0) @@ -157,7 +174,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { let el4 = elg.next() let el5 = elg.next() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) for el in [el1, el2, el3, el4] { XCTAssertEqual(connections.startingGeneralPurposeConnections, 0) @@ -181,7 +202,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { let el1 = elg.next() let el2 = elg.next() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) for el in [el1, el1, el1, el1, el2] { let connID = connections.createNewConnection(on: el) @@ -228,7 +253,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testCloseConnectionIfIdle() { let elg = EmbeddedEventLoopGroup(loops: 1) - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) let el1 = elg.next() @@ -248,7 +277,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testCloseConnectionIfIdleButLeasedRaceCondition() { let elg = EmbeddedEventLoopGroup(loops: 1) - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) let el1 = elg.next() @@ -267,7 +300,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testCloseConnectionIfIdleButClosedRaceCondition() { let elg = EmbeddedEventLoopGroup(loops: 1) - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) let el1 = elg.next() @@ -288,7 +325,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { let el3 = elg.next() let el4 = elg.next() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: .init(), maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: .init(), + maximumConnectionUses: nil + ) for el in [el1, el2, el3, el4] { let connID = connections.createNewConnection(on: el) @@ -343,7 +384,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testMigrationFromHTTP2() { let elg = EmbeddedEventLoopGroup(loops: 4) let generator = HTTPConnectionPool.Connection.ID.Generator() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: generator, + maximumConnectionUses: nil + ) let el1 = elg.next() let el2 = elg.next() @@ -372,7 +417,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testMigrationFromHTTP2WithPendingRequestsWithRequiredEventLoop() { let elg = EmbeddedEventLoopGroup(loops: 4) let generator = HTTPConnectionPool.Connection.ID.Generator() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: generator, + maximumConnectionUses: nil + ) let el1 = elg.next() let el2 = elg.next() @@ -411,7 +460,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testMigrationFromHTTP2WithPendingRequestsWithRequiredEventLoopSameAsStartingConnections() { let elg = EmbeddedEventLoopGroup(loops: 4) let generator = HTTPConnectionPool.Connection.ID.Generator() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: generator, + maximumConnectionUses: nil + ) let el1 = elg.next() let el2 = elg.next() @@ -439,7 +492,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testMigrationFromHTTP2WithPendingRequestsWithPreferredEventLoop() { let elg = EmbeddedEventLoopGroup(loops: 4) let generator = HTTPConnectionPool.Connection.ID.Generator() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: generator, + maximumConnectionUses: nil + ) let el1 = elg.next() let el2 = elg.next() @@ -478,7 +535,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testMigrationFromHTTP2WithAlreadyLeasedHTTP1Connection() { let elg = EmbeddedEventLoopGroup(loops: 4) let generator = HTTPConnectionPool.Connection.ID.Generator() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: generator, + maximumConnectionUses: nil + ) let el1 = elg.next() let el2 = elg.next() let el3 = elg.next() @@ -522,7 +583,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testMigrationFromHTTP2WithMoreStartingConnectionsThanMaximumAllowedConccurentConnections() { let elg = EmbeddedEventLoopGroup(loops: 4) let generator = HTTPConnectionPool.Connection.ID.Generator() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 2, generator: generator, maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 2, + generator: generator, + maximumConnectionUses: nil + ) let el1 = elg.next() let el2 = elg.next() @@ -557,7 +622,11 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { func testMigrationFromHTTP2StartsEnoghOverflowConnectionsForRequiredEventLoopRequests() { let elg = EmbeddedEventLoopGroup(loops: 4) let generator = HTTPConnectionPool.Connection.ID.Generator() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 1, generator: generator, maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 1, + generator: generator, + maximumConnectionUses: nil + ) let el1 = elg.next() let el2 = elg.next() @@ -599,16 +668,23 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { let el2 = elg.next() let generator = HTTPConnectionPool.Connection.ID.Generator() - var connections = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: generator, maximumConnectionUses: nil) + var connections = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: generator, + maximumConnectionUses: nil + ) let connID1 = connections.createNewConnection(on: el1) let context = connections.migrateToHTTP2() - XCTAssertEqual(context, .init( - backingOff: [], - starting: [(connID1, el1)], - close: [] - )) + XCTAssertEqual( + context, + .init( + backingOff: [], + starting: [(connID1, el1)], + close: [] + ) + ) let connID2 = generator.next() @@ -626,8 +702,7 @@ class HTTPConnectionPool_HTTP1ConnectionsTests: XCTestCase { extension HTTPConnectionPool.HTTP1Connections.HTTP1ToHTTP2MigrationContext: Equatable { public static func == (lhs: Self, rhs: Self) -> Bool { - return lhs.close == rhs.close && - lhs.starting.elementsEqual(rhs.starting, by: { $0.0 == $1.0 && $0.1 === $1.1 }) && - lhs.backingOff.elementsEqual(rhs.backingOff, by: { $0.0 == $1.0 && $0.1 === $1.1 }) + lhs.close == rhs.close && lhs.starting.elementsEqual(rhs.starting, by: { $0.0 == $1.0 && $0.1 === $1.1 }) + && lhs.backingOff.elementsEqual(rhs.backingOff, by: { $0.0 == $1.0 && $0.1 === $1.1 }) } } diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1StateTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1StateTests.swift index 367fdaffb..2be6cfa26 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1StateTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP1StateTests.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOEmbedded import NIOHTTP1 import NIOPosix import XCTest +@testable import AsyncHTTPClient + class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { func testCreatingAndFailingConnections() { struct SomeError: Error, Equatable {} @@ -197,9 +198,12 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { guard case .createConnection(let connectionID, on: let connectionEL) = action.connection else { return XCTFail("Unexpected connection action: \(action.connection)") } - XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux - let failedConnect1 = state.failedToCreateNewConnection(HTTPClientError.connectTimeout, connectionID: connectionID) + let failedConnect1 = state.failedToCreateNewConnection( + HTTPClientError.connectTimeout, + connectionID: connectionID + ) XCTAssertEqual(failedConnect1.request, .none) guard case .scheduleBackoffTimer(connectionID, let backoffTimeAmount1, _) = failedConnect1.connection else { return XCTFail("Unexpected connection action: \(failedConnect1.connection)") @@ -212,9 +216,12 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { return XCTFail("Unexpected connection action: \(backoffDoneAction.connection)") } XCTAssertGreaterThan(newConnectionID, connectionID) - XCTAssert(connectionEL === newEventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === newEventLoop) // XCTAssertIdentical not available on Linux - let failedConnect2 = state.failedToCreateNewConnection(HTTPClientError.connectTimeout, connectionID: newConnectionID) + let failedConnect2 = state.failedToCreateNewConnection( + HTTPClientError.connectTimeout, + connectionID: newConnectionID + ) XCTAssertEqual(failedConnect2.request, .none) guard case .scheduleBackoffTimer(newConnectionID, let backoffTimeAmount2, _) = failedConnect2.connection else { return XCTFail("Unexpected connection action: \(failedConnect2.connection)") @@ -227,7 +234,9 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { guard case .failRequest(let requestToFail, let requestError, cancelTimeout: false) = failRequest.request else { return XCTFail("Unexpected request action: \(action.request)") } - XCTAssert(requestToFail.__testOnly_wrapped_request() === mockRequest) // XCTAssertIdentical not available on Linux + + // XCTAssertIdentical not available on Linux + XCTAssert(requestToFail.__testOnly_wrapped_request() === mockRequest) XCTAssertEqual(requestError as? HTTPClientError, .connectTimeout) XCTAssertEqual(failRequest.connection, .none) @@ -257,7 +266,7 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { guard case .createConnection(let connectionID, on: let connectionEL) = executeAction.connection else { return XCTFail("Unexpected connection action: \(executeAction.connection)") } - XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux // 2. cancel request @@ -269,7 +278,9 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssertEqual(state.timeoutRequest(request.id), .none, "To late timeout is ignored") // 4. succeed connection attempt - let connectedAction = state.newHTTP1ConnectionCreated(.__testOnly_connection(id: connectionID, eventLoop: connectionEL)) + let connectedAction = state.newHTTP1ConnectionCreated( + .__testOnly_connection(id: connectionID, eventLoop: connectionEL) + ) XCTAssertEqual(connectedAction.request, .none, "Request must not be executed") XCTAssertEqual(connectedAction.connection, .scheduleTimeoutTimer(connectionID, on: connectionEL)) } @@ -296,15 +307,18 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { guard case .createConnection(let connectionID, on: let connectionEL) = executeAction.connection else { return XCTFail("Unexpected connection action: \(executeAction.connection)") } - XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux // 2. connection succeeds - let connection: HTTPConnectionPool.Connection = .__testOnly_connection(id: connectionID, eventLoop: connectionEL) + let connection: HTTPConnectionPool.Connection = .__testOnly_connection( + id: connectionID, + eventLoop: connectionEL + ) let connectedAction = state.newHTTP1ConnectionCreated(connection) guard case .executeRequest(request, connection, cancelTimeout: true) = connectedAction.request else { return XCTFail("Unexpected request action: \(connectedAction.request)") } - XCTAssert(request.__testOnly_wrapped_request() === mockRequest) // XCTAssertIdentical not available on Linux + XCTAssert(request.__testOnly_wrapped_request() === mockRequest) // XCTAssertIdentical not available on Linux XCTAssertEqual(connectedAction.connection, .none) // 3. shutdown @@ -324,7 +338,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { let finalRequest = HTTPConnectionPool.Request(finalMockRequest) let failAction = state.executeRequest(finalRequest) XCTAssertEqual(failAction.connection, .none) - XCTAssertEqual(failAction.request, .failRequest(finalRequest, HTTPClientError.alreadyShutdown, cancelTimeout: false)) + XCTAssertEqual( + failAction.request, + .failRequest(finalRequest, HTTPClientError.alreadyShutdown, cancelTimeout: false) + ) // 5. close open connection let closeAction = state.http1ConnectionClosed(connectionID) @@ -345,7 +362,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { // Add eight requests to fill all connections for _ in 0..<8 { let eventLoop = elg.next() - guard let expectedConnection = connections.newestParkedConnection(for: eventLoop) ?? connections.newestParkedConnection else { + guard + let expectedConnection = connections.newestParkedConnection(for: eventLoop) + ?? connections.newestParkedConnection + else { return XCTFail("Expected to still have connections available") } @@ -354,7 +374,8 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { let action = state.executeRequest(request) XCTAssertEqual(action.connection, .cancelTimeoutTimer(expectedConnection.id)) - guard case .executeRequest(let returnedRequest, expectedConnection, cancelTimeout: false) = action.request else { + guard case .executeRequest(let returnedRequest, expectedConnection, cancelTimeout: false) = action.request + else { return XCTFail("Expected to execute a request next, but got: \(action.request)") } @@ -428,7 +449,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { // 10% of the cases enforce the eventLoop let elRequired = (0..<10).randomElement().flatMap { $0 == 0 ? true : false }! - let mockRequest = MockHTTPScheduableRequest(eventLoop: reqEventLoop, requiresEventLoopForChannel: elRequired) + let mockRequest = MockHTTPScheduableRequest( + eventLoop: reqEventLoop, + requiresEventLoopForChannel: elRequired + ) let request = HTTPConnectionPool.Request(mockRequest) let action = state.executeRequest(request) @@ -440,7 +464,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssert(connEventLoop === reqEventLoop) XCTAssertEqual(action.request, .scheduleRequestTimeout(for: request, on: reqEventLoop)) - let connection: HTTPConnectionPool.Connection = .__testOnly_connection(id: connectionID, eventLoop: connEventLoop) + let connection: HTTPConnectionPool.Connection = .__testOnly_connection( + id: connectionID, + eventLoop: connEventLoop + ) let createdAction = state.newHTTP1ConnectionCreated(connection) XCTAssertEqual(createdAction.request, .executeRequest(request, connection, cancelTimeout: true)) XCTAssertEqual(createdAction.connection, .none) @@ -451,7 +478,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssertEqual(state.http1ConnectionClosed(connectionID), .none) case .cancelTimeoutTimer(let connectionID): - guard let expectedConnection = connections.newestParkedConnection(for: reqEventLoop) ?? connections.newestParkedConnection else { + guard + let expectedConnection = connections.newestParkedConnection(for: reqEventLoop) + ?? connections.newestParkedConnection + else { return XCTFail("Expected to have connections available") } @@ -459,7 +489,11 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssert(expectedConnection.eventLoop === reqEventLoop) } - XCTAssertEqual(connectionID, expectedConnection.id, "Request is scheduled on the connection we expected") + XCTAssertEqual( + connectionID, + expectedConnection.id, + "Request is scheduled on the connection we expected" + ) XCTAssertNoThrow(try connections.activateConnection(connectionID)) guard case .executeRequest(let request, let connection, cancelTimeout: false) = action.request else { @@ -469,8 +503,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssertNoThrow(try connections.execute(request.__testOnly_wrapped_request(), on: connection)) XCTAssertNoThrow(try connections.finishExecution(connection.id)) - XCTAssertEqual(state.http1ConnectionReleased(connection.id), - .init(request: .none, connection: .scheduleTimeoutTimer(connection.id, on: connection.eventLoop))) + XCTAssertEqual( + state.http1ConnectionReleased(connection.id), + .init(request: .none, connection: .scheduleTimeoutTimer(connection.id, on: connection.eventLoop)) + ) XCTAssertNoThrow(try connections.parkConnection(connectionID)) default: @@ -542,7 +578,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { // Add eight requests to fill all connections for _ in 0..<8 { let eventLoop = elg.next() - guard let expectedConnection = connections.newestParkedConnection(for: eventLoop) ?? connections.newestParkedConnection else { + guard + let expectedConnection = connections.newestParkedConnection(for: eventLoop) + ?? connections.newestParkedConnection + else { return XCTFail("Expected to still have connections available") } @@ -589,12 +628,20 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { guard let newConnection = maybeNewConnection else { return XCTFail("Expected to get a new connection") } let afterRecreationAction = state.newHTTP1ConnectionCreated(newConnection) XCTAssertEqual(afterRecreationAction.connection, .none) - guard case .executeRequest(let request, newConnection, cancelTimeout: true) = afterRecreationAction.request else { + guard + case .executeRequest(let request, newConnection, cancelTimeout: true) = afterRecreationAction + .request + else { return XCTFail("Unexpected request action: \(action.request)") } XCTAssertEqual(request.id, queuedRequestsOrder.popFirst()) - XCTAssertNoThrow(try connections.execute(queuer.get(request.id, request: request.__testOnly_wrapped_request()), on: newConnection)) + XCTAssertNoThrow( + try connections.execute( + queuer.get(request.id, request: request.__testOnly_wrapped_request()), + on: newConnection + ) + ) case .none: XCTAssert(queuer.isEmpty) @@ -730,7 +777,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssertEqual(executeAction.request, .scheduleRequestTimeout(for: request, on: mockRequest.eventLoop)) - let failAction = state.failedToCreateNewConnection(HTTPClientError.httpProxyHandshakeTimeout, connectionID: connectionID) + let failAction = state.failedToCreateNewConnection( + HTTPClientError.httpProxyHandshakeTimeout, + connectionID: connectionID + ) guard case .scheduleBackoffTimer(connectionID, backoff: _, on: let timerEL) = failAction.connection else { return XCTFail("Expected to create a backoff timer") } @@ -738,7 +788,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssertEqual(failAction.request, .none) let timeoutAction = state.timeoutRequest(request.id) - XCTAssertEqual(timeoutAction.request, .failRequest(request, HTTPClientError.httpProxyHandshakeTimeout, cancelTimeout: false)) + XCTAssertEqual( + timeoutAction.request, + .failRequest(request, HTTPClientError.httpProxyHandshakeTimeout, cancelTimeout: false) + ) XCTAssertEqual(timeoutAction.connection, .none) } @@ -764,7 +817,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssertEqual(executeAction.request, .scheduleRequestTimeout(for: request, on: mockRequest.eventLoop)) let timeoutAction = state.timeoutRequest(request.id) - XCTAssertEqual(timeoutAction.request, .failRequest(request, HTTPClientError.connectTimeout, cancelTimeout: false)) + XCTAssertEqual( + timeoutAction.request, + .failRequest(request, HTTPClientError.connectTimeout, cancelTimeout: false) + ) XCTAssertEqual(timeoutAction.connection, .none) } @@ -802,7 +858,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssertEqual(executeAction2.request, .scheduleRequestTimeout(for: request2, on: connEL1)) - let failAction = state.failedToCreateNewConnection(HTTPClientError.httpProxyHandshakeTimeout, connectionID: connectionID1) + let failAction = state.failedToCreateNewConnection( + HTTPClientError.httpProxyHandshakeTimeout, + connectionID: connectionID1 + ) guard case .scheduleBackoffTimer(connectionID1, backoff: _, on: let timerEL) = failAction.connection else { return XCTFail("Expected to create a backoff timer") } @@ -816,7 +875,10 @@ class HTTPConnectionPool_HTTP1StateMachineTests: XCTestCase { XCTAssertEqual(createdAction.connection, .none) let timeoutAction = state.timeoutRequest(request2.id) - XCTAssertEqual(timeoutAction.request, .failRequest(request2, HTTPClientError.getConnectionFromPoolTimeout, cancelTimeout: false)) + XCTAssertEqual( + timeoutAction.request, + .failRequest(request2, HTTPClientError.getConnectionFromPoolTimeout, cancelTimeout: false) + ) XCTAssertEqual(timeoutAction.connection, .none) } } diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2ConnectionsTest.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2ConnectionsTest.swift index 69bf62d81..dd56a9102 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2ConnectionsTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2ConnectionsTest.swift @@ -12,11 +12,12 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOEmbedded import XCTest +@testable import AsyncHTTPClient + class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { func testCreatingConnections() { let elg = EmbeddedEventLoopGroup(loops: 4) @@ -32,7 +33,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { XCTAssertTrue(connections.hasConnectionThatCanOrWillBeAbleToExecuteRequests) XCTAssertTrue(connections.hasConnectionThatCanOrWillBeAbleToExecuteRequests(for: el1)) let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1) - let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished(conn1, maxConcurrentStreams: 100) + let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished( + conn1, + maxConcurrentStreams: 100 + ) XCTAssertEqual(conn1CreatedContext.availableStreams, 100) XCTAssertEqual(conn1CreatedContext.isIdle, true) XCTAssert(conn1CreatedContext.eventLoop === el1) @@ -46,7 +50,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { let conn2ID = connections.createNewConnection(on: el2) XCTAssertTrue(connections.hasConnectionThatCanOrWillBeAbleToExecuteRequests(for: el2)) let conn2: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn2ID, eventLoop: el2) - let (conn2Index, conn2CreatedContext) = connections.newHTTP2ConnectionEstablished(conn2, maxConcurrentStreams: 100) + let (conn2Index, conn2CreatedContext) = connections.newHTTP2ConnectionEstablished( + conn2, + maxConcurrentStreams: 100 + ) XCTAssertEqual(conn1CreatedContext.availableStreams, 100) XCTAssertTrue(conn1CreatedContext.isIdle) XCTAssert(conn2CreatedContext.eventLoop === el2) @@ -83,7 +90,9 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { XCTAssert(conn1FailContext.eventLoop === el1) XCTAssertFalse(connections.hasConnectionThatCanOrWillBeAbleToExecuteRequests) XCTAssertFalse(connections.hasConnectionThatCanOrWillBeAbleToExecuteRequests(for: el1)) - let (replaceConn1ID, replaceConn1EL) = connections.createNewConnectionByReplacingClosedConnection(at: conn1FailIndex) + let (replaceConn1ID, replaceConn1EL) = connections.createNewConnectionByReplacingClosedConnection( + at: conn1FailIndex + ) XCTAssert(replaceConn1EL === el1) XCTAssertEqual(replaceConn1ID, 1) XCTAssertTrue(connections.hasConnectionThatCanOrWillBeAbleToExecuteRequests) @@ -336,13 +345,19 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { let conn1ID = connections.createNewConnection(on: el1) let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1) - let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished(conn1, maxConcurrentStreams: 100) + let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished( + conn1, + maxConcurrentStreams: 100 + ) XCTAssertEqual(conn1CreatedContext.availableStreams, 100) let (leasedConn1, leasdConnContext1) = connections.leaseStreams(at: conn1Index, count: 100) XCTAssertEqual(leasedConn1, conn1) XCTAssertEqual(leasdConnContext1.wasIdle, true) - XCTAssertNil(connections.leaseStream(onRequired: el1), "should not be able to lease stream because they are all already leased") + XCTAssertNil( + connections.leaseStream(onRequired: el1), + "should not be able to lease stream because they are all already leased" + ) let (_, releaseContext) = connections.releaseStream(conn1ID) XCTAssertFalse(releaseContext.isIdle) @@ -354,7 +369,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { XCTAssertEqual(leasedConn, conn1) XCTAssertEqual(leaseContext.wasIdle, false) - XCTAssertNil(connections.leaseStream(onRequired: el1), "should not be able to lease stream because they are all already leased") + XCTAssertNil( + connections.leaseStream(onRequired: el1), + "should not be able to lease stream because they are all already leased" + ) } func testGoAway() { @@ -364,7 +382,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { let conn1ID = connections.createNewConnection(on: el1) let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1) - let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished(conn1, maxConcurrentStreams: 10) + let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished( + conn1, + maxConcurrentStreams: 10 + ) XCTAssertEqual(conn1CreatedContext.availableStreams, 10) let (leasedConn1, leasdConnContext1) = connections.leaseStreams(at: conn1Index, count: 2) @@ -386,7 +407,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { ) ) - XCTAssertNil(connections.leaseStream(onRequired: el1), "we should not be able to lease a stream because the connection is draining") + XCTAssertNil( + connections.leaseStream(onRequired: el1), + "we should not be able to lease a stream because the connection is draining" + ) // a server can potentially send more than one connection go away and we should not crash XCTAssertTrue(connections.goAwayReceived(conn1ID)?.eventLoop === el1) @@ -445,7 +469,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { let conn1ID = connections.createNewConnection(on: el1) let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1) - let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished(conn1, maxConcurrentStreams: 1) + let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished( + conn1, + maxConcurrentStreams: 1 + ) XCTAssertEqual(conn1CreatedContext.availableStreams, 1) let (leasedConn1, leasdConnContext1) = connections.leaseStreams(at: conn1Index, count: 1) @@ -454,7 +481,8 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { XCTAssertNil(connections.leaseStream(onRequired: el1), "all streams are in use") - guard let (_, newSettingsContext1) = connections.newHTTP2MaxConcurrentStreamsReceived(conn1ID, newMaxStreams: 2) else { + guard let (_, newSettingsContext1) = connections.newHTTP2MaxConcurrentStreamsReceived(conn1ID, newMaxStreams: 2) + else { return XCTFail("Expected to get a new settings context") } XCTAssertEqual(newSettingsContext1.availableStreams, 1) @@ -467,7 +495,8 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { XCTAssertEqual(leasedConn2, conn1) XCTAssertEqual(leaseContext2.wasIdle, false) - guard let (_, newSettingsContext2) = connections.newHTTP2MaxConcurrentStreamsReceived(conn1ID, newMaxStreams: 1) else { + guard let (_, newSettingsContext2) = connections.newHTTP2MaxConcurrentStreamsReceived(conn1ID, newMaxStreams: 1) + else { return XCTFail("Expected to get a new settings context") } XCTAssertEqual(newSettingsContext2.availableStreams, 0) @@ -500,7 +529,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { let conn1ID = connections.createNewConnection(on: el1) let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1) - let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished(conn1, maxConcurrentStreams: 1) + let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished( + conn1, + maxConcurrentStreams: 1 + ) XCTAssertEqual(conn1CreatedContext.availableStreams, 1) let (leasedConn1, leasdConnContext1) = connections.leaseStreams(at: conn1Index, count: 1) @@ -535,7 +567,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { let conn1ID = connections.createNewConnection(on: el1) let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1) - let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished(conn1, maxConcurrentStreams: 1) + let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished( + conn1, + maxConcurrentStreams: 1 + ) XCTAssertEqual(conn1CreatedContext.availableStreams, 1) let (leasedConn1, leasdConnContext1) = connections.leaseStreams(at: conn1Index, count: 1) XCTAssertEqual(leasedConn1, conn1) @@ -556,9 +591,11 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { starting: [(conn1ID, el1)], backingOff: [(conn2ID, el2)] ) - XCTAssertTrue(connections.createConnectionsAfterMigrationIfNeeded( - requiredEventLoopsOfPendingRequests: [el1, el2] - ).isEmpty) + XCTAssertTrue( + connections.createConnectionsAfterMigrationIfNeeded( + requiredEventLoopsOfPendingRequests: [el1, el2] + ).isEmpty + ) XCTAssertEqual( connections.stats, @@ -574,7 +611,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { ) let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1) - let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished(conn1, maxConcurrentStreams: 100) + let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished( + conn1, + maxConcurrentStreams: 100 + ) XCTAssertEqual(conn1CreatedContext.availableStreams, 100) let (leasedConn1, leasdConnContext1) = connections.leaseStreams(at: conn1Index, count: 2) @@ -615,7 +655,10 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { ) let conn1: HTTPConnectionPool.Connection = .__testOnly_connection(id: conn1ID, eventLoop: el1) - let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished(conn1, maxConcurrentStreams: 100) + let (conn1Index, conn1CreatedContext) = connections.newHTTP2ConnectionEstablished( + conn1, + maxConcurrentStreams: 100 + ) XCTAssertEqual(conn1CreatedContext.availableStreams, 100) let (leasedConn1, leasdConnContext1) = connections.leaseStreams(at: conn1Index, count: 2) @@ -714,9 +757,12 @@ class HTTPConnectionPool_HTTP2ConnectionsTests: XCTestCase { backingOff: [(conn3ID, el3)] ) - XCTAssertTrue(connections.createConnectionsAfterMigrationIfNeeded( - requiredEventLoopsOfPendingRequests: [el1, el2, el3] - ).isEmpty, "we still have an active connection for el1 and should not create a new one") + XCTAssertTrue( + connections.createConnectionsAfterMigrationIfNeeded( + requiredEventLoopsOfPendingRequests: [el1, el2, el3] + ).isEmpty, + "we still have an active connection for el1 and should not create a new one" + ) guard let (leasedConn, _) = connections.leaseStream(onRequired: el1) else { return XCTFail("could not lease stream on el1") diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2StateMachineTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2StateMachineTests.swift index 046040266..e64fd5e71 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2StateMachineTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2StateMachineTests.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOEmbedded import NIOHTTP1 import NIOPosix import XCTest +@testable import AsyncHTTPClient + private typealias Action = HTTPConnectionPool.StateMachine.Action private typealias ConnectionAction = HTTPConnectionPool.StateMachine.ConnectionAction private typealias RequestAction = HTTPConnectionPool.StateMachine.RequestAction @@ -127,14 +128,17 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { /// shutdown should only close one connection let shutdownAction = state.shutdown() XCTAssertEqual(shutdownAction.request, .none) - XCTAssertEqual(shutdownAction.connection, .cleanupConnections( - .init( - close: [conn], - cancel: [], - connectBackoff: [] - ), - isShutdown: .yes(unclean: false) - )) + XCTAssertEqual( + shutdownAction.connection, + .cleanupConnections( + .init( + close: [conn], + cancel: [], + connectBackoff: [] + ), + isShutdown: .yes(unclean: false) + ) + ) } func testConnectionFailureBackoff() { @@ -158,9 +162,12 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { guard case .createConnection(let connectionID, on: let connectionEL) = action.connection else { return XCTFail("Unexpected connection action: \(action.connection)") } - XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux - let failedConnect1 = state.failedToCreateNewConnection(HTTPClientError.connectTimeout, connectionID: connectionID) + let failedConnect1 = state.failedToCreateNewConnection( + HTTPClientError.connectTimeout, + connectionID: connectionID + ) XCTAssertEqual(failedConnect1.request, .none) guard case .scheduleBackoffTimer(connectionID, let backoffTimeAmount1, _) = failedConnect1.connection else { return XCTFail("Unexpected connection action: \(failedConnect1.connection)") @@ -173,9 +180,12 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { return XCTFail("Unexpected connection action: \(backoffDoneAction.connection)") } XCTAssertGreaterThan(newConnectionID, connectionID) - XCTAssert(connectionEL === newEventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === newEventLoop) // XCTAssertIdentical not available on Linux - let failedConnect2 = state.failedToCreateNewConnection(HTTPClientError.connectTimeout, connectionID: newConnectionID) + let failedConnect2 = state.failedToCreateNewConnection( + HTTPClientError.connectTimeout, + connectionID: newConnectionID + ) XCTAssertEqual(failedConnect2.request, .none) guard case .scheduleBackoffTimer(newConnectionID, let backoffTimeAmount2, _) = failedConnect2.connection else { return XCTFail("Unexpected connection action: \(failedConnect2.connection)") @@ -188,7 +198,8 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { guard case .failRequest(let requestToFail, let requestError, cancelTimeout: false) = failRequest.request else { return XCTFail("Unexpected request action: \(action.request)") } - XCTAssert(requestToFail.__testOnly_wrapped_request() === mockRequest) // XCTAssertIdentical not available on Linux + // XCTAssertIdentical not available on Linux + XCTAssert(requestToFail.__testOnly_wrapped_request() === mockRequest) XCTAssertEqual(requestError as? HTTPClientError, .connectTimeout) XCTAssertEqual(failRequest.connection, .none) @@ -218,7 +229,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { guard case .createConnection(let connectionID, on: let connectionEL) = action.connection else { return XCTFail("Unexpected connection action: \(action.connection)") } - XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux // 2. initialise shutdown let shutdownAction = state.shutdown() @@ -257,11 +268,12 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { guard case .createConnection(let connectionID, on: let connectionEL) = action.connection else { return XCTFail("Unexpected connection action: \(action.connection)") } - XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux let failedConnectAction = state.failedToCreateNewConnection(SomeError(), connectionID: connectionID) XCTAssertEqual(failedConnectAction.connection, .none) - guard case .failRequestsAndCancelTimeouts(let requestsToFail, let requestError) = failedConnectAction.request else { + guard case .failRequestsAndCancelTimeouts(let requestsToFail, let requestError) = failedConnectAction.request + else { return XCTFail("Unexpected request action: \(action.request)") } XCTAssertEqualTypeAndValue(requestError, SomeError()) @@ -289,7 +301,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { guard case .createConnection(let connectionID, on: let connectionEL) = executeAction.connection else { return XCTFail("Unexpected connection action: \(executeAction.connection)") } - XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux // 2. cancel request let cancelAction = state.cancelRequest(request.id) @@ -329,15 +341,18 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { guard case .createConnection(let connectionID, on: let connectionEL) = executeAction.connection else { return XCTFail("Unexpected connection action: \(executeAction.connection)") } - XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux + XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux // 2. connection succeeds - let connection: HTTPConnectionPool.Connection = .__testOnly_connection(id: connectionID, eventLoop: connectionEL) + let connection: HTTPConnectionPool.Connection = .__testOnly_connection( + id: connectionID, + eventLoop: connectionEL + ) let connectedAction = state.newHTTP2ConnectionEstablished(connection, maxConcurrentStreams: 100) guard case .executeRequestsAndCancelTimeouts([request], connection) = connectedAction.request else { return XCTFail("Unexpected request action: \(connectedAction.request)") } - XCTAssert(request.__testOnly_wrapped_request() === mockRequest) // XCTAssertIdentical not available on Linux + XCTAssert(request.__testOnly_wrapped_request() === mockRequest) // XCTAssertIdentical not available on Linux XCTAssertEqual(connectedAction.connection, .none) // 3. shutdown @@ -357,7 +372,10 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { let finalRequest = HTTPConnectionPool.Request(finalMockRequest) let failAction = state.executeRequest(finalRequest) XCTAssertEqual(failAction.connection, .none) - XCTAssertEqual(failAction.request, .failRequest(finalRequest, HTTPClientError.alreadyShutdown, cancelTimeout: false)) + XCTAssertEqual( + failAction.request, + .failRequest(finalRequest, HTTPClientError.alreadyShutdown, cancelTimeout: false) + ) // 5. close open connection let closeAction = state.http2ConnectionClosed(connectionID) @@ -416,7 +434,10 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { newHTTP2Connection: conn2, maxConcurrentStreams: 100 ) - XCTAssertEqual(http2ConnectAction.connection, .migration(createConnections: [], closeConnections: [], scheduleTimeout: nil)) + XCTAssertEqual( + http2ConnectAction.connection, + .migration(createConnections: [], closeConnections: [], scheduleTimeout: nil) + ) guard case .executeRequestsAndCancelTimeouts([request2], conn2) = http2ConnectAction.request else { return XCTFail("Unexpected request action \(http2ConnectAction.request)") } @@ -428,11 +449,17 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { let shutdownAction = http2State.shutdown() XCTAssertEqual(shutdownAction.request, .none) - XCTAssertEqual(shutdownAction.connection, .cleanupConnections(.init( - close: [conn2], - cancel: [], - connectBackoff: [] - ), isShutdown: .no)) + XCTAssertEqual( + shutdownAction.connection, + .cleanupConnections( + .init( + close: [conn2], + cancel: [], + connectBackoff: [] + ), + isShutdown: .no + ) + ) let releaseAction = http2State.http1ConnectionReleased(conn1ID) XCTAssertEqual(releaseAction.request, .none) @@ -445,7 +472,11 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { // establish one idle http2 connection let idGenerator = HTTPConnectionPool.Connection.ID.Generator() - var http1Conns = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: idGenerator, maximumConnectionUses: nil) + var http1Conns = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: idGenerator, + maximumConnectionUses: nil + ) let conn1ID = http1Conns.createNewConnection(on: el1) var state = HTTPConnectionPool.HTTP2StateMachine( idGenerator: idGenerator, @@ -455,14 +486,22 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { ) let conn1 = HTTPConnectionPool.Connection.__testOnly_connection(id: conn1ID, eventLoop: el1) - let connectAction = state.migrateFromHTTP1(http1Connections: http1Conns, requests: .init(), newHTTP2Connection: conn1, maxConcurrentStreams: 100) + let connectAction = state.migrateFromHTTP1( + http1Connections: http1Conns, + requests: .init(), + newHTTP2Connection: conn1, + maxConcurrentStreams: 100 + ) XCTAssertEqual(connectAction.request, .none) - XCTAssertEqual(connectAction.connection, .migration( - createConnections: [], - closeConnections: [], - scheduleTimeout: (conn1ID, el1) - )) + XCTAssertEqual( + connectAction.connection, + .migration( + createConnections: [], + closeConnections: [], + scheduleTimeout: (conn1ID, el1) + ) + ) // execute request on idle connection let mockRequest1 = MockHTTPScheduableRequest(eventLoop: el1) @@ -495,7 +534,11 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { // establish one idle http2 connection let idGenerator = HTTPConnectionPool.Connection.ID.Generator() - var http1Conns = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: idGenerator, maximumConnectionUses: nil) + var http1Conns = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: idGenerator, + maximumConnectionUses: nil + ) let conn1ID = http1Conns.createNewConnection(on: el1) var state = HTTPConnectionPool.HTTP2StateMachine( idGenerator: idGenerator, @@ -505,13 +548,21 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { ) let conn1 = HTTPConnectionPool.Connection.__testOnly_connection(id: conn1ID, eventLoop: el1) - let connectAction = state.migrateFromHTTP1(http1Connections: http1Conns, requests: .init(), newHTTP2Connection: conn1, maxConcurrentStreams: 100) + let connectAction = state.migrateFromHTTP1( + http1Connections: http1Conns, + requests: .init(), + newHTTP2Connection: conn1, + maxConcurrentStreams: 100 + ) XCTAssertEqual(connectAction.request, .none) - XCTAssertEqual(connectAction.connection, .migration( - createConnections: [], - closeConnections: [], - scheduleTimeout: (conn1ID, el1) - )) + XCTAssertEqual( + connectAction.connection, + .migration( + createConnections: [], + closeConnections: [], + scheduleTimeout: (conn1ID, el1) + ) + ) // let the connection timeout let timeoutAction = state.connectionIdleTimeout(conn1ID) @@ -528,7 +579,11 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { // establish one idle http2 connection let idGenerator = HTTPConnectionPool.Connection.ID.Generator() - var http1Conns = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: idGenerator, maximumConnectionUses: nil) + var http1Conns = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: idGenerator, + maximumConnectionUses: nil + ) let conn1ID = http1Conns.createNewConnection(on: el1) var state = HTTPConnectionPool.HTTP2StateMachine( idGenerator: idGenerator, @@ -537,13 +592,21 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { maximumConnectionUses: nil ) let conn1 = HTTPConnectionPool.Connection.__testOnly_connection(id: conn1ID, eventLoop: el1) - let connectAction = state.migrateFromHTTP1(http1Connections: http1Conns, requests: .init(), newHTTP2Connection: conn1, maxConcurrentStreams: 100) + let connectAction = state.migrateFromHTTP1( + http1Connections: http1Conns, + requests: .init(), + newHTTP2Connection: conn1, + maxConcurrentStreams: 100 + ) XCTAssertEqual(connectAction.request, .none) - XCTAssertEqual(connectAction.connection, .migration( - createConnections: [], - closeConnections: [], - scheduleTimeout: (conn1ID, el1) - )) + XCTAssertEqual( + connectAction.connection, + .migration( + createConnections: [], + closeConnections: [], + scheduleTimeout: (conn1ID, el1) + ) + ) // create new http2 connection let mockRequest1 = MockHTTPScheduableRequest(eventLoop: el2, requiresEventLoopForChannel: true) @@ -568,7 +631,11 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { // establish one idle http2 connection let idGenerator = HTTPConnectionPool.Connection.ID.Generator() - var http1Conns = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: idGenerator, maximumConnectionUses: nil) + var http1Conns = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: idGenerator, + maximumConnectionUses: nil + ) let conn1ID = http1Conns.createNewConnection(on: el1) var state = HTTPConnectionPool.HTTP2StateMachine( idGenerator: idGenerator, @@ -586,11 +653,14 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { maxConcurrentStreams: 100 ) XCTAssertEqual(connectAction.request, .none) - XCTAssertEqual(connectAction.connection, .migration( - createConnections: [], - closeConnections: [], - scheduleTimeout: (conn1ID, el1) - )) + XCTAssertEqual( + connectAction.connection, + .migration( + createConnections: [], + closeConnections: [], + scheduleTimeout: (conn1ID, el1) + ) + ) let goAwayAction = state.http2ConnectionGoAwayReceived(conn1ID) XCTAssertEqual(goAwayAction.request, .none) @@ -603,7 +673,11 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { // establish one idle http2 connection let idGenerator = HTTPConnectionPool.Connection.ID.Generator() - var http1Conns = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: idGenerator, maximumConnectionUses: nil) + var http1Conns = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: idGenerator, + maximumConnectionUses: nil + ) let conn1ID = http1Conns.createNewConnection(on: el1) var state = HTTPConnectionPool.HTTP2StateMachine( idGenerator: idGenerator, @@ -620,11 +694,14 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { maxConcurrentStreams: 100 ) XCTAssertEqual(connectAction.request, .none) - XCTAssertEqual(connectAction.connection, .migration( - createConnections: [], - closeConnections: [], - scheduleTimeout: (conn1ID, el1) - )) + XCTAssertEqual( + connectAction.connection, + .migration( + createConnections: [], + closeConnections: [], + scheduleTimeout: (conn1ID, el1) + ) + ) // execute request on idle connection let mockRequest1 = MockHTTPScheduableRequest(eventLoop: el1) @@ -649,7 +726,11 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { // establish one idle http2 connection let idGenerator = HTTPConnectionPool.Connection.ID.Generator() - var http1Conns = HTTPConnectionPool.HTTP1Connections(maximumConcurrentConnections: 8, generator: idGenerator, maximumConnectionUses: nil) + var http1Conns = HTTPConnectionPool.HTTP1Connections( + maximumConcurrentConnections: 8, + generator: idGenerator, + maximumConnectionUses: nil + ) let conn1ID = http1Conns.createNewConnection(on: el1) var state = HTTPConnectionPool.HTTP2StateMachine( idGenerator: idGenerator, @@ -666,11 +747,14 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { maxConcurrentStreams: 1 ) XCTAssertEqual(connectAction1.request, .none) - XCTAssertEqual(connectAction1.connection, .migration( - createConnections: [], - closeConnections: [], - scheduleTimeout: (conn1ID, el1) - )) + XCTAssertEqual( + connectAction1.connection, + .migration( + createConnections: [], + closeConnections: [], + scheduleTimeout: (conn1ID, el1) + ) + ) // execute request let mockRequest1 = MockHTTPScheduableRequest(eventLoop: el1) @@ -770,11 +854,14 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { XCTAssertNoThrow(try connections.execute(request.__testOnly_wrapped_request(), on: conn1)) } - XCTAssertEqual(migrationAction.connection, .migration( - createConnections: [], - closeConnections: [], - scheduleTimeout: nil - )) + XCTAssertEqual( + migrationAction.connection, + .migration( + createConnections: [], + closeConnections: [], + scheduleTimeout: nil + ) + ) /// remaining connections should be closed immediately without executing any request for connID in connectionIDs.dropFirst() { @@ -933,7 +1020,10 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { guard case .executeRequestsAndCancelTimeouts(let requests, let conn) = migrationAction.request else { return XCTFail("unexpected request action \(migrationAction.request)") } - XCTAssertEqual(migrationAction.connection, .migration(createConnections: [], closeConnections: [], scheduleTimeout: nil)) + XCTAssertEqual( + migrationAction.connection, + .migration(createConnections: [], closeConnections: [], scheduleTimeout: nil) + ) XCTAssertEqual(conn, http2Conn) XCTAssertEqual(requests.count, 10) @@ -1030,14 +1120,20 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { } // a request with new required event loop should create a new connection - let mockRequestWithRequiredEventLoop = MockHTTPScheduableRequest(eventLoop: el2, requiresEventLoopForChannel: true) + let mockRequestWithRequiredEventLoop = MockHTTPScheduableRequest( + eventLoop: el2, + requiresEventLoopForChannel: true + ) let requestWithRequiredEventLoop = HTTPConnectionPool.Request(mockRequestWithRequiredEventLoop) let action2 = state.executeRequest(requestWithRequiredEventLoop) guard case .createConnection(let http1ConnId, let http1EventLoop) = action2.connection else { return XCTFail("Unexpected connection action \(action2.connection)") } XCTAssertTrue(http1EventLoop === el2) - XCTAssertEqual(action2.request, .scheduleRequestTimeout(for: requestWithRequiredEventLoop, on: mockRequestWithRequiredEventLoop.eventLoop)) + XCTAssertEqual( + action2.request, + .scheduleRequestTimeout(for: requestWithRequiredEventLoop, on: mockRequestWithRequiredEventLoop.eventLoop) + ) XCTAssertNoThrow(try connections.createConnection(http1ConnId, on: el2)) XCTAssertNoThrow(try queuer.queue(mockRequestWithRequiredEventLoop, id: requestWithRequiredEventLoop.id)) @@ -1048,7 +1144,10 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { guard case .executeRequest(let request2, http1Conn, cancelTimeout: true) = migrationAction2.request else { return XCTFail("unexpected request action \(migrationAction2.request)") } - guard case .migration(let createConnections, closeConnections: [], scheduleTimeout: nil) = migrationAction2.connection else { + guard + case .migration(let createConnections, closeConnections: [], scheduleTimeout: nil) = migrationAction2 + .connection + else { return XCTFail("unexpected connection action \(migrationAction2.connection)") } XCTAssertEqual(createConnections.map { $0.1.id }, [el2.id]) @@ -1102,14 +1201,20 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { } // a request with new required event loop should create a new connection - let mockRequestWithRequiredEventLoop = MockHTTPScheduableRequest(eventLoop: el2, requiresEventLoopForChannel: true) + let mockRequestWithRequiredEventLoop = MockHTTPScheduableRequest( + eventLoop: el2, + requiresEventLoopForChannel: true + ) let requestWithRequiredEventLoop = HTTPConnectionPool.Request(mockRequestWithRequiredEventLoop) let action2 = state.executeRequest(requestWithRequiredEventLoop) guard case .createConnection(let http1ConnId, let http1EventLoop) = action2.connection else { return XCTFail("Unexpected connection action \(action2.connection)") } XCTAssertTrue(http1EventLoop === el2) - XCTAssertEqual(action2.request, .scheduleRequestTimeout(for: requestWithRequiredEventLoop, on: mockRequestWithRequiredEventLoop.eventLoop)) + XCTAssertEqual( + action2.request, + .scheduleRequestTimeout(for: requestWithRequiredEventLoop, on: mockRequestWithRequiredEventLoop.eventLoop) + ) XCTAssertNoThrow(try connections.createConnection(http1ConnId, on: el2)) XCTAssertNoThrow(try queuer.queue(mockRequestWithRequiredEventLoop, id: requestWithRequiredEventLoop.id)) @@ -1131,7 +1236,10 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { XCTAssertNoThrow(try connections.succeedConnectionCreationHTTP1(http1ConnId)) let migrationAction2 = state.newHTTP1ConnectionCreated(http1Conn) XCTAssertEqual(migrationAction2.request, .none) - XCTAssertEqual(migrationAction2.connection, .migration(createConnections: [], closeConnections: [http1Conn], scheduleTimeout: nil)) + XCTAssertEqual( + migrationAction2.connection, + .migration(createConnections: [], closeConnections: [http1Conn], scheduleTimeout: nil) + ) // in http/1 state, we should close idle http2 connections XCTAssertNoThrow(try connections.finishExecution(http2Conn.id)) @@ -1234,10 +1342,16 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { case 0: XCTAssertEqual(executeAction.connection, .cancelTimeoutTimer(generalPurposeConnection.id)) XCTAssertNoThrow(try connections.activateConnection(generalPurposeConnection.id)) - XCTAssertEqual(executeAction.request, .executeRequest(request, generalPurposeConnection, cancelTimeout: false)) + XCTAssertEqual( + executeAction.request, + .executeRequest(request, generalPurposeConnection, cancelTimeout: false) + ) XCTAssertNoThrow(try connections.execute(mockRequest, on: generalPurposeConnection)) case 1..<100: - XCTAssertEqual(executeAction.request, .executeRequest(request, generalPurposeConnection, cancelTimeout: false)) + XCTAssertEqual( + executeAction.request, + .executeRequest(request, generalPurposeConnection, cancelTimeout: false) + ) XCTAssertEqual(executeAction.connection, .none) XCTAssertNoThrow(try connections.execute(mockRequest, on: generalPurposeConnection)) case 100..<1000: @@ -1255,7 +1369,8 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { XCTAssertNoThrow(try connections.finishExecution(generalPurposeConnection.id)) let finishAction = state.http2ConnectionStreamClosed(generalPurposeConnection.id) XCTAssertEqual(finishAction.connection, .none) - guard case .executeRequestsAndCancelTimeouts(let requests, generalPurposeConnection) = finishAction.request else { + guard case .executeRequestsAndCancelTimeouts(let requests, generalPurposeConnection) = finishAction.request + else { return XCTFail("Unexpected request action: \(finishAction.request)") } guard requests.count == 1, let request = requests.first else { @@ -1270,11 +1385,23 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { // Next the server allows for more concurrent streams let newMaxStreams = 200 - XCTAssertNoThrow(try connections.newHTTP2ConnectionSettingsReceived(generalPurposeConnection.id, maxConcurrentStreams: newMaxStreams)) - let newMaxStreamsAction = state.newHTTP2MaxConcurrentStreamsReceived(generalPurposeConnection.id, newMaxStreams: newMaxStreams) + XCTAssertNoThrow( + try connections.newHTTP2ConnectionSettingsReceived( + generalPurposeConnection.id, + maxConcurrentStreams: newMaxStreams + ) + ) + let newMaxStreamsAction = state.newHTTP2MaxConcurrentStreamsReceived( + generalPurposeConnection.id, + newMaxStreams: newMaxStreams + ) XCTAssertEqual(newMaxStreamsAction.connection, .none) - guard case .executeRequestsAndCancelTimeouts(let requests, generalPurposeConnection) = newMaxStreamsAction.request else { - return XCTFail("Unexpected request action after new max concurrent stream setting: \(newMaxStreamsAction.request)") + guard + case .executeRequestsAndCancelTimeouts(let requests, generalPurposeConnection) = newMaxStreamsAction.request + else { + return XCTFail( + "Unexpected request action after new max concurrent stream setting: \(newMaxStreamsAction.request)" + ) } XCTAssertEqual(requests.count, 100, "Expected to execute 100 more requests") for request in requests { @@ -1291,7 +1418,8 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { XCTAssertNoThrow(try connections.finishExecution(generalPurposeConnection.id)) let finishAction = state.http2ConnectionStreamClosed(generalPurposeConnection.id) XCTAssertEqual(finishAction.connection, .none) - guard case .executeRequestsAndCancelTimeouts(let requests, generalPurposeConnection) = finishAction.request else { + guard case .executeRequestsAndCancelTimeouts(let requests, generalPurposeConnection) = finishAction.request + else { return XCTFail("Unexpected request action: \(finishAction.request)") } guard requests.count == 1, let request = requests.first else { @@ -1304,8 +1432,16 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { // Next the server allows for fewer concurrent streams let fewerMaxStreams = 50 - XCTAssertNoThrow(try connections.newHTTP2ConnectionSettingsReceived(generalPurposeConnection.id, maxConcurrentStreams: fewerMaxStreams)) - let fewerMaxStreamsAction = state.newHTTP2MaxConcurrentStreamsReceived(generalPurposeConnection.id, newMaxStreams: fewerMaxStreams) + XCTAssertNoThrow( + try connections.newHTTP2ConnectionSettingsReceived( + generalPurposeConnection.id, + maxConcurrentStreams: fewerMaxStreams + ) + ) + let fewerMaxStreamsAction = state.newHTTP2MaxConcurrentStreamsReceived( + generalPurposeConnection.id, + newMaxStreams: fewerMaxStreams + ) XCTAssertEqual(fewerMaxStreamsAction.connection, .none) XCTAssertEqual(fewerMaxStreamsAction.request, .none) @@ -1323,7 +1459,8 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { XCTAssertNoThrow(try connections.finishExecution(generalPurposeConnection.id)) let finishAction = state.http2ConnectionStreamClosed(generalPurposeConnection.id) XCTAssertEqual(finishAction.connection, .none) - guard case .executeRequestsAndCancelTimeouts(let requests, generalPurposeConnection) = finishAction.request else { + guard case .executeRequestsAndCancelTimeouts(let requests, generalPurposeConnection) = finishAction.request + else { return XCTFail("Unexpected request action: \(finishAction.request)") } guard requests.count == 1, let request = requests.first else { @@ -1343,7 +1480,10 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase { switch remaining { case 1: timeoutTimerScheduled = true - XCTAssertEqual(finishAction.connection, .scheduleTimeoutTimer(generalPurposeConnection.id, on: generalPurposeConnection.eventLoop)) + XCTAssertEqual( + finishAction.connection, + .scheduleTimeoutTimer(generalPurposeConnection.id, on: generalPurposeConnection.eventLoop) + ) XCTAssertNoThrow(try connections.parkConnection(generalPurposeConnection.id)) case 2...50: XCTAssertEqual(finishAction.connection, .none) @@ -1388,13 +1528,17 @@ func XCTAssertEqualTypeAndValue( file: StaticString = #filePath, line: UInt = #line ) { - XCTAssertNoThrow(try { - let lhs = try lhs() - let rhs = try rhs() - guard let lhsAsRhs = lhs as? Right else { - XCTFail("could not cast \(lhs) of type \(type(of: lhs)) to \(type(of: rhs))", file: file, line: line) - return - } - XCTAssertEqual(lhsAsRhs, rhs, file: file, line: line) - }(), file: file, line: line) + XCTAssertNoThrow( + try { + let lhs = try lhs() + let rhs = try rhs() + guard let lhsAsRhs = lhs as? Right else { + XCTFail("could not cast \(lhs) of type \(type(of: lhs)) to \(type(of: rhs))", file: file, line: line) + return + } + XCTAssertEqual(lhsAsRhs, rhs, file: file, line: line) + }(), + file: file, + line: line + ) } diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+ManagerTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+ManagerTests.swift index d84e7f442..ef59c9463 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+ManagerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+ManagerTests.swift @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOHTTP1 import NIOPosix import XCTest +@testable import AsyncHTTPClient + class HTTPConnectionPool_ManagerTests: XCTestCase { func testManagerHappyPath() { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 4) @@ -49,15 +50,17 @@ class HTTPConnectionPool_ManagerTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(5), - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(5), + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } @@ -105,15 +108,17 @@ class HTTPConnectionPool_ManagerTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(5), - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(5), + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+RequestQueueTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+RequestQueueTests.swift index f8d6044cd..d792895d3 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+RequestQueueTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+RequestQueueTests.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOEmbedded @@ -20,6 +19,8 @@ import NIOHTTP1 import NIOSSL import XCTest +@testable import AsyncHTTPClient + class HTTPConnectionPool_RequestQueueTests: XCTestCase { func testCountAndIsEmptyWorks() { var queue = HTTPConnectionPool.RequestQueue() diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+StateTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+StateTestUtils.swift index 53bba940c..bd9752d5d 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+StateTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+StateTestUtils.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Atomics import Dispatch import NIOConcurrencyHelpers import NIOCore import NIOEmbedded +@testable import AsyncHTTPClient + /// An `EventLoopGroup` of `EmbeddedEventLoop`s. final class EmbeddedEventLoopGroup: EventLoopGroup { private let loops: [EmbeddedEventLoop] @@ -34,7 +35,7 @@ final class EmbeddedEventLoopGroup: EventLoopGroup { } internal func makeIterator() -> EventLoopIterator { - return EventLoopIterator(self.loops) + EventLoopIterator(self.loops) } internal func shutdownGracefully(queue: DispatchQueue, _ callback: @escaping (Error?) -> Void) { @@ -56,7 +57,7 @@ final class EmbeddedEventLoopGroup: EventLoopGroup { extension HTTPConnectionPool.Request: Equatable { public static func == (lhs: Self, rhs: Self) -> Bool { - return lhs.id == rhs.id + lhs.id == rhs.id } } @@ -78,15 +79,24 @@ extension HTTPConnectionPool.StateMachine.ConnectionAction: Equatable { switch (lhs, rhs) { case (.createConnection(let lhsConnID, on: let lhsEL), .createConnection(let rhsConnID, on: let rhsEL)): return lhsConnID == rhsConnID && lhsEL === rhsEL - case (.scheduleBackoffTimer(let lhsConnID, let lhsBackoff, on: let lhsEL), .scheduleBackoffTimer(let rhsConnID, let rhsBackoff, on: let rhsEL)): + case ( + .scheduleBackoffTimer(let lhsConnID, let lhsBackoff, on: let lhsEL), + .scheduleBackoffTimer(let rhsConnID, let rhsBackoff, on: let rhsEL) + ): return lhsConnID == rhsConnID && lhsBackoff == rhsBackoff && lhsEL === rhsEL case (.scheduleTimeoutTimer(let lhsConnID, on: let lhsEL), .scheduleTimeoutTimer(let rhsConnID, on: let rhsEL)): return lhsConnID == rhsConnID && lhsEL === rhsEL case (.cancelTimeoutTimer(let lhsConnID), .cancelTimeoutTimer(let rhsConnID)): return lhsConnID == rhsConnID - case (.closeConnection(let lhsConn, isShutdown: let lhsShut), .closeConnection(let rhsConn, isShutdown: let rhsShut)): + case ( + .closeConnection(let lhsConn, isShutdown: let lhsShut), + .closeConnection(let rhsConn, isShutdown: let rhsShut) + ): return lhsConn == rhsConn && lhsShut == rhsShut - case (.cleanupConnections(let lhsContext, isShutdown: let lhsShut), .cleanupConnections(let rhsContext, isShutdown: let rhsShut)): + case ( + .cleanupConnections(let lhsContext, isShutdown: let lhsShut), + .cleanupConnections(let rhsContext, isShutdown: let rhsShut) + ): return lhsContext == rhsContext && lhsShut == rhsShut case ( .migration( @@ -100,12 +110,13 @@ extension HTTPConnectionPool.StateMachine.ConnectionAction: Equatable { let rhsScheduleTimeout ) ): - return lhsCreateConnections.elementsEqual(rhsCreateConnections, by: { - $0.0 == $1.0 && $0.1 === $1.1 - }) && - lhsCloseConnections == rhsCloseConnections && - lhsScheduleTimeout?.0 == rhsScheduleTimeout?.0 && - lhsScheduleTimeout?.1 === rhsScheduleTimeout?.1 + return lhsCreateConnections.elementsEqual( + rhsCreateConnections, + by: { + $0.0 == $1.0 && $0.1 === $1.1 + } + ) && lhsCloseConnections == rhsCloseConnections && lhsScheduleTimeout?.0 == rhsScheduleTimeout?.0 + && lhsScheduleTimeout?.1 === rhsScheduleTimeout?.1 case (.none, .none): return true default: @@ -117,15 +128,27 @@ extension HTTPConnectionPool.StateMachine.ConnectionAction: Equatable { extension HTTPConnectionPool.StateMachine.RequestAction: Equatable { public static func == (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { - case (.executeRequest(let lhsReq, let lhsConn, let lhsReqID), .executeRequest(let rhsReq, let rhsConn, let rhsReqID)): + case ( + .executeRequest(let lhsReq, let lhsConn, let lhsReqID), + .executeRequest(let rhsReq, let rhsConn, let rhsReqID) + ): return lhsReq == rhsReq && lhsConn == rhsConn && lhsReqID == rhsReqID - case (.executeRequestsAndCancelTimeouts(let lhsReqs, let lhsConn), .executeRequestsAndCancelTimeouts(let rhsReqs, let rhsConn)): + case ( + .executeRequestsAndCancelTimeouts(let lhsReqs, let lhsConn), + .executeRequestsAndCancelTimeouts(let rhsReqs, let rhsConn) + ): return lhsReqs.elementsEqual(rhsReqs, by: { $0 == $1 }) && lhsConn == rhsConn - case (.failRequest(let lhsReq, _, cancelTimeout: let lhsReqID), .failRequest(let rhsReq, _, cancelTimeout: let rhsReqID)): + case ( + .failRequest(let lhsReq, _, cancelTimeout: let lhsReqID), + .failRequest(let rhsReq, _, cancelTimeout: let rhsReqID) + ): return lhsReq == rhsReq && lhsReqID == rhsReqID case (.failRequestsAndCancelTimeouts(let lhsReqs, _), .failRequestsAndCancelTimeouts(let rhsReqs, _)): return lhsReqs.elementsEqual(rhsReqs, by: { $0 == $1 }) - case (.scheduleRequestTimeout(for: let lhsReq, on: let lhsEL), .scheduleRequestTimeout(for: let rhsReq, on: let rhsEL)): + case ( + .scheduleRequestTimeout(for: let lhsReq, on: let lhsEL), + .scheduleRequestTimeout(for: let rhsReq, on: let rhsEL) + ): return lhsReq == rhsReq && lhsEL === rhsEL case (.none, .none): return true @@ -146,7 +169,10 @@ extension HTTPConnectionPool.HTTP2StateMachine.EstablishedConnectionAction: Equa switch (lhs, rhs) { case (.scheduleTimeoutTimer(let lhsConnID, on: let lhsEL), .scheduleTimeoutTimer(let rhsConnID, on: let rhsEL)): return lhsConnID == rhsConnID && lhsEL === rhsEL - case (.closeConnection(let lhsConn, isShutdown: let lhsShut), .closeConnection(let rhsConn, isShutdown: let rhsShut)): + case ( + .closeConnection(let lhsConn, isShutdown: let lhsShut), + .closeConnection(let rhsConn, isShutdown: let rhsShut) + ): return lhsConn == rhsConn && lhsShut == rhsShut case (.none, .none): return true diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift index a75cfb63c..a40703456 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOHTTP1 import NIOPosix import XCTest +@testable import AsyncHTTPClient + class HTTPConnectionPoolTests: XCTestCase { func testOnlyOneConnectionIsUsedForSubSequentRequests() { let httpBin = HTTPBin() @@ -53,15 +54,17 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoop, logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .distantFuture, - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoop, logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .distantFuture, + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } @@ -111,15 +114,19 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .init(.testOnly_exact(channelOn: eventLoopGroup.next(), delegateOn: eventLoopGroup.next())), - task: .init(eventLoop: eventLoop, logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .distantFuture, - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .init( + .testOnly_exact(channelOn: eventLoopGroup.next(), delegateOn: eventLoopGroup.next()) + ), + task: .init(eventLoop: eventLoop, logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .distantFuture, + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } @@ -170,15 +177,19 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .init(.testOnly_exact(channelOn: eventLoopGroup.next(), delegateOn: eventLoopGroup.next())), - task: .init(eventLoop: eventLoop, logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .distantFuture, - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .init( + .testOnly_exact(channelOn: eventLoopGroup.next(), delegateOn: eventLoopGroup.next()) + ), + task: .init(eventLoop: eventLoop, logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .distantFuture, + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } @@ -225,15 +236,17 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .distantFuture, - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .distantFuture, + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } @@ -279,15 +292,17 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(5), - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(5), + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } @@ -327,15 +342,17 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(5), - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(5), + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } @@ -383,15 +400,17 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(5), - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(5), + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } @@ -429,15 +448,17 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequest: HTTPClient.Request? var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/wait")) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(5), - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(5), + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } @@ -489,15 +510,17 @@ class HTTPConnectionPoolTests: XCTestCase { var maybeRequestBag: RequestBag? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: url)) - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: XCTUnwrap(maybeRequest), - eventLoopPreference: .indifferent, - task: .init(eventLoop: eventLoopGroup.next(), logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(5), - requestOptions: .forTests(), - delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: XCTUnwrap(maybeRequest), + eventLoopPreference: .indifferent, + task: .init(eventLoop: eventLoopGroup.next(), logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(5), + requestOptions: .forTests(), + delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) + ) + ) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to get a request") } pool.executeRequest(requestBag) @@ -521,7 +544,10 @@ class HTTPConnectionPoolTests: XCTestCase { var backoff = HTTPConnectionPool.calculateBackoff(failedAttempt: 1) // The value should be 100ms±3ms - XCTAssertLessThanOrEqual((backoff - .milliseconds(100)).nanoseconds.magnitude, TimeAmount.milliseconds(3).nanoseconds.magnitude) + XCTAssertLessThanOrEqual( + (backoff - .milliseconds(100)).nanoseconds.magnitude, + TimeAmount.milliseconds(3).nanoseconds.magnitude + ) // Should always increase // We stop when we get within the jitter of 60s, which is 1.8s @@ -537,7 +563,8 @@ class HTTPConnectionPoolTests: XCTestCase { // Ok, now we should be able to do a hundred increments, and always hit 60s, plus or minus 1.8s of jitter. for offset in 0..<100 { XCTAssertLessThanOrEqual( - (HTTPConnectionPool.calculateBackoff(failedAttempt: attempt + offset) - .seconds(60)).nanoseconds.magnitude, + (HTTPConnectionPool.calculateBackoff(failedAttempt: attempt + offset) - .seconds(60)).nanoseconds + .magnitude, TimeAmount.milliseconds(1800).nanoseconds.magnitude ) } diff --git a/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests.swift b/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests.swift index 92bf42b1d..8fe879745 100644 --- a/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests.swift @@ -12,22 +12,29 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOEmbedded import NIOHTTP1 import NIOSSL import XCTest +@testable import AsyncHTTPClient + class HTTPRequestStateMachineTests: XCTestCase { func testSimpleGETRequest() { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let responseBody = ByteBuffer(bytes: [1, 2, 3, 4]) XCTAssertEqual(state.channelRead(.body(responseBody)), .wait) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.none, .init([responseBody]))) @@ -36,10 +43,21 @@ class HTTPRequestStateMachineTests: XCTestCase { func testPOSTRequestWithWriterBackpressure() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "4")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .POST, + uri: "/", + headers: HTTPHeaders([("content-length", "4")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(4)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false) + ) let part0 = IOData.byteBuffer(ByteBuffer(bytes: [0])) let part1 = IOData.byteBuffer(ByteBuffer(bytes: [1])) let part2 = IOData.byteBuffer(ByteBuffer(bytes: [2])) @@ -62,7 +80,10 @@ class HTTPRequestStateMachineTests: XCTestCase { XCTAssertEqual(state.requestStreamFinished(promise: nil), .sendRequestEnd(nil)) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let responseBody = ByteBuffer(bytes: [1, 2, 3, 4]) XCTAssertEqual(state.channelRead(.body(responseBody)), .wait) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.none, .init([responseBody]))) @@ -71,14 +92,25 @@ class HTTPRequestStateMachineTests: XCTestCase { func testPOSTContentLengthIsTooLong() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "4")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .POST, + uri: "/", + headers: HTTPHeaders([("content-length", "4")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(4)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) let part0 = IOData.byteBuffer(ByteBuffer(bytes: [0, 1, 2, 3])) let part1 = IOData.byteBuffer(ByteBuffer(bytes: [0, 1, 2, 3])) XCTAssertEqual(state.requestStreamPartReceived(part0, promise: nil), .sendBodyPart(part0, nil)) - state.requestStreamPartReceived(part1, promise: nil).assertFailRequest(HTTPClientError.bodyLengthMismatch, .close(nil)) + state.requestStreamPartReceived(part1, promise: nil).assertFailRequest( + HTTPClientError.bodyLengthMismatch, + .close(nil) + ) // if another error happens the new one is ignored XCTAssertEqual(state.errorHappened(HTTPClientError.remoteConnectionClosed), .wait) @@ -86,9 +118,17 @@ class HTTPRequestStateMachineTests: XCTestCase { func testPOSTContentLengthIsTooShort() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "8")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .POST, + uri: "/", + headers: HTTPHeaders([("content-length", "8")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(8)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) let part0 = IOData.byteBuffer(ByteBuffer(bytes: [0, 1, 2, 3])) XCTAssertEqual(state.requestStreamPartReceived(part0, promise: nil), .sendBodyPart(part0, nil)) @@ -97,28 +137,51 @@ class HTTPRequestStateMachineTests: XCTestCase { func testRequestBodyStreamIsCancelledIfServerRespondsWith301() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "12")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .POST, + uri: "/", + headers: HTTPHeaders([("content-length", "12")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(12)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false) + ) let part = IOData.byteBuffer(ByteBuffer(bytes: [0, 1, 2, 3])) XCTAssertEqual(state.requestStreamPartReceived(part, promise: nil), .sendBodyPart(part, nil)) // response is coming before having send all data let responseHead = HTTPResponseHead(version: .http1_1, status: .movedPermanently) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: true)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: true) + ) XCTAssertEqual(state.writabilityChanged(writable: false), .wait) XCTAssertEqual(state.writabilityChanged(writable: true), .wait) - XCTAssertEqual(state.requestStreamPartReceived(part, promise: nil), .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil), - "Expected to drop all stream data after having received a response head, with status >= 300") + XCTAssertEqual( + state.requestStreamPartReceived(part, promise: nil), + .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil), + "Expected to drop all stream data after having received a response head, with status >= 300" + ) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, .init())) - XCTAssertEqual(state.requestStreamPartReceived(part, promise: nil), .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil), - "Expected to drop all stream data after having received a response head, with status >= 300") - - XCTAssertEqual(state.requestStreamFinished(promise: nil), .failSendStreamFinished(HTTPClientError.requestStreamCancelled, nil), - "Expected to drop all stream data after having received a response head, with status >= 300") + XCTAssertEqual( + state.requestStreamPartReceived(part, promise: nil), + .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil), + "Expected to drop all stream data after having received a response head, with status >= 300" + ) + + XCTAssertEqual( + state.requestStreamFinished(promise: nil), + .failSendStreamFinished(HTTPClientError.requestStreamCancelled, nil), + "Expected to drop all stream data after having received a response head, with status >= 300" + ) } func testStreamPartReceived_whenCancelled() { @@ -126,47 +189,84 @@ class HTTPRequestStateMachineTests: XCTestCase { let part = IOData.byteBuffer(ByteBuffer(bytes: [0, 1, 2, 3])) XCTAssertEqual(state.requestCancelled(), .failRequest(HTTPClientError.cancelled, .none)) - XCTAssertEqual(state.requestStreamPartReceived(part, promise: nil), .failSendBodyPart(HTTPClientError.cancelled, nil), - "Expected to drop all stream data after having received a response head, with status >= 300") + XCTAssertEqual( + state.requestStreamPartReceived(part, promise: nil), + .failSendBodyPart(HTTPClientError.cancelled, nil), + "Expected to drop all stream data after having received a response head, with status >= 300" + ) } func testRequestBodyStreamIsCancelledIfServerRespondsWith301WhileWriteBackpressure() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "12")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .POST, + uri: "/", + headers: HTTPHeaders([("content-length", "12")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(12)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) - XCTAssertEqual(state.headSent(), .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) + XCTAssertEqual( + state.headSent(), + .notifyRequestHeadSendSuccessfully(resumeRequestBodyStream: true, startIdleTimer: false) + ) let part = IOData.byteBuffer(ByteBuffer(bytes: [0, 1, 2, 3])) XCTAssertEqual(state.requestStreamPartReceived(part, promise: nil), .sendBodyPart(part, nil)) XCTAssertEqual(state.writabilityChanged(writable: false), .pauseRequestBodyStream) // response is coming before having send all data let responseHead = HTTPResponseHead(version: .http1_1, status: .movedPermanently) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.writabilityChanged(writable: true), .wait) - XCTAssertEqual(state.requestStreamPartReceived(part, promise: nil), .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil), - "Expected to drop all stream data after having received a response head, with status >= 300") + XCTAssertEqual( + state.requestStreamPartReceived(part, promise: nil), + .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil), + "Expected to drop all stream data after having received a response head, with status >= 300" + ) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, .init())) - XCTAssertEqual(state.requestStreamPartReceived(part, promise: nil), .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil), - "Expected to drop all stream data after having received a response head, with status >= 300") - - XCTAssertEqual(state.requestStreamFinished(promise: nil), .failSendStreamFinished(HTTPClientError.requestStreamCancelled, nil), - "Expected to drop all stream data after having received a response head, with status >= 300") + XCTAssertEqual( + state.requestStreamPartReceived(part, promise: nil), + .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil), + "Expected to drop all stream data after having received a response head, with status >= 300" + ) + + XCTAssertEqual( + state.requestStreamFinished(promise: nil), + .failSendStreamFinished(HTTPClientError.requestStreamCancelled, nil), + "Expected to drop all stream data after having received a response head, with status >= 300" + ) } func testRequestBodyStreamIsContinuedIfServerRespondsWith200() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "12")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .POST, + uri: "/", + headers: HTTPHeaders([("content-length", "12")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(12)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) let part0 = IOData.byteBuffer(ByteBuffer(bytes: 0...3)) XCTAssertEqual(state.requestStreamPartReceived(part0, promise: nil), .sendBodyPart(part0, nil)) // response is coming before having send all data let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.channelRead(.end(nil)), .forwardResponseBodyParts(.init())) let part1 = IOData.byteBuffer(ByteBuffer(bytes: 4...7)) @@ -175,20 +275,34 @@ class HTTPRequestStateMachineTests: XCTestCase { XCTAssertEqual(state.requestStreamPartReceived(part2, promise: nil), .sendBodyPart(part2, nil)) XCTAssertEqual(state.requestStreamFinished(promise: nil), .succeedRequest(.sendRequestEnd(nil), .init())) - XCTAssertEqual(state.requestStreamPartReceived(part2, promise: nil), .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil)) + XCTAssertEqual( + state.requestStreamPartReceived(part2, promise: nil), + .failSendBodyPart(HTTPClientError.requestStreamCancelled, nil) + ) } func testRequestBodyStreamIsContinuedIfServerSendHeadWithStatus200() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "12")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .POST, + uri: "/", + headers: HTTPHeaders([("content-length", "12")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(12)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) let part0 = IOData.byteBuffer(ByteBuffer(bytes: 0...3)) XCTAssertEqual(state.requestStreamPartReceived(part0, promise: nil), .sendBodyPart(part0, nil)) // response is coming before having send all data let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let part1 = IOData.byteBuffer(ByteBuffer(bytes: 4...7)) XCTAssertEqual(state.requestStreamPartReceived(part1, promise: nil), .sendBodyPart(part1, nil)) @@ -201,15 +315,26 @@ class HTTPRequestStateMachineTests: XCTestCase { func testRequestIsFailedIfRequestBodySizeIsWrongEvenAfterServerRespondedWith200() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "12")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .POST, + uri: "/", + headers: HTTPHeaders([("content-length", "12")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(12)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) let part0 = IOData.byteBuffer(ByteBuffer(bytes: 0...3)) XCTAssertEqual(state.requestStreamPartReceived(part0, promise: nil), .sendBodyPart(part0, nil)) // response is coming before having send all data let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.channelRead(.end(nil)), .forwardResponseBodyParts(.init())) let part1 = IOData.byteBuffer(ByteBuffer(bytes: 4...7)) @@ -220,15 +345,26 @@ class HTTPRequestStateMachineTests: XCTestCase { func testRequestIsFailedIfRequestBodySizeIsWrongEvenAfterServerSendHeadWithStatus200() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/", headers: HTTPHeaders([("content-length", "12")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .POST, + uri: "/", + headers: HTTPHeaders([("content-length", "12")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(12)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) let part0 = IOData.byteBuffer(ByteBuffer(bytes: 0...3)) XCTAssertEqual(state.requestStreamPartReceived(part0, promise: nil), .sendBodyPart(part0, nil)) // response is coming before having send all data let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let part1 = IOData.byteBuffer(ByteBuffer(bytes: 4...7)) XCTAssertEqual(state.requestStreamPartReceived(part1, promise: nil), .sendBodyPart(part1, nil)) @@ -245,7 +381,10 @@ class HTTPRequestStateMachineTests: XCTestCase { XCTAssertEqual(state.writabilityChanged(writable: true), .sendRequestHead(requestHead, sendEnd: true)) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let responseBody = ByteBuffer(bytes: [1, 2, 3, 4]) XCTAssertEqual(state.channelRead(.body(responseBody)), .wait) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.none, .init([responseBody]))) @@ -264,10 +403,20 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) - - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) + + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let part0 = ByteBuffer(bytes: 0...3) let part1 = ByteBuffer(bytes: 4...7) let part2 = ByteBuffer(bytes: 8...11) @@ -291,10 +440,20 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) - - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) + + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let part0 = ByteBuffer(bytes: 0...3) let part1 = ByteBuffer(bytes: 4...7) let part2 = ByteBuffer(bytes: 8...11) @@ -318,10 +477,20 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) - - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) + + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let part0 = ByteBuffer(bytes: 0...3) let part1 = ByteBuffer(bytes: 4...7) let part2 = ByteBuffer(bytes: 8...11) @@ -336,7 +505,11 @@ class HTTPRequestStateMachineTests: XCTestCase { XCTAssertEqual(state.read(), .read) XCTAssertEqual(state.channelRead(.body(part2)), .wait) XCTAssertEqual(state.read(), .read, "Calling `read` while we wait for a channelReadComplete doesn't crash") - XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait, "Calling `demandMoreResponseBodyParts` while we wait for a channelReadComplete doesn't crash") + XCTAssertEqual( + state.demandMoreResponseBodyParts(), + .wait, + "Calling `demandMoreResponseBodyParts` while we wait for a channelReadComplete doesn't crash" + ) XCTAssertEqual(state.channelReadComplete(), .forwardResponseBodyParts(.init([part2]))) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.read(), .read) @@ -365,11 +538,17 @@ class HTTPRequestStateMachineTests: XCTestCase { // --- sending request let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) // --- receiving response let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["content-length": "4"]) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let responseBody = ByteBuffer(bytes: [1, 2, 3, 4]) XCTAssertEqual(state.channelRead(.body(responseBody)), .wait) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.none, .init([responseBody]))) @@ -380,27 +559,51 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) state.requestCancelled().assertFailRequest(HTTPClientError.cancelled, .close(nil)) } func testRemoteSuddenlyClosesTheConnection() { var state = HTTPRequestStateMachine(isChannelWritable: true) - let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/", headers: .init([("content-length", "4")])) + let requestHead = HTTPRequestHead( + version: .http1_1, + method: .GET, + uri: "/", + headers: .init([("content-length", "4")]) + ) let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(4)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) state.requestCancelled().assertFailRequest(HTTPClientError.cancelled, .close(nil)) - XCTAssertEqual(state.requestStreamPartReceived(.byteBuffer(.init(bytes: 1...3)), promise: nil), .failSendBodyPart(HTTPClientError.cancelled, nil)) + XCTAssertEqual( + state.requestStreamPartReceived(.byteBuffer(.init(bytes: 1...3)), promise: nil), + .failSendBodyPart(HTTPClientError.cancelled, nil) + ) } func testReadTimeoutLeadsToFailureWithEverythingAfterBeingIgnored() { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) - - let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: HTTPHeaders([("content-length", "12")])) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) + + let responseHead = HTTPResponseHead( + version: .http1_1, + status: .ok, + headers: HTTPHeaders([("content-length", "12")]) + ) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) let part0 = ByteBuffer(bytes: 0...3) XCTAssertEqual(state.channelRead(.body(part0)), .wait) state.idleReadTimeoutTriggered().assertFailRequest(HTTPClientError.readTimeout, .close(nil)) @@ -414,13 +617,19 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let continueHead = HTTPResponseHead(version: .http1_1, status: .continue) XCTAssertEqual(state.channelRead(.head(continueHead)), .wait) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.none, .init())) XCTAssertEqual(state.channelReadComplete(), .wait) XCTAssertEqual(state.read(), .read) @@ -430,10 +639,16 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.none, .init())) XCTAssertEqual(state.idleReadTimeoutTriggered(), .wait, "A read timeout that fires to late must be ignored") } @@ -442,10 +657,16 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.none, .init())) XCTAssertEqual(state.requestCancelled(), .wait, "A cancellation that happens to late is ignored") } @@ -454,9 +675,15 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) - - state.errorHappened(HTTPParserError.invalidChunkSize).assertFailRequest(HTTPParserError.invalidChunkSize, .close(nil)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) + + state.errorHappened(HTTPParserError.invalidChunkSize).assertFailRequest( + HTTPParserError.invalidChunkSize, + .close(nil) + ) XCTAssertEqual(state.requestCancelled(), .wait, "A cancellation that happens to late is ignored") } @@ -464,10 +691,16 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_0, status: .internalServerError) - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.channelReadComplete(), .wait) XCTAssertEqual(state.read(), .read) @@ -480,11 +713,17 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_0, status: .internalServerError) let body = ByteBuffer(string: "foo bar") - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.channelReadComplete(), .wait) XCTAssertEqual(state.read(), .read) @@ -498,13 +737,22 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .stream) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: false)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: false) + ) let part1: ByteBuffer = .init(string: "foo") - XCTAssertEqual(state.requestStreamPartReceived(.byteBuffer(part1), promise: nil), .sendBodyPart(.byteBuffer(part1), nil)) + XCTAssertEqual( + state.requestStreamPartReceived(.byteBuffer(part1), promise: nil), + .sendBodyPart(.byteBuffer(part1), nil) + ) let responseHead = HTTPResponseHead(version: .http1_0, status: .ok) let body = ByteBuffer(string: "foo bar") - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.channelReadComplete(), .wait) XCTAssertEqual(state.read(), .read) @@ -518,11 +766,17 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) let body = ByteBuffer(string: "foo bar") - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.channelRead(.body(body)), .wait) state.errorHappened(NIOSSLError.uncleanShutdown).assertFailRequest(NIOSSLError.uncleanShutdown, .close(nil)) @@ -534,7 +788,10 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) XCTAssertEqual(state.errorHappened(NIOSSLError.uncleanShutdown), .wait) state.channelInactive().assertFailRequest(HTTPClientError.remoteConnectionClosed, .none) @@ -545,7 +802,10 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) state.errorHappened(ArbitraryError()).assertFailRequest(ArbitraryError(), .close(nil)) XCTAssertEqual(state.channelInactive(), .wait) @@ -555,17 +815,26 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["content-length": "30"]) let body = ByteBuffer(string: "foo bar") - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.read(), .read) XCTAssertEqual(state.channelRead(.body(body)), .wait) XCTAssertEqual(state.channelReadComplete(), .forwardResponseBodyParts([body])) XCTAssertEqual(state.errorHappened(NIOSSLError.uncleanShutdown), .wait) - state.errorHappened(HTTPParserError.invalidEOFState).assertFailRequest(HTTPParserError.invalidEOFState, .close(nil)) + state.errorHappened(HTTPParserError.invalidEOFState).assertFailRequest( + HTTPParserError.invalidEOFState, + .close(nil) + ) XCTAssertEqual(state.channelInactive(), .wait) } @@ -573,11 +842,17 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["Content-Length": "50"]) let body = ByteBuffer(string: "foo bar") - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.channelReadComplete(), .wait) XCTAssertEqual(state.read(), .read) @@ -594,11 +869,17 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["Content-Length": "50"]) let body = ByteBuffer(string: "foo bar") - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.channelReadComplete(), .wait) XCTAssertEqual(state.read(), .read) @@ -615,11 +896,17 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["Content-Length": "50"]) let body = ByteBuffer(string: "foo bar") - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.channelReadComplete(), .wait) XCTAssertEqual(state.read(), .read) @@ -635,11 +922,17 @@ class HTTPRequestStateMachineTests: XCTestCase { var state = HTTPRequestStateMachine(isChannelWritable: true) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .fixedSize(0)) - XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, sendEnd: true)) + XCTAssertEqual( + state.startRequest(head: requestHead, metadata: metadata), + .sendRequestHead(requestHead, sendEnd: true) + ) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["Content-Length": "50"]) let body = ByteBuffer(string: "foo bar") - XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) + XCTAssertEqual( + state.channelRead(.head(responseHead)), + .forwardResponseHead(responseHead, pauseRequestBodyStream: false) + ) XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait) XCTAssertEqual(state.channelReadComplete(), .wait) XCTAssertEqual(state.read(), .read) @@ -688,13 +981,19 @@ extension HTTPRequestStateMachine.Action: Equatable { case (.resumeRequestBodyStream, .resumeRequestBodyStream): return true - case (.forwardResponseHead(let lhsHead, let lhsPauseRequestBodyStream), .forwardResponseHead(let rhsHead, let rhsPauseRequestBodyStream)): + case ( + .forwardResponseHead(let lhsHead, let lhsPauseRequestBodyStream), + .forwardResponseHead(let rhsHead, let rhsPauseRequestBodyStream) + ): return lhsHead == rhsHead && lhsPauseRequestBodyStream == rhsPauseRequestBodyStream case (.forwardResponseBodyParts(let lhsData), .forwardResponseBodyParts(let rhsData)): return lhsData == rhsData - case (.succeedRequest(let lhsFinalAction, let lhsFinalBuffer), .succeedRequest(let rhsFinalAction, let rhsFinalBuffer)): + case ( + .succeedRequest(let lhsFinalAction, let lhsFinalBuffer), + .succeedRequest(let rhsFinalAction, let rhsFinalBuffer) + ): return lhsFinalAction == rhsFinalAction && lhsFinalBuffer == rhsFinalBuffer case (.failRequest(_, let lhsFinalAction), .failRequest(_, let rhsFinalAction)): @@ -706,10 +1005,16 @@ extension HTTPRequestStateMachine.Action: Equatable { case (.wait, .wait): return true - case (.failSendBodyPart(let lhsError as HTTPClientError, let lhsPromise), .failSendBodyPart(let rhsError as HTTPClientError, let rhsPromise)): + case ( + .failSendBodyPart(let lhsError as HTTPClientError, let lhsPromise), + .failSendBodyPart(let rhsError as HTTPClientError, let rhsPromise) + ): return lhsError == rhsError && lhsPromise?.futureResult == rhsPromise?.futureResult - case (.failSendStreamFinished(let lhsError as HTTPClientError, let lhsPromise), .failSendStreamFinished(let rhsError as HTTPClientError, let rhsPromise)): + case ( + .failSendStreamFinished(let lhsError as HTTPClientError, let lhsPromise), + .failSendStreamFinished(let rhsError as HTTPClientError, let rhsPromise) + ): return lhsError == rhsError && lhsPromise?.futureResult == rhsPromise?.futureResult default: @@ -719,7 +1024,10 @@ extension HTTPRequestStateMachine.Action: Equatable { } extension HTTPRequestStateMachine.Action.FinalSuccessfulRequestAction: Equatable { - public static func == (lhs: HTTPRequestStateMachine.Action.FinalSuccessfulRequestAction, rhs: HTTPRequestStateMachine.Action.FinalSuccessfulRequestAction) -> Bool { + public static func == ( + lhs: HTTPRequestStateMachine.Action.FinalSuccessfulRequestAction, + rhs: HTTPRequestStateMachine.Action.FinalSuccessfulRequestAction + ) -> Bool { switch (lhs, rhs) { case (.close, close): return true @@ -737,7 +1045,10 @@ extension HTTPRequestStateMachine.Action.FinalSuccessfulRequestAction: Equatable } extension HTTPRequestStateMachine.Action.FinalFailedRequestAction: Equatable { - public static func == (lhs: HTTPRequestStateMachine.Action.FinalFailedRequestAction, rhs: HTTPRequestStateMachine.Action.FinalFailedRequestAction) -> Bool { + public static func == ( + lhs: HTTPRequestStateMachine.Action.FinalFailedRequestAction, + rhs: HTTPRequestStateMachine.Action.FinalFailedRequestAction + ) -> Bool { switch (lhs, rhs) { case (.close(let lhsPromise), close(let rhsPromise)): return lhsPromise?.futureResult == rhsPromise?.futureResult @@ -759,7 +1070,11 @@ extension HTTPRequestStateMachine.Action { line: UInt = #line ) where Error: Swift.Error & Equatable { guard case .failRequest(let actualError, let actualFinalStreamAction) = self else { - return XCTFail("expected .failRequest(\(expectedError), \(expectedFinalStreamAction)) but got \(self)", file: file, line: line) + return XCTFail( + "expected .failRequest(\(expectedError), \(expectedFinalStreamAction)) but got \(self)", + file: file, + line: line + ) } if let actualError = actualError as? Error { XCTAssertEqual(actualError, expectedError, file: file, line: line) diff --git a/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests.swift b/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests.swift index e7cfed4d0..e9a0d46dc 100644 --- a/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests.swift +++ b/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests.swift @@ -14,9 +14,6 @@ import AsyncHTTPClient import Atomics -#if canImport(Network) -import Network -#endif import Logging import NIOConcurrencyHelpers import NIOCore @@ -29,6 +26,10 @@ import NIOTestUtils import NIOTransportServices import XCTest +#if canImport(Network) +import Network +#endif + final class TestIdleTimeoutNoReuse: XCTestCaseHTTPClientTestsBaseClass { func testIdleTimeoutNoReuse() throws { var req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .GET) diff --git a/Tests/AsyncHTTPClientTests/LRUCacheTests.swift b/Tests/AsyncHTTPClientTests/LRUCacheTests.swift index 6392bcebe..6173c34eb 100644 --- a/Tests/AsyncHTTPClientTests/LRUCacheTests.swift +++ b/Tests/AsyncHTTPClientTests/LRUCacheTests.swift @@ -12,9 +12,10 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import XCTest +@testable import AsyncHTTPClient + class LRUCacheTests: XCTestCase { func testBasicsWork() { var cache = LRUCache(capacity: 1) diff --git a/Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift b/Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift index 7b4eb19d9..e49c67f19 100644 --- a/Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift +++ b/Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOHTTP1 import NIOSSL +@testable import AsyncHTTPClient + /// A mock connection pool (not creating any actual connections) that is used to validate /// connection actions returned by the `HTTPConnectionPool.StateMachine`. struct MockConnectionPool { @@ -554,7 +555,9 @@ extension MockConnectionPool { let request = HTTPConnectionPool.Request(mockRequest) let action = state.executeRequest(request) - guard case .scheduleRequestTimeout(request, on: let waitEL) = action.request, mockRequest.eventLoop === waitEL else { + guard case .scheduleRequestTimeout(request, on: let waitEL) = action.request, + mockRequest.eventLoop === waitEL + else { throw SetupError.expectedRequestToBeAddedToQueue } @@ -621,7 +624,9 @@ extension MockConnectionPool { let request = HTTPConnectionPool.Request(mockRequest) let executeAction = state.executeRequest(request) - guard case .scheduleRequestTimeout(request, on: let waitEL) = executeAction.request, mockRequest.eventLoop === waitEL else { + guard case .scheduleRequestTimeout(request, on: let waitEL) = executeAction.request, + mockRequest.eventLoop === waitEL + else { throw SetupError.expectedRequestToBeAddedToQueue } @@ -634,7 +639,10 @@ extension MockConnectionPool { // 2. the connection becomes available - let newConnection = try connections.succeedConnectionCreationHTTP2(connectionID, maxConcurrentStreams: maxConcurrentStreams) + let newConnection = try connections.succeedConnectionCreationHTTP2( + connectionID, + maxConcurrentStreams: maxConcurrentStreams + ) let action = state.newHTTP2ConnectionCreated(newConnection, maxConcurrentStreams: maxConcurrentStreams) guard case .executeRequestsAndCancelTimeouts([request], newConnection) = action.request else { @@ -674,10 +682,12 @@ final class MockHTTPScheduableRequest: HTTPSchedulableRequest { let preferredEventLoop: EventLoop let requiredEventLoop: EventLoop? - init(eventLoop: EventLoop, - logger: Logger = Logger(label: "mock"), - connectionTimeout: TimeAmount = .seconds(60), - requiresEventLoopForChannel: Bool = false) { + init( + eventLoop: EventLoop, + logger: Logger = Logger(label: "mock"), + connectionTimeout: TimeAmount = .seconds(60), + requiresEventLoopForChannel: Bool = false + ) { self.logger = logger self.connectionDeadline = .now() + connectionTimeout @@ -692,7 +702,7 @@ final class MockHTTPScheduableRequest: HTTPSchedulableRequest { } var eventLoop: EventLoop { - return self.preferredEventLoop + self.preferredEventLoop } // MARK: HTTPSchedulableRequest diff --git a/Tests/AsyncHTTPClientTests/Mocks/MockHTTPExecutableRequest.swift b/Tests/AsyncHTTPClientTests/Mocks/MockHTTPExecutableRequest.swift index aa0dc45eb..021c69731 100644 --- a/Tests/AsyncHTTPClientTests/Mocks/MockHTTPExecutableRequest.swift +++ b/Tests/AsyncHTTPClientTests/Mocks/MockHTTPExecutableRequest.swift @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOHTTP1 import XCTest +@testable import AsyncHTTPClient + final class MockHTTPExecutableRequest: HTTPExecutableRequest { enum Event { /// ``Event`` without associated values diff --git a/Tests/AsyncHTTPClientTests/Mocks/MockRequestExecutor.swift b/Tests/AsyncHTTPClientTests/Mocks/MockRequestExecutor.swift index b37ce8fa3..f85c75ce5 100644 --- a/Tests/AsyncHTTPClientTests/Mocks/MockRequestExecutor.swift +++ b/Tests/AsyncHTTPClientTests/Mocks/MockRequestExecutor.swift @@ -12,10 +12,11 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOConcurrencyHelpers import NIOCore +@testable import AsyncHTTPClient + // This is a MockRequestExecutor, that is synchronized on its EventLoop. final class MockRequestExecutor { enum Errors: Error { @@ -47,7 +48,7 @@ final class MockRequestExecutor { } var requestBodyPartsCount: Int { - return self.blockingQueue.count + self.blockingQueue.count } let eventLoop: EventLoop @@ -82,7 +83,8 @@ final class MockRequestExecutor { request.requestHeadSent() } - func receiveRequestBody(deadline: NIODeadline = .now() + .seconds(5), _ verify: (ByteBuffer) throws -> Void) throws { + func receiveRequestBody(deadline: NIODeadline = .now() + .seconds(5), _ verify: (ByteBuffer) throws -> Void) throws + { enum ReceiveAction { case value(RequestParts) case future(EventLoopFuture) @@ -155,10 +157,11 @@ final class MockRequestExecutor { func receiveResponseDemand(deadline: NIODeadline = .now() + .seconds(5)) throws { let secondsUntilDeath = deadline - NIODeadline.now() - guard self.responseBodyDemandLock.lock( - whenValue: true, - timeoutSeconds: .init(secondsUntilDeath.nanoseconds / 1_000_000_000) - ) + guard + self.responseBodyDemandLock.lock( + whenValue: true, + timeoutSeconds: .init(secondsUntilDeath.nanoseconds / 1_000_000_000) + ) else { throw TimeoutError() } @@ -168,10 +171,11 @@ final class MockRequestExecutor { func receiveCancellation(deadline: NIODeadline = .now() + .seconds(5)) throws { let secondsUntilDeath = deadline - NIODeadline.now() - guard self.cancellationLock.lock( - whenValue: true, - timeoutSeconds: .init(secondsUntilDeath.nanoseconds / 1_000_000_000) - ) + guard + self.cancellationLock.lock( + whenValue: true, + timeoutSeconds: .init(secondsUntilDeath.nanoseconds / 1_000_000_000) + ) else { throw TimeoutError() } @@ -265,8 +269,12 @@ extension MockRequestExecutor { internal func popFirst(deadline: NIODeadline) throws -> Element { let secondsUntilDeath = deadline - NIODeadline.now() - guard self.condition.lock(whenValue: true, - timeoutSeconds: .init(secondsUntilDeath.nanoseconds / 1_000_000_000)) else { + guard + self.condition.lock( + whenValue: true, + timeoutSeconds: .init(secondsUntilDeath.nanoseconds / 1_000_000_000) + ) + else { throw TimeoutError() } let first = self.buffer.removeFirst() diff --git a/Tests/AsyncHTTPClientTests/Mocks/MockRequestQueuer.swift b/Tests/AsyncHTTPClientTests/Mocks/MockRequestQueuer.swift index 520b51875..44e820444 100644 --- a/Tests/AsyncHTTPClientTests/Mocks/MockRequestQueuer.swift +++ b/Tests/AsyncHTTPClientTests/Mocks/MockRequestQueuer.swift @@ -12,11 +12,12 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOCore import NIOHTTP1 +@testable import AsyncHTTPClient + /// A mock request queue (not creating any timers) that is used to validate /// request actions returned by the `HTTPConnectionPool.StateMachine`. struct MockRequestQueuer { diff --git a/Tests/AsyncHTTPClientTests/NWWaitingHandlerTests.swift b/Tests/AsyncHTTPClientTests/NWWaitingHandlerTests.swift index ff9e7f45d..033214ffe 100644 --- a/Tests/AsyncHTTPClientTests/NWWaitingHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/NWWaitingHandlerTests.swift @@ -47,9 +47,14 @@ class NWWaitingHandlerTests: XCTestCase { let waitingEventHandler = NWWaitingHandler(requester: requester, connectionID: connectionID) let embedded = EmbeddedChannel(handlers: [waitingEventHandler]) - embedded.pipeline.fireUserInboundEventTriggered(NIOTSNetworkEvents.WaitingForConnectivity(transientError: .dns(1))) + embedded.pipeline.fireUserInboundEventTriggered( + NIOTSNetworkEvents.WaitingForConnectivity(transientError: .dns(1)) + ) - XCTAssertTrue(requester.waitingForConnectivityCalled, "Expected the handler to invoke .waitingForConnectivity on the requester") + XCTAssertTrue( + requester.waitingForConnectivityCalled, + "Expected the handler to invoke .waitingForConnectivity on the requester" + ) XCTAssertEqual(requester.connectionID, connectionID, "Expected the handler to pass connectionID to requester") XCTAssertEqual(requester.transientError, NWError.dns(1)) } @@ -60,7 +65,10 @@ class NWWaitingHandlerTests: XCTestCase { let embedded = EmbeddedChannel(handlers: [waitingEventHandler]) embedded.pipeline.fireUserInboundEventTriggered(NIOTSNetworkEvents.BetterPathAvailable()) - XCTAssertFalse(requester.waitingForConnectivityCalled, "Should not call .waitingForConnectivity on unrelated events") + XCTAssertFalse( + requester.waitingForConnectivityCalled, + "Should not call .waitingForConnectivity on unrelated events" + ) } func testWaitingHandlerPassesTheEventDownTheContext() { diff --git a/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests.swift b/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests.swift index 756facb3f..026a45d4c 100644 --- a/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests.swift +++ b/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests.swift @@ -14,9 +14,6 @@ import AsyncHTTPClient import Atomics -#if canImport(Network) -import Network -#endif import Logging import NIOConcurrencyHelpers import NIOCore @@ -29,6 +26,10 @@ import NIOTestUtils import NIOTransportServices import XCTest +#if canImport(Network) +import Network +#endif + final class NoBytesSentOverBodyLimitTests: XCTestCaseHTTPClientTestsBaseClass { func testNoBytesSentOverBodyLimit() throws { let server = NIOHTTP1TestServer(group: self.serverGroup) diff --git a/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests.swift b/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests.swift index fd8e45273..35a09c421 100644 --- a/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests.swift +++ b/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests.swift @@ -14,9 +14,6 @@ import AsyncHTTPClient import Atomics -#if canImport(Network) -import Network -#endif import Logging import NIOConcurrencyHelpers import NIOCore @@ -29,10 +26,16 @@ import NIOTestUtils import NIOTransportServices import XCTest +#if canImport(Network) +import Network +#endif + final class RacePoolIdleConnectionsAndGetTests: XCTestCaseHTTPClientTestsBaseClass { func testRacePoolIdleConnectionsAndGet() { - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(connectionPool: .init(idleTimeout: .milliseconds(10)))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(connectionPool: .init(idleTimeout: .milliseconds(10))) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } diff --git a/Tests/AsyncHTTPClientTests/RequestBagTests.swift b/Tests/AsyncHTTPClientTests/RequestBagTests.swift index a9b9bd0dd..365c1063c 100644 --- a/Tests/AsyncHTTPClientTests/RequestBagTests.swift +++ b/Tests/AsyncHTTPClientTests/RequestBagTests.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOConcurrencyHelpers import NIOCore @@ -21,6 +20,8 @@ import NIOHTTP1 import NIOPosix import XCTest +@testable import AsyncHTTPClient + final class RequestBagTests: XCTestCase { func testWriteBackpressureWorks() { let embeddedEventLoop = EmbeddedEventLoop() @@ -39,7 +40,8 @@ final class RequestBagTests: XCTestCase { let expectedWrites = bytesToSent / 100 + ((bytesToSent % 100 > 0) ? 1 : 0) let writeDonePromise = embeddedEventLoop.makePromise(of: Void.self) - let requestBody: HTTPClient.Body = .stream(contentLength: Int64(bytesToSent)) { writer -> EventLoopFuture in + let requestBody: HTTPClient.Body = .stream(contentLength: Int64(bytesToSent)) { + writer -> EventLoopFuture in @Sendable func write(donePromise: EventLoopPromise) { let futureWrite: EventLoopFuture? = testState.withLockedValue { state in XCTAssertTrue(state.streamIsAllowedToWrite) @@ -67,20 +69,24 @@ final class RequestBagTests: XCTestCase { } var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "https://swift.org", method: .POST, body: requestBody)) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request(url: "https://swift.org", method: .POST, body: requestBody) + ) guard let request = maybeRequest else { return XCTFail("Expected to have a request") } let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } XCTAssert(bag.task.eventLoop === embeddedEventLoop) @@ -101,9 +107,11 @@ final class RequestBagTests: XCTestCase { // after starting the body stream we should have received two writes var receivedBytes = 0 for i in 0..? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } XCTAssert(bag.task.eventLoop === embeddedEventLoop) @@ -220,15 +236,17 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } XCTAssert(bag.eventLoop === embeddedEventLoop) @@ -253,15 +271,17 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } XCTAssert(bag.eventLoop === embeddedEventLoop) @@ -292,15 +312,17 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } XCTAssert(bag.eventLoop === embeddedEventLoop) @@ -333,15 +355,17 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } XCTAssert(bag.eventLoop === embeddedEventLoop) @@ -374,15 +398,17 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } let queuer = MockTaskQueuer() @@ -408,15 +434,17 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) @@ -436,23 +464,27 @@ final class RequestBagTests: XCTestCase { let logger = Logger(label: "test") var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( - url: "https://swift.org", - body: .bytes([1, 2, 3, 4, 5]) - )) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "https://swift.org", + body: .bytes([1, 2, 3, 4, 5]) + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to have a request") } let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) @@ -476,11 +508,13 @@ final class RequestBagTests: XCTestCase { var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( - url: "https://swift.org", - method: .POST, - body: .byteBuffer(.init(bytes: [1])) - )) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "https://swift.org", + method: .POST, + body: .byteBuffer(.init(bytes: [1])) + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to have a request") } struct MyError: Error, Equatable {} @@ -506,15 +540,17 @@ final class RequestBagTests: XCTestCase { } let delegate = Delegate(didFinishPromise: embeddedEventLoop.makePromise()) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) @@ -545,40 +581,44 @@ final class RequestBagTests: XCTestCase { let writeSecondPartPromise = embeddedEventLoop.makePromise(of: Void.self) let firstWriteSuccess: NIOLockedValueBox = .init(false) - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( - url: "https://swift.org", - method: .POST, - headers: ["content-length": "12"], - body: .stream(contentLength: 12) { writer -> EventLoopFuture in - return writer.write(.byteBuffer(.init(bytes: 0...3))).flatMap { _ in - firstWriteSuccess.withLockedValue { $0 = true } - - return writeSecondPartPromise.futureResult - }.flatMap { - return writer.write(.byteBuffer(.init(bytes: 4...7))) - }.always { result in - XCTAssertTrue(firstWriteSuccess.withLockedValue { $0 }) - - guard case .failure(let error) = result else { - return XCTFail("Expected the second write to fail") + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request( + url: "https://swift.org", + method: .POST, + headers: ["content-length": "12"], + body: .stream(contentLength: 12) { writer -> EventLoopFuture in + writer.write(.byteBuffer(.init(bytes: 0...3))).flatMap { _ in + firstWriteSuccess.withLockedValue { $0 = true } + + return writeSecondPartPromise.futureResult + }.flatMap { + writer.write(.byteBuffer(.init(bytes: 4...7))) + }.always { result in + XCTAssertTrue(firstWriteSuccess.withLockedValue { $0 }) + + guard case .failure(let error) = result else { + return XCTFail("Expected the second write to fail") + } + XCTAssertEqual(error as? HTTPClientError, .requestStreamCancelled) } - XCTAssertEqual(error as? HTTPClientError, .requestStreamCancelled) } - } - )) + ) + ) guard let request = maybeRequest else { return XCTFail("Expected to have a request") } let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) @@ -614,15 +654,17 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: nil, - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) @@ -670,36 +712,47 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? var redirectTriggered = false - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: .init( + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( request: request, - redirectState: RedirectState( - .follow(max: 5, allowCycles: false), - initialURL: request.url.absoluteString - )!, - execute: { request, _ in - XCTAssertEqual(request.url.absoluteString, "https://swift.org/sswg") - XCTAssertFalse(redirectTriggered) - - let task = HTTPClient.Task(eventLoop: embeddedEventLoop, logger: logger) - task.promise.fail(HTTPClientError.cancelled) - redirectTriggered = true - return task - } - ), - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: .init( + request: request, + redirectState: RedirectState( + .follow(max: 5, allowCycles: false), + initialURL: request.url.absoluteString + )!, + execute: { request, _ in + XCTAssertEqual(request.url.absoluteString, "https://swift.org/sswg") + XCTAssertFalse(redirectTriggered) + + let task = HTTPClient.Task( + eventLoop: embeddedEventLoop, + logger: logger + ) + task.promise.fail(HTTPClientError.cancelled) + redirectTriggered = true + return task + } + ), + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) executor.runRequest(bag) XCTAssertFalse(executor.signalledDemandForResponseBody) - bag.receiveResponseHead(.init(version: .http1_1, status: .permanentRedirect, headers: ["content-length": "\(3 * 1024)", "location": "https://swift.org/sswg"])) + bag.receiveResponseHead( + .init( + version: .http1_1, + status: .permanentRedirect, + headers: ["content-length": "\(3 * 1024)", "location": "https://swift.org/sswg"] + ) + ) XCTAssertNil(delegate.backpressurePromise) XCTAssertTrue(executor.signalledDemandForResponseBody) executor.resetResponseStreamDemandSignal() @@ -745,36 +798,47 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? var redirectTriggered = false - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: .init( + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( request: request, - redirectState: RedirectState( - .follow(max: 5, allowCycles: false), - initialURL: request.url.absoluteString - )!, - execute: { request, _ in - XCTAssertEqual(request.url.absoluteString, "https://swift.org/sswg") - XCTAssertFalse(redirectTriggered) - - let task = HTTPClient.Task(eventLoop: embeddedEventLoop, logger: logger) - task.promise.fail(HTTPClientError.cancelled) - redirectTriggered = true - return task - } - ), - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: .init( + request: request, + redirectState: RedirectState( + .follow(max: 5, allowCycles: false), + initialURL: request.url.absoluteString + )!, + execute: { request, _ in + XCTAssertEqual(request.url.absoluteString, "https://swift.org/sswg") + XCTAssertFalse(redirectTriggered) + + let task = HTTPClient.Task( + eventLoop: embeddedEventLoop, + logger: logger + ) + task.promise.fail(HTTPClientError.cancelled) + redirectTriggered = true + return task + } + ), + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) executor.runRequest(bag) XCTAssertFalse(executor.signalledDemandForResponseBody) - bag.receiveResponseHead(.init(version: .http1_1, status: .permanentRedirect, headers: ["content-length": "\(4 * 1024)", "location": "https://swift.org/sswg"])) + bag.receiveResponseHead( + .init( + version: .http1_1, + status: .permanentRedirect, + headers: ["content-length": "\(4 * 1024)", "location": "https://swift.org/sswg"] + ) + ) XCTAssertNil(delegate.backpressurePromise) XCTAssertFalse(executor.signalledDemandForResponseBody) XCTAssertTrue(executor.isCancelled) @@ -794,36 +858,47 @@ final class RequestBagTests: XCTestCase { let delegate = UploadCountingDelegate(eventLoop: embeddedEventLoop) var maybeRequestBag: RequestBag? var redirectTriggered = false - XCTAssertNoThrow(maybeRequestBag = try RequestBag( - request: request, - eventLoopPreference: .delegate(on: embeddedEventLoop), - task: .init(eventLoop: embeddedEventLoop, logger: logger), - redirectHandler: .init( + XCTAssertNoThrow( + maybeRequestBag = try RequestBag( request: request, - redirectState: RedirectState( - .follow(max: 5, allowCycles: false), - initialURL: request.url.absoluteString - )!, - execute: { request, _ in - XCTAssertEqual(request.url.absoluteString, "https://swift.org/sswg") - XCTAssertFalse(redirectTriggered) - - let task = HTTPClient.Task(eventLoop: embeddedEventLoop, logger: logger) - task.promise.fail(HTTPClientError.cancelled) - redirectTriggered = true - return task - } - ), - connectionDeadline: .now() + .seconds(30), - requestOptions: .forTests(), - delegate: delegate - )) + eventLoopPreference: .delegate(on: embeddedEventLoop), + task: .init(eventLoop: embeddedEventLoop, logger: logger), + redirectHandler: .init( + request: request, + redirectState: RedirectState( + .follow(max: 5, allowCycles: false), + initialURL: request.url.absoluteString + )!, + execute: { request, _ in + XCTAssertEqual(request.url.absoluteString, "https://swift.org/sswg") + XCTAssertFalse(redirectTriggered) + + let task = HTTPClient.Task( + eventLoop: embeddedEventLoop, + logger: logger + ) + task.promise.fail(HTTPClientError.cancelled) + redirectTriggered = true + return task + } + ), + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + ) + ) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) executor.runRequest(bag) XCTAssertFalse(executor.signalledDemandForResponseBody) - bag.receiveResponseHead(.init(version: .http1_1, status: .permanentRedirect, headers: ["content-length": "\(3 * 1024)", "location": "https://swift.org/sswg"])) + bag.receiveResponseHead( + .init( + version: .http1_1, + status: .permanentRedirect, + headers: ["content-length": "\(3 * 1024)", "location": "https://swift.org/sswg"] + ) + ) XCTAssertNil(delegate.backpressurePromise) XCTAssertTrue(executor.signalledDemandForResponseBody) executor.resetResponseStreamDemandSignal() @@ -867,7 +942,9 @@ final class RequestBagTests: XCTestCase { do { var maybeRequest: HTTPClient.Request? - XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/", method: .POST)) + XCTAssertNoThrow( + maybeRequest = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/", method: .POST) + ) guard var request = maybeRequest else { return XCTFail("Expected to have a request here") } let writerPromise = group.any().makePromise(of: HTTPClient.Body.StreamWriter.self) diff --git a/Tests/AsyncHTTPClientTests/RequestValidationTests.swift b/Tests/AsyncHTTPClientTests/RequestValidationTests.swift index c50d3afd1..ea5a6bd66 100644 --- a/Tests/AsyncHTTPClientTests/RequestValidationTests.swift +++ b/Tests/AsyncHTTPClientTests/RequestValidationTests.swift @@ -12,11 +12,12 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOHTTP1 import XCTest +@testable import AsyncHTTPClient + class RequestValidationTests: XCTestCase { func testContentLengthHeaderIsRemovedFromGETIfNoBody() { var headers = HTTPHeaders([("Content-Length", "0")]) @@ -29,13 +30,17 @@ class RequestValidationTests: XCTestCase { func testContentLengthHeaderIsAddedToPOSTAndPUTWithNoBody() { var putHeaders = HTTPHeaders() var putMetadata: RequestFramingMetadata? - XCTAssertNoThrow(putMetadata = try putHeaders.validateAndSetTransportFraming(method: .PUT, bodyLength: .known(0))) + XCTAssertNoThrow( + putMetadata = try putHeaders.validateAndSetTransportFraming(method: .PUT, bodyLength: .known(0)) + ) XCTAssertEqual(putHeaders.first(name: "Content-Length"), "0") XCTAssertEqual(putMetadata?.body, .fixedSize(0)) var postHeaders = HTTPHeaders() var postMetadata: RequestFramingMetadata? - XCTAssertNoThrow(postMetadata = try postHeaders.validateAndSetTransportFraming(method: .POST, bodyLength: .known(0))) + XCTAssertNoThrow( + postMetadata = try postHeaders.validateAndSetTransportFraming(method: .POST, bodyLength: .known(0)) + ) XCTAssertEqual(postHeaders.first(name: "Content-Length"), "0") XCTAssertEqual(postMetadata?.body, .fixedSize(0)) } @@ -90,7 +95,7 @@ class RequestValidationTests: XCTestCase { func testMetadataDetectConnectionClose() { var headers = HTTPHeaders([ - ("Connection", "close"), + ("Connection", "close") ]) var metadata: RequestFramingMetadata? XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: .GET, bodyLength: .known(0))) @@ -114,7 +119,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT, .TRACE] { var headers: HTTPHeaders = .init() var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0)) + ) XCTAssertTrue(headers["content-length"].isEmpty) XCTAssertTrue(headers["transfer-encoding"].isEmpty) XCTAssertEqual(metadata?.body, .fixedSize(0)) @@ -123,7 +130,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.POST, .PUT] { var headers: HTTPHeaders = .init() var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0)) + ) XCTAssertEqual(headers["content-length"].first, "0") XCTAssertFalse(headers["transfer-encoding"].contains("chunked")) XCTAssertEqual(metadata?.body, .fixedSize(0)) @@ -139,7 +148,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT] { var headers: HTTPHeaders = .init() var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(1))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(1)) + ) XCTAssertEqual(headers["content-length"].first, "1") XCTAssertTrue(headers["transfer-encoding"].isEmpty) XCTAssertEqual(metadata?.body, .fixedSize(1)) @@ -149,7 +160,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT] { var headers: HTTPHeaders = .init() var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .unknown)) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .unknown) + ) XCTAssertTrue(headers["content-length"].isEmpty) XCTAssertTrue(headers["transfer-encoding"].contains("chunked")) XCTAssertEqual(metadata?.body, .stream) @@ -159,7 +172,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.POST, .PUT] { var headers: HTTPHeaders = .init() var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(1))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(1)) + ) XCTAssertEqual(headers["content-length"].first, "1") XCTAssertTrue(headers["transfer-encoding"].isEmpty) XCTAssertEqual(metadata?.body, .fixedSize(1)) @@ -169,7 +184,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.POST, .PUT] { var headers: HTTPHeaders = .init() var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .unknown)) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .unknown) + ) XCTAssertTrue(headers["content-length"].isEmpty) XCTAssertTrue(headers["transfer-encoding"].contains("chunked")) XCTAssertEqual(metadata?.body, .stream) @@ -184,7 +201,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT, .TRACE] { var headers: HTTPHeaders = .init([("Content-Length", "1")]) var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0)) + ) XCTAssertTrue(headers["content-length"].isEmpty) XCTAssertTrue(headers["transfer-encoding"].isEmpty) XCTAssertEqual(metadata?.body, .fixedSize(0)) @@ -193,7 +212,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.POST, .PUT] { var headers: HTTPHeaders = .init([("Content-Length", "1")]) var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0)) + ) XCTAssertEqual(headers["content-length"].first, "0") XCTAssertTrue(headers["transfer-encoding"].isEmpty) XCTAssertEqual(metadata?.body, .fixedSize(0)) @@ -208,7 +229,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT] { var headers: HTTPHeaders = .init([("Content-Length", "1")]) var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(1))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(1)) + ) XCTAssertEqual(headers["content-length"].first, "1") XCTAssertTrue(headers["transfer-encoding"].isEmpty) XCTAssertEqual(metadata?.body, .fixedSize(1)) @@ -217,7 +240,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.POST, .PUT] { var headers: HTTPHeaders = .init([("Content-Length", "1")]) var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(1))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(1)) + ) XCTAssertEqual(headers["content-length"].first, "1") XCTAssertTrue(headers["transfer-encoding"].isEmpty) XCTAssertEqual(metadata?.body, .fixedSize(1)) @@ -232,7 +257,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.GET, .HEAD, .DELETE, .CONNECT, .TRACE] { var headers: HTTPHeaders = .init([("Transfer-Encoding", "chunked")]) var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0)) + ) XCTAssertTrue(headers["content-length"].isEmpty) XCTAssertFalse(headers["transfer-encoding"].contains("chunked")) XCTAssertEqual(metadata?.body, .fixedSize(0)) @@ -241,7 +268,9 @@ class RequestValidationTests: XCTestCase { for method: HTTPMethod in [.POST, .PUT] { var headers: HTTPHeaders = .init([("Transfer-Encoding", "chunked")]) var metadata: RequestFramingMetadata? - XCTAssertNoThrow(metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0))) + XCTAssertNoThrow( + metadata = try headers.validateAndSetTransportFraming(method: method, bodyLength: .known(0)) + ) XCTAssertEqual(headers["content-length"].first, "0") XCTAssertFalse(headers["transfer-encoding"].contains("chunked")) XCTAssertEqual(metadata?.body, .fixedSize(0)) @@ -337,21 +366,27 @@ class RequestValidationTests: XCTestCase { func testTransferEncodingsAreOverwrittenIfBodyLengthIsFixed() { var headers: HTTPHeaders = [ - "Transfer-Encoding": "gzip, chunked", + "Transfer-Encoding": "gzip, chunked" ] XCTAssertNoThrow(try headers.validateAndSetTransportFraming(method: .POST, bodyLength: .known(1))) - XCTAssertEqual(headers, [ - "Content-Length": "1", - ]) + XCTAssertEqual( + headers, + [ + "Content-Length": "1" + ] + ) } func testTransferEncodingsAreOverwrittenIfBodyLengthIsDynamic() { var headers: HTTPHeaders = [ - "Transfer-Encoding": "gzip, chunked", + "Transfer-Encoding": "gzip, chunked" ] XCTAssertNoThrow(try headers.validateAndSetTransportFraming(method: .POST, bodyLength: .unknown)) - XCTAssertEqual(headers, [ - "Transfer-Encoding": "chunked", - ]) + XCTAssertEqual( + headers, + [ + "Transfer-Encoding": "chunked" + ] + ) } } diff --git a/Tests/AsyncHTTPClientTests/ResponseDelayGetTests.swift b/Tests/AsyncHTTPClientTests/ResponseDelayGetTests.swift index 0af5c7243..5fd1d6720 100644 --- a/Tests/AsyncHTTPClientTests/ResponseDelayGetTests.swift +++ b/Tests/AsyncHTTPClientTests/ResponseDelayGetTests.swift @@ -14,9 +14,6 @@ import AsyncHTTPClient import Atomics -#if canImport(Network) -import Network -#endif import Logging import NIOConcurrencyHelpers import NIOCore @@ -29,15 +26,21 @@ import NIOTestUtils import NIOTransportServices import XCTest +#if canImport(Network) +import Network +#endif + final class ResponseDelayGetTests: XCTestCaseHTTPClientTestsBaseClass { func testResponseDelayGet() throws { - let req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", - method: .GET, - headers: ["X-internal-delay": "2000"], - body: nil) + let req = try HTTPClient.Request( + url: self.defaultHTTPBinURLPrefix + "get", + method: .GET, + headers: ["X-internal-delay": "2000"], + body: nil + ) let start = NIODeadline.now() let response = try self.defaultClient.execute(request: req).wait() - XCTAssertGreaterThanOrEqual(.now() - start, .milliseconds(1_900 /* 1.9 seconds */ )) + XCTAssertGreaterThanOrEqual(.now() - start, .milliseconds(1_900)) XCTAssertEqual(response.status, .ok) } } diff --git a/Tests/AsyncHTTPClientTests/SOCKSEventsHandlerTests.swift b/Tests/AsyncHTTPClientTests/SOCKSEventsHandlerTests.swift index 066a631a5..1170aa444 100644 --- a/Tests/AsyncHTTPClientTests/SOCKSEventsHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/SOCKSEventsHandlerTests.swift @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOEmbedded import NIOSOCKS import XCTest +@testable import AsyncHTTPClient + class SOCKSEventsHandlerTests: XCTestCase { func testHandlerHappyPath() { let socksEventsHandler = SOCKSEventsHandler(deadline: .now() + .seconds(10)) diff --git a/Tests/AsyncHTTPClientTests/SOCKSTestUtils.swift b/Tests/AsyncHTTPClientTests/SOCKSTestUtils.swift index d888769b4..6dda7d928 100644 --- a/Tests/AsyncHTTPClientTests/SOCKSTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/SOCKSTestUtils.swift @@ -40,7 +40,13 @@ class MockSOCKSServer { self.channel.localAddress!.port! } - init(expectedURL: String, expectedResponse: String, misbehave: Bool = false, file: String = #filePath, line: UInt = #line) throws { + init( + expectedURL: String, + expectedResponse: String, + misbehave: Bool = false, + file: String = #filePath, + line: UInt = #line + ) throws { let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) let bootstrap: ServerBootstrap if misbehave { @@ -57,7 +63,12 @@ class MockSOCKSServer { return channel.pipeline.addHandlers([ handshakeHandler, SOCKSTestHandler(handshakeHandler: handshakeHandler), - TestHTTPServer(expectedURL: expectedURL, expectedResponse: expectedResponse, file: file, line: line), + TestHTTPServer( + expectedURL: expectedURL, + expectedResponse: expectedResponse, + file: file, + line: line + ), ]) } } @@ -86,17 +97,28 @@ class SOCKSTestHandler: ChannelInboundHandler, RemovableChannelHandler { let message = self.unwrapInboundIn(data) switch message { case .greeting: - context.writeAndFlush(.init( - ServerMessage.selectedAuthenticationMethod(.init(method: .noneRequired))), promise: nil) + context.writeAndFlush( + .init( + ServerMessage.selectedAuthenticationMethod(.init(method: .noneRequired)) + ), + promise: nil + ) case .authenticationData: context.fireErrorCaught(MockSOCKSError(description: "Received authentication data but didn't receive any.")) case .request(let request): - context.writeAndFlush(.init( - ServerMessage.response(.init(reply: .succeeded, boundAddress: request.addressType))), promise: nil) - context.channel.pipeline.addHandlers([ - ByteToMessageHandler(HTTPRequestDecoder()), - HTTPResponseEncoder(), - ], position: .after(self)).whenSuccess { + context.writeAndFlush( + .init( + ServerMessage.response(.init(reply: .succeeded, boundAddress: request.addressType)) + ), + promise: nil + ) + context.channel.pipeline.addHandlers( + [ + ByteToMessageHandler(HTTPRequestDecoder()), + HTTPResponseEncoder(), + ], + position: .after(self) + ).whenSuccess { context.channel.pipeline.removeHandler(self, promise: nil) context.channel.pipeline.removeHandler(self.handshakeHandler, promise: nil) } @@ -134,7 +156,12 @@ class TestHTTPServer: ChannelInboundHandler { break case .end: context.write(self.wrapOutboundOut(.head(.init(version: .http1_1, status: .ok))), promise: nil) - context.write(self.wrapOutboundOut(.body(.byteBuffer(context.channel.allocator.buffer(string: self.expectedResponse)))), promise: nil) + context.write( + self.wrapOutboundOut( + .body(.byteBuffer(context.channel.allocator.buffer(string: self.expectedResponse))) + ), + promise: nil + ) context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) } } diff --git a/Tests/AsyncHTTPClientTests/SSLContextCacheTests.swift b/Tests/AsyncHTTPClientTests/SSLContextCacheTests.swift index 438c643d7..c7588cc7d 100644 --- a/Tests/AsyncHTTPClientTests/SSLContextCacheTests.swift +++ b/Tests/AsyncHTTPClientTests/SSLContextCacheTests.swift @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOPosix import NIOSSL import XCTest +@testable import AsyncHTTPClient + final class SSLContextCacheTests: XCTestCase { func testRequestingSSLContextWorks() { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) @@ -27,9 +28,13 @@ final class SSLContextCacheTests: XCTestCase { XCTAssertNoThrow(try group.syncShutdownGracefully()) } - XCTAssertNoThrow(try cache.sslContext(tlsConfiguration: .makeClientConfiguration(), - eventLoop: eventLoop, - logger: HTTPClient.loggingDisabled).wait()) + XCTAssertNoThrow( + try cache.sslContext( + tlsConfiguration: .makeClientConfiguration(), + eventLoop: eventLoop, + logger: HTTPClient.loggingDisabled + ).wait() + ) } func testCacheWorks() { @@ -43,12 +48,20 @@ final class SSLContextCacheTests: XCTestCase { var firstContext: NIOSSLContext? var secondContext: NIOSSLContext? - XCTAssertNoThrow(firstContext = try cache.sslContext(tlsConfiguration: .makeClientConfiguration(), - eventLoop: eventLoop, - logger: HTTPClient.loggingDisabled).wait()) - XCTAssertNoThrow(secondContext = try cache.sslContext(tlsConfiguration: .makeClientConfiguration(), - eventLoop: eventLoop, - logger: HTTPClient.loggingDisabled).wait()) + XCTAssertNoThrow( + firstContext = try cache.sslContext( + tlsConfiguration: .makeClientConfiguration(), + eventLoop: eventLoop, + logger: HTTPClient.loggingDisabled + ).wait() + ) + XCTAssertNoThrow( + secondContext = try cache.sslContext( + tlsConfiguration: .makeClientConfiguration(), + eventLoop: eventLoop, + logger: HTTPClient.loggingDisabled + ).wait() + ) XCTAssertNotNil(firstContext) XCTAssertNotNil(secondContext) XCTAssert(firstContext === secondContext) @@ -65,16 +78,24 @@ final class SSLContextCacheTests: XCTestCase { var firstContext: NIOSSLContext? var secondContext: NIOSSLContext? - XCTAssertNoThrow(firstContext = try cache.sslContext(tlsConfiguration: .makeClientConfiguration(), - eventLoop: eventLoop, - logger: HTTPClient.loggingDisabled).wait()) + XCTAssertNoThrow( + firstContext = try cache.sslContext( + tlsConfiguration: .makeClientConfiguration(), + eventLoop: eventLoop, + logger: HTTPClient.loggingDisabled + ).wait() + ) // Second one has a _different_ TLSConfiguration. var testTLSConfig = TLSConfiguration.makeClientConfiguration() testTLSConfig.certificateVerification = .none - XCTAssertNoThrow(secondContext = try cache.sslContext(tlsConfiguration: testTLSConfig, - eventLoop: eventLoop, - logger: HTTPClient.loggingDisabled).wait()) + XCTAssertNoThrow( + secondContext = try cache.sslContext( + tlsConfiguration: testTLSConfig, + eventLoop: eventLoop, + logger: HTTPClient.loggingDisabled + ).wait() + ) XCTAssertNotNil(firstContext) XCTAssertNotNil(secondContext) XCTAssert(firstContext !== secondContext) diff --git a/Tests/AsyncHTTPClientTests/StressGetHttpsTests.swift b/Tests/AsyncHTTPClientTests/StressGetHttpsTests.swift index 4c5cd1816..587e6c64c 100644 --- a/Tests/AsyncHTTPClientTests/StressGetHttpsTests.swift +++ b/Tests/AsyncHTTPClientTests/StressGetHttpsTests.swift @@ -14,9 +14,6 @@ import AsyncHTTPClient import Atomics -#if canImport(Network) -import Network -#endif import Logging import NIOConcurrencyHelpers import NIOCore @@ -29,11 +26,17 @@ import NIOTestUtils import NIOTransportServices import XCTest +#if canImport(Network) +import Network +#endif + final class StressGetHttpsTests: XCTestCaseHTTPClientTestsBaseClass { func testStressGetHttps() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) @@ -43,7 +46,11 @@ final class StressGetHttpsTests: XCTestCaseHTTPClientTestsBaseClass { let requestCount = 200 var futureResults = [EventLoopFuture]() for _ in 1...requestCount { - let req = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, headers: ["X-internal-delay": "100"]) + let req = try HTTPClient.Request( + url: "https://localhost:\(localHTTPBin.port)/get", + method: .GET, + headers: ["X-internal-delay": "100"] + ) futureResults.append(localClient.execute(request: req)) } XCTAssertNoThrow(try EventLoopFuture.andAllSucceed(futureResults, on: eventLoop).wait()) diff --git a/Tests/AsyncHTTPClientTests/TLSEventsHandlerTests.swift b/Tests/AsyncHTTPClientTests/TLSEventsHandlerTests.swift index c119c7e50..96cdf68f6 100644 --- a/Tests/AsyncHTTPClientTests/TLSEventsHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/TLSEventsHandlerTests.swift @@ -12,13 +12,14 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOEmbedded import NIOSSL import NIOTLS import XCTest +@testable import AsyncHTTPClient + class TLSEventsHandlerTests: XCTestCase { func testHandlerHappyPath() { let tlsEventsHandler = TLSEventsHandler(deadline: nil) diff --git a/Tests/AsyncHTTPClientTests/Transaction+StateMachineTests.swift b/Tests/AsyncHTTPClientTests/Transaction+StateMachineTests.swift index a8d3d5a5e..a631e9a93 100644 --- a/Tests/AsyncHTTPClientTests/Transaction+StateMachineTests.swift +++ b/Tests/AsyncHTTPClientTests/Transaction+StateMachineTests.swift @@ -12,12 +12,13 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import NIOCore import NIOEmbedded import NIOHTTP1 import XCTest +@testable import AsyncHTTPClient + struct NoOpAsyncSequenceProducerDelegate: NIOAsyncSequenceProducerDelegate { func produceMore() {} func didTerminate() {} @@ -37,7 +38,10 @@ final class Transaction_StateMachineTests: XCTestCase { state.requestWasQueued(queuer) let failAction = state.fail(HTTPClientError.cancelled) - guard case .failResponseHead(_, let error, let scheduler, let rexecutor, let bodyStreamContinuation) = failAction else { + guard + case .failResponseHead(_, let error, let scheduler, let rexecutor, let bodyStreamContinuation) = + failAction + else { return XCTFail("Unexpected fail action: \(failAction)") } XCTAssertEqual(error as? HTTPClientError, .cancelled) @@ -88,7 +92,10 @@ final class Transaction_StateMachineTests: XCTestCase { XCTAssertIdentical(scheduler as? MockTaskQueuer, queuer) let failAction = state.fail(MyError()) - guard case .failResponseHead(let continuation, let error, nil, nil, bodyStreamContinuation: nil) = failAction else { + guard + case .failResponseHead(let continuation, let error, nil, nil, bodyStreamContinuation: nil) = + failAction + else { return XCTFail("Unexpected fail action: \(failAction)") } XCTAssertIdentical(scheduler as? MockTaskQueuer, queuer) @@ -118,7 +125,10 @@ final class Transaction_StateMachineTests: XCTestCase { XCTAssertIdentical(scheduler as? MockTaskQueuer, queuer) let failAction = state.fail(MyError()) - guard case .failResponseHead(let continuation, let error, nil, nil, bodyStreamContinuation: nil) = failAction else { + guard + case .failResponseHead(let continuation, let error, nil, nil, bodyStreamContinuation: nil) = + failAction + else { return XCTFail("Unexpected fail action: \(failAction)") } XCTAssertIdentical(scheduler as? MockTaskQueuer, queuer) @@ -203,7 +213,10 @@ final class Transaction_StateMachineTests: XCTestCase { XCTAssertEqual(state.willExecuteRequest(executor), .none) state.requestWasQueued(queuer) let head = HTTPResponseHead(version: .http1_1, status: .ok) - let receiveResponseHeadAction = state.receiveResponseHead(head, delegate: NoOpAsyncSequenceProducerDelegate()) + let receiveResponseHeadAction = state.receiveResponseHead( + head, + delegate: NoOpAsyncSequenceProducerDelegate() + ) guard case .succeedResponseHead(_, let continuation) = receiveResponseHeadAction else { return XCTFail("Unexpected action: \(receiveResponseHeadAction)") } @@ -258,7 +271,7 @@ extension Transaction.StateMachine.NextWriteAction: Equatable { public static func == (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { case (.writeAndWait(let lhsEx), .writeAndWait(let rhsEx)), - (.writeAndContinue(let lhsEx), .writeAndContinue(let rhsEx)): + (.writeAndContinue(let lhsEx), .writeAndContinue(let rhsEx)): if let lhsMock = lhsEx as? MockRequestExecutor, let rhsMock = rhsEx as? MockRequestExecutor { return lhsMock === rhsMock } diff --git a/Tests/AsyncHTTPClientTests/TransactionTests.swift b/Tests/AsyncHTTPClientTests/TransactionTests.swift index 40f71d010..ff3a51d27 100644 --- a/Tests/AsyncHTTPClientTests/TransactionTests.swift +++ b/Tests/AsyncHTTPClientTests/TransactionTests.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -@testable import AsyncHTTPClient import Logging import NIOConcurrencyHelpers import NIOCore @@ -21,6 +20,8 @@ import NIOHTTP1 import NIOPosix import XCTest +@testable import AsyncHTTPClient + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) typealias PreparedRequest = HTTPClientRequest.Prepared @@ -91,11 +92,18 @@ final class TransactionTests: XCTestCase { transaction.deadlineExceeded() struct Executor: HTTPRequestExecutor { - func writeRequestBodyPart(_: NIOCore.IOData, request: AsyncHTTPClient.HTTPExecutableRequest, promise: NIOCore.EventLoopPromise?) { + func writeRequestBodyPart( + _: NIOCore.IOData, + request: AsyncHTTPClient.HTTPExecutableRequest, + promise: NIOCore.EventLoopPromise? + ) { XCTFail() } - func finishRequestBodyStream(_ task: AsyncHTTPClient.HTTPExecutableRequest, promise: NIOCore.EventLoopPromise?) { + func finishRequestBodyStream( + _ task: AsyncHTTPClient.HTTPExecutableRequest, + promise: NIOCore.EventLoopPromise? + ) { XCTFail() } @@ -253,15 +261,17 @@ final class TransactionTests: XCTestCase { XCTAssertFalse(streamWriter.hasDemand, "Did not expect to have demand yet") transaction.resumeRequestBodyStream() - await streamWriter.demand() // wait's for the stream writer to signal demand + await streamWriter.demand() // wait's for the stream writer to signal demand transaction.pauseRequestBodyStream() let part = ByteBuffer(integer: i) streamWriter.write(part) - XCTAssertNoThrow(try executor.receiveRequestBody { - XCTAssertEqual($0, part) - }) + XCTAssertNoThrow( + try executor.receiveRequestBody { + XCTAssertEqual($0, part) + } + ) } transaction.resumeRequestBodyStream() @@ -306,11 +316,13 @@ final class TransactionTests: XCTestCase { let connectionCreator = TestConnectionCreator() let delegate = TestHTTP2ConnectionDelegate() var maybeHTTP2Connection: HTTP2Connection? - XCTAssertNoThrow(maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( - to: httpBin.port, - delegate: delegate, - on: eventLoop - )) + XCTAssertNoThrow( + maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( + to: httpBin.port, + delegate: delegate, + on: eventLoop + ) + ) guard let http2Connection = maybeHTTP2Connection else { return XCTFail("Expected to have an HTTP2 connection here.") } @@ -370,9 +382,11 @@ final class TransactionTests: XCTestCase { let executor = MockRequestExecutor(eventLoop: embeddedEventLoop) executor.runRequest(transaction) executor.resumeRequestBodyStream() - XCTAssertNoThrow(try executor.receiveRequestBody { - XCTAssertEqual($0.getString(at: 0, length: $0.readableBytes), "Hello world!") - }) + XCTAssertNoThrow( + try executor.receiveRequestBody { + XCTAssertEqual($0.getString(at: 0, length: $0.readableBytes), "Hello world!") + } + ) XCTAssertNoThrow(try executor.receiveEndOfStream()) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["foo": "bar"]) @@ -413,9 +427,11 @@ final class TransactionTests: XCTestCase { await writer.demand() writer.write(.init(string: "Hello world!")) - XCTAssertNoThrow(try executor.receiveRequestBody { - XCTAssertEqual($0.getString(at: 0, length: $0.readableBytes), "Hello world!") - }) + XCTAssertNoThrow( + try executor.receiveRequestBody { + XCTAssertEqual($0.getString(at: 0, length: $0.readableBytes), "Hello world!") + } + ) XCTAssertFalse(executor.isCancelled) struct WriteError: Error, Equatable {} @@ -502,11 +518,13 @@ final class TransactionTests: XCTestCase { let connectionCreator = TestConnectionCreator() let delegate = TestHTTP2ConnectionDelegate() var maybeHTTP2Connection: HTTP2Connection? - XCTAssertNoThrow(maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( - to: httpBin.port, - delegate: delegate, - on: eventLoop - )) + XCTAssertNoThrow( + maybeHTTP2Connection = try connectionCreator.createHTTP2Connection( + to: httpBin.port, + delegate: delegate, + on: eventLoop + ) + ) guard let http2Connection = maybeHTTP2Connection else { return XCTFail("Expected to have an HTTP2 connection here.") } @@ -638,7 +656,8 @@ extension Transaction { ) async -> (Transaction, _Concurrency.Task) { let transactionPromise = Promise() let task = Task { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + try await withCheckedThrowingContinuation { + (continuation: CheckedContinuation) in let transaction = Transaction( request: request, requestOptions: requestOptions, diff --git a/Tests/AsyncHTTPClientTests/XCTest+AsyncAwait.swift b/Tests/AsyncHTTPClientTests/XCTest+AsyncAwait.swift index e1d2e4592..6cdcf4f8a 100644 --- a/Tests/AsyncHTTPClientTests/XCTest+AsyncAwait.swift +++ b/Tests/AsyncHTTPClientTests/XCTest+AsyncAwait.swift @@ -11,21 +11,21 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -/* - * Copyright 2021, gRPC Authors All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// +// Copyright 2021, gRPC Authors All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// import XCTest @@ -53,7 +53,7 @@ extension XCTestCase { try await operation() } catch { XCTFail("Error thrown while executing \(function): \(error)", file: file, line: line) - Thread.callStackSymbols.forEach { print($0) } + for symbol in Thread.callStackSymbols { print(symbol) } } expectation.fulfill() } diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 2d1e57def..000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -ARG swift_version=5.7 -ARG ubuntu_version=jammy -ARG base_image=swift:$swift_version-$ubuntu_version -FROM $base_image -# needed to do again after FROM due to docker limitation -ARG swift_version -ARG ubuntu_version - -# set as UTF-8 -RUN apt-get update && apt-get install -y locales locales-all -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US.UTF-8 - -# dependencies -RUN apt-get update && apt-get install -y wget -RUN apt-get update && apt-get install -y lsof dnsutils netcat-openbsd net-tools libz-dev curl jq # used by integration tests - -# ruby and jazzy for docs generation -RUN apt-get update && apt-get install -y ruby ruby-dev libsqlite3-dev build-essential -# jazzy no longer works on xenial as ruby is too old. -RUN if [ "${ubuntu_version}" = "focal" ] ; then echo "gem: --no-document" > ~/.gemrc; fi -RUN if [ "${ubuntu_version}" = "focal" ] ; then gem install jazzy; fi - -# tools -RUN mkdir -p $HOME/.tools -RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile - -# swiftformat (until part of the toolchain) - -ARG swiftformat_version=0.48.8 -RUN git clone --branch $swiftformat_version --depth 1 https://github.com/nicklockwood/SwiftFormat $HOME/.tools/swift-format -RUN cd $HOME/.tools/swift-format && swift build -c release -RUN ln -s $HOME/.tools/swift-format/.build/release/swiftformat $HOME/.tools/swiftformat diff --git a/docker/docker-compose.2204.510.yaml b/docker/docker-compose.2204.510.yaml deleted file mode 100644 index 8dbf21183..000000000 --- a/docker/docker-compose.2204.510.yaml +++ /dev/null @@ -1,22 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: async-http-client:22.04-5.10 - build: - args: - ubuntu_version: "jammy" - swift_version: "5.10" - - documentation-check: - image: async-http-client:22.04-5.10 - - test: - image: async-http-client:22.04-5.10 - environment: - - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error - #- SANITIZER_ARG=--sanitize=thread - - shell: - image: async-http-client:22.04-5.10 diff --git a/docker/docker-compose.2204.58.yaml b/docker/docker-compose.2204.58.yaml deleted file mode 100644 index 89b410ae2..000000000 --- a/docker/docker-compose.2204.58.yaml +++ /dev/null @@ -1,22 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: async-http-client:22.04-5.8 - build: - args: - ubuntu_version: "jammy" - swift_version: "5.8" - - documentation-check: - image: async-http-client:22.04-5.8 - - test: - image: async-http-client:22.04-5.8 - environment: - - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error - #- SANITIZER_ARG=--sanitize=thread - - shell: - image: async-http-client:22.04-5.8 diff --git a/docker/docker-compose.2204.59.yaml b/docker/docker-compose.2204.59.yaml deleted file mode 100644 index b125fff39..000000000 --- a/docker/docker-compose.2204.59.yaml +++ /dev/null @@ -1,22 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: async-http-client:22.04-5.9 - build: - args: - ubuntu_version: "jammy" - swift_version: "5.9" - - documentation-check: - image: async-http-client:22.04-5.9 - - test: - image: async-http-client:22.04-5.9 - environment: - - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error - #- SANITIZER_ARG=--sanitize=thread - - shell: - image: async-http-client:22.04-5.9 diff --git a/docker/docker-compose.2204.main.yaml b/docker/docker-compose.2204.main.yaml deleted file mode 100644 index 8dfa4c921..000000000 --- a/docker/docker-compose.2204.main.yaml +++ /dev/null @@ -1,21 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: async-http-client:22.04-main - build: - args: - base_image: "swiftlang/swift:nightly-main-jammy" - - documentation-check: - image: async-http-client:22.04-main - - test: - image: async-http-client:22.04-main - environment: - - IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error - #- SANITIZER_ARG=--sanitize=thread - - shell: - image: async-http-client:22.04-main diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml deleted file mode 100644 index 9ac4a6eea..000000000 --- a/docker/docker-compose.yaml +++ /dev/null @@ -1,45 +0,0 @@ -# this file is not designed to be run directly -# instead, use the docker-compose.. files -# eg docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.1804.50.yaml run test -version: "3" - -services: - - runtime-setup: - image: async-http-client:default - build: - context: . - dockerfile: Dockerfile - - common: &common - image: async-http-client:default - depends_on: [runtime-setup] - volumes: - - ~/.ssh:/root/.ssh - - ..:/code:z - working_dir: /code - cap_drop: - - CAP_NET_RAW - - CAP_NET_BIND_SERVICE - - soundness: - <<: *common - command: /bin/bash -xcl "./scripts/soundness.sh" - - documentation-check: - <<: *common - command: /bin/bash -xcl "./scripts/check-docs.sh" - - test: - <<: *common - command: /bin/bash -xcl "swift test --parallel -Xswiftc -warnings-as-errors --enable-test-discovery $${SANITIZER_ARG-} $${IMPORT_CHECK_ARG-}" - - # util - - shell: - <<: *common - entrypoint: /bin/bash - - docs: - <<: *common - command: /bin/bash -cl "./scripts/generate_docs.sh" diff --git a/scripts/check-docs.sh b/scripts/check-docs.sh deleted file mode 100755 index 61a13a56f..000000000 --- a/scripts/check-docs.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## 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 -## -##===----------------------------------------------------------------------===## - -set -eu - -raw_targets=$(sed -E -n -e 's/^.* - documentation_targets: \[(.*)\].*$/\1/p' .spi.yml) -targets=(${raw_targets//,/ }) - -for target in "${targets[@]}"; do - swift package plugin generate-documentation --target "$target" --warnings-as-errors --analyze --level detailed -done diff --git a/scripts/check_no_api_breakages.sh b/scripts/check_no_api_breakages.sh deleted file mode 100755 index 2d7028617..000000000 --- a/scripts/check_no_api_breakages.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the AsyncHTTPClient open source project -## -## Copyright (c) 2018-2022 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 -## -##===----------------------------------------------------------------------===## - -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftNIO open source project -## -## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftNIO project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -set -eu - -function usage() { - echo >&2 "Usage: $0 REPO-GITHUB-URL NEW-VERSION OLD-VERSIONS..." - echo >&2 - echo >&2 "This script requires a Swift 5.6+ toolchain." - echo >&2 - echo >&2 "Examples:" - echo >&2 - echo >&2 "Check between main and tag 1.9.0 of async-http-client:" - echo >&2 " $0 https://github.com/swift-server/async-http-client main 1.9.0" - echo >&2 - echo >&2 "Check between HEAD and commit 64cf63d7 using the provided toolchain:" - echo >&2 " xcrun --toolchain org.swift.5120190702a $0 ../some-local-repo HEAD 64cf63d7" -} - -if [[ $# -lt 3 ]]; then - usage - exit 1 -fi - -tmpdir=$(mktemp -d /tmp/.check-api_XXXXXX) -repo_url=$1 -new_tag=$2 -shift 2 - -repodir="$tmpdir/repo" -git clone "$repo_url" "$repodir" -git -C "$repodir" fetch -q origin '+refs/pull/*:refs/remotes/origin/pr/*' -cd "$repodir" -git checkout -q "$new_tag" - -for old_tag in "$@"; do - echo "Checking public API breakages from $old_tag to $new_tag" - - swift package diagnose-api-breaking-changes "$old_tag" -done - -echo done diff --git a/scripts/generate_docs.sh b/scripts/generate_docs.sh deleted file mode 100755 index 82da814d3..000000000 --- a/scripts/generate_docs.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## 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 -## -##===----------------------------------------------------------------------===## - -set -e - -my_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -root_path="$my_path/.." -version=$(git describe --abbrev=0 --tags || echo "main") -modules=(AsyncHTTPClient) - -if [[ "$(uname -s)" == "Linux" ]]; then - # build code if required - if [[ ! -d "$root_path/.build/x86_64-unknown-linux" ]]; then - swift build - fi - # setup source-kitten if required - mkdir -p "$root_path/.build/sourcekitten" - source_kitten_source_path="$root_path/.build/sourcekitten/source" - if [[ ! -d "$source_kitten_source_path" ]]; then - git clone https://github.com/jpsim/SourceKitten.git "$source_kitten_source_path" - fi - source_kitten_path="$source_kitten_source_path/.build/debug" - if [[ ! -d "$source_kitten_path" ]]; then - rm -rf "$source_kitten_source_path/.swift-version" - cd "$source_kitten_source_path" && swift build && cd "$root_path" - fi - # generate - for module in "${modules[@]}"; do - if [[ ! -f "$root_path/.build/sourcekitten/$module.json" ]]; then - "$source_kitten_path/sourcekitten" doc --spm --module-name $module > "$root_path/.build/sourcekitten/$module.json" - fi - done -fi - -[[ -d docs/$version ]] || mkdir -p docs/$version -[[ -d async-http-client.xcodeproj ]] || swift package generate-xcodeproj - -# run jazzy -if ! command -v jazzy > /dev/null; then - gem install jazzy --no-ri --no-rdoc -fi - -jazzy_dir="$root_path/.build/jazzy" -rm -rf "$jazzy_dir" -mkdir -p "$jazzy_dir" - -module_switcher="$jazzy_dir/README.md" -jazzy_args=(--clean - --author 'AsyncHTTPClient team' - --readme "$module_switcher" - --author_url https://github.com/swift-server/async-http-client - --github_url https://github.com/swift-server/async-http-client - --github-file-prefix "https://github.com/swift-server/async-http-client/tree/$version" - --theme fullwidth - --xcodebuild-arguments -scheme,async-http-client-Package) -cat > "$module_switcher" <<"EOF" -# AsyncHTTPClient Docs - -AsyncHTTPClient is a Swift HTTP Client package. - -To get started with AsyncHTTPClient, [`import AsyncHTTPClient`](../AsyncHTTPClient/index.html). The -most important type is [`HTTPClient`](https://swift-server.github.io/async-http-client/docs/current/AsyncHTTPClient/Classes/HTTPClient.html) -which you can use to emit log messages. - -EOF - -tmp=`mktemp -d` -for module in "${modules[@]}"; do - args=("${jazzy_args[@]}" --output "$jazzy_dir/docs/$version/$module" --docset-path "$jazzy_dir/docset/$version/$module" - --module "$module" --module-version $version - --root-url "https://swift-server.github.io/async-http-client/docs/$version/$module/") - if [[ -f "$root_path/.build/sourcekitten/$module.json" ]]; then - args+=(--sourcekitten-sourcefile "$root_path/.build/sourcekitten/$module.json") - fi - jazzy "${args[@]}" -done - -# push to github pages -if [[ $PUSH == true ]]; then - BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) - GIT_AUTHOR=$(git --no-pager show -s --format='%an <%ae>' HEAD) - git fetch origin +gh-pages:gh-pages - git checkout gh-pages - rm -rf "docs/$version" - rm -rf "docs/current" - cp -r "$jazzy_dir/docs/$version" docs/ - cp -r "docs/$version" docs/current - git add --all docs - echo '' > index.html - git add index.html - touch .nojekyll - git add .nojekyll - changes=$(git diff-index --name-only HEAD) - if [[ -n "$changes" ]]; then - echo -e "changes detected\n$changes" - git commit --author="$GIT_AUTHOR" -m "publish $version docs" - git push origin gh-pages - else - echo "no changes detected" - fi - git checkout -f $BRANCH_NAME -fi diff --git a/scripts/soundness.sh b/scripts/soundness.sh deleted file mode 100755 index 216eab206..000000000 --- a/scripts/soundness.sh +++ /dev/null @@ -1,152 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the AsyncHTTPClient open source project -## -## Copyright (c) 2018-2022 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 -## -##===----------------------------------------------------------------------===## - -set -eu -here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -function replace_acceptable_years() { - # this needs to replace all acceptable forms with 'YEARS' - sed -e 's/20[12][0-9]-20[12][0-9]/YEARS/' -e 's/20[12][0-9]/YEARS/' -} - -printf "=> Checking for unacceptable language... " -# This greps for unacceptable terminology. The square bracket[s] are so that -# "git grep" doesn't find the lines that greps :). -unacceptable_terms=( - -e blacklis[t] - -e whitelis[t] - -e slav[e] - -e sanit[y] -) -if git grep --color=never -i "${unacceptable_terms[@]}" > /dev/null; then - printf "\033[0;31mUnacceptable language found.\033[0m\n" - git grep -i "${unacceptable_terms[@]}" - exit 1 -fi -printf "\033[0;32mokay.\033[0m\n" - -printf "=> Checking format... " -FIRST_OUT="$(git status --porcelain)" -swiftformat . > /dev/null 2>&1 -SECOND_OUT="$(git status --porcelain)" -if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then - printf "\033[0;31mformatting issues!\033[0m\n" - git --no-pager diff - exit 1 -else - printf "\033[0;32mokay.\033[0m\n" -fi - -printf "=> Checking license headers\n" -tmp=$(mktemp /tmp/.async-http-client-soundness_XXXXXX) - -for language in swift-or-c bash dtrace; do - printf " * $language... " - declare -a matching_files - declare -a exceptions - expections=( ) - matching_files=( -name '*' ) - case "$language" in - swift-or-c) - exceptions=( -name c_nio_http_parser.c -o -name c_nio_http_parser.h -o -name cpp_magic.h -o -name Package.swift -o -name CNIOSHA1.h -o -name c_nio_sha1.c -o -name ifaddrs-android.c -o -name ifaddrs-android.h -o -name 'Package@swift*.swift' ) - matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) - cat > "$tmp" <<"EOF" -//===----------------------------------------------------------------------===// -// -// This source file is part of the AsyncHTTPClient open source project -// -// Copyright (c) YEARS 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 -// -//===----------------------------------------------------------------------===// -EOF - ;; - bash) - matching_files=( -name '*.sh' ) - cat > "$tmp" <<"EOF" -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the AsyncHTTPClient open source project -## -## Copyright (c) YEARS 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 -## -##===----------------------------------------------------------------------===## -EOF - ;; - dtrace) - matching_files=( -name '*.d' ) - cat > "$tmp" <<"EOF" -#!/usr/sbin/dtrace -q -s -/*===----------------------------------------------------------------------===* - * - * This source file is part of the AsyncHTTPClient open source project - * - * Copyright (c) YEARS 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 - * - *===----------------------------------------------------------------------===*/ -EOF - ;; - *) - echo >&2 "ERROR: unknown language '$language'" - ;; - esac - - expected_lines=$(cat "$tmp" | wc -l) - expected_sha=$(cat "$tmp" | shasum) - - ( - cd "$here/.." - find . \ - \( \! -path './.build/*' -a \ - \( "${matching_files[@]}" \) -a \ - \( \! \( "${exceptions[@]}" \) \) \) | while read line; do - if [[ "$(cat "$line" | replace_acceptable_years | head -n $expected_lines | shasum)" != "$expected_sha" ]]; then - printf "\033[0;31mmissing headers in file '$line'!\033[0m\n" - diff -u <(cat "$line" | replace_acceptable_years | head -n $expected_lines) "$tmp" - exit 1 - fi - done - printf "\033[0;32mokay.\033[0m\n" - ) -done - -rm "$tmp" - -# This checks for the umbrella NIO module. -printf "=> Checking for imports of umbrella NIO module... " -if git grep --color=never -i "^[ \t]*import \+NIO[ \t]*$" > /dev/null; then - printf "\033[0;31mUmbrella imports found.\033[0m\n" - git grep -i "^[ \t]*import \+NIO[ \t]*$" - exit 1 -fi -printf "\033[0;32mokay.\033[0m\n" From d3c4d56a6c7f13529143100d0568dd4ba3884d48 Mon Sep 17 00:00:00 2001 From: Rick Newton-Rogers Date: Mon, 28 Oct 2024 16:26:44 +0000 Subject: [PATCH 2/3] Disable warnings as errors on 6.0 Motivation: We currently have a range of thread-safety warnings on Swift 6.0 (which will be errors in Swift 6 Language mode). Resolving these is out of scope for CI work. Modifications: Disabled warnings-as-errors on Swift 6 CI pipelines Result: Resolve Swift 6 CI failures. --- .github/workflows/main.yml | 3 +-- .github/workflows/pull_request.yml | 2 +- .licenseignore | 2 ++ .swift-format | 6 ++++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1374edfb7..6e5453369 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,9 +11,8 @@ jobs: name: Unit tests uses: apple/swift-nio/.github/workflows/unit_tests.yml@main with: - linux_5_8_enabled: false linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" - linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 6873117ab..9d7185505 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,7 +16,7 @@ jobs: with: linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" - linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" diff --git a/.licenseignore b/.licenseignore index 922c4f760..edceaab62 100644 --- a/.licenseignore +++ b/.licenseignore @@ -33,3 +33,5 @@ Dockerfile .dockerignore Snippets/* dev/git.commit.template +.unacceptablelanguageignore +Tests/AsyncHTTPClientTests/Resources/*.pem diff --git a/.swift-format b/.swift-format index 7fa06fb30..7e8ae7391 100644 --- a/.swift-format +++ b/.swift-format @@ -18,6 +18,12 @@ "maximumBlankLines" : 1, "respectsExistingLineBreaks" : true, "prioritizeKeepingFunctionOutputTogether" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow", + "XCTAssertThrowsError" + ] + }, "rules" : { "AllPublicDeclarationsHaveDocumentation" : false, "AlwaysUseLiteralForEmptyCollectionInit" : false, From 82412552508775ee8043890307bdce403952af94 Mon Sep 17 00:00:00 2001 From: Rick Newton-Rogers Date: Tue, 29 Oct 2024 14:38:42 +0000 Subject: [PATCH 3/3] NOTICE.txt use git hash --- .unacceptablelanguageignore | 1 - NOTICE.txt | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 .unacceptablelanguageignore diff --git a/.unacceptablelanguageignore b/.unacceptablelanguageignore deleted file mode 100644 index d89f79f62..000000000 --- a/.unacceptablelanguageignore +++ /dev/null @@ -1 +0,0 @@ -NOTICE.txt diff --git a/NOTICE.txt b/NOTICE.txt index 095a11740..86a969171 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -50,13 +50,13 @@ This product contains a derivation of the Tony Stone's 'process_test_files.rb'. * https://www.apache.org/licenses/LICENSE-2.0 * HOMEPAGE: * https://github.com/tonystone/build-tools/commit/6c417b7569df24597a48a9aa7b505b636e8f73a1 - * https://github.com/tonystone/build-tools/blob/master/source/xctest_tool.rb + * https://github.com/tonystone/build-tools/blob/cf3440f43bde2053430285b4ed0709c865892eb5/source/xctest_tool.rb --- This product contains a derivation of Fabian Fett's 'Base64.swift'. * LICENSE (Apache License 2.0): - * https://github.com/fabianfett/swift-base64-kit/blob/master/LICENSE + * https://github.com/swift-extras/swift-extras-base64/blob/b8af49699d59ad065b801715a5009619100245ca/LICENSE * HOMEPAGE: * https://github.com/fabianfett/swift-base64-kit