mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-05-03 07:32:29 +00:00
Adopt GitHub actions (#780)
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. * Disabled warnings-as-errors on Swift 6.0 CI pipelines for now. ### Result: Feature parity with old CI.
This commit is contained in:
committed by
GitHub
parent
acaca2d50d
commit
c621142327
@@ -0,0 +1,18 @@
|
||||
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_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: "--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"
|
||||
@@ -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: "--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.
|
||||
@@ -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
|
||||
@@ -0,0 +1,37 @@
|
||||
.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
|
||||
.unacceptablelanguageignore
|
||||
Tests/AsyncHTTPClientTests/Resources/*.pem
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"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,
|
||||
"noAssignmentInExpressions" : {
|
||||
"allowedFunctions" : [
|
||||
"XCTAssertNoThrow",
|
||||
"XCTAssertThrowsError"
|
||||
]
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
+3
-3
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-3
@@ -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(
|
||||
|
||||
@@ -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<HTTPClientResponse, Swift.Error>) -> Void in
|
||||
let transaction = Transaction(
|
||||
request: request,
|
||||
requestOptions: .fromClientConfiguration(self.configuration),
|
||||
logger: logger,
|
||||
connectionDeadline: .now() + (self.configuration.timeout.connectionCreationTimeout),
|
||||
preferredEventLoop: eventLoop,
|
||||
responseContinuation: continuation
|
||||
)
|
||||
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<HTTPClientResponse, Swift.Error>) -> 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)
|
||||
cancelHandler.registerTransaction(transaction)
|
||||
|
||||
self.poolManager.executeRequest(transaction)
|
||||
self.poolManager.executeRequest(transaction)
|
||||
}
|
||||
},
|
||||
onCancel: {
|
||||
cancelHandler.cancel(reason: .taskCanceled)
|
||||
}
|
||||
}, onCancel: {
|
||||
cancelHandler.cancel(reason: .taskCanceled)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<ByteBuffer> = 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)
|
||||
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
|
||||
}
|
||||
if buffer.readableBytes > 0 {
|
||||
return buffer
|
||||
}
|
||||
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)
|
||||
})
|
||||
} 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 allocator.buffer(bytes: chunk)
|
||||
}
|
||||
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 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)
|
||||
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
|
||||
}
|
||||
if buffer.readableBytes > 0 {
|
||||
return buffer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
)
|
||||
return body
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: AsyncSequence>(_ body: Body, maxBytes: Int) async throws -> ByteBuffer where Body.Element == ByteBuffer {
|
||||
func collect<Body: AsyncSequence>(_ 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 {
|
||||
|
||||
@@ -82,9 +82,20 @@ extension Transaction {
|
||||
enum FailAction {
|
||||
case none
|
||||
/// fail response before head received. scheduler and executor are exclusive here.
|
||||
case failResponseHead(CheckedContinuation<HTTPClientResponse, Error>, Error, HTTPRequestScheduler?, HTTPRequestExecutor?, bodyStreamContinuation: CheckedContinuation<Void, Error>?)
|
||||
case failResponseHead(
|
||||
CheckedContinuation<HTTPClientResponse, Error>,
|
||||
Error,
|
||||
HTTPRequestScheduler?,
|
||||
HTTPRequestExecutor?,
|
||||
bodyStreamContinuation: CheckedContinuation<Void, Error>?
|
||||
)
|
||||
/// fail response after response head received. fail the response stream (aka call to `next()`)
|
||||
case failResponseStream(TransactionBody.Source, Error, HTTPRequestExecutor, bodyStreamContinuation: CheckedContinuation<Void, Error>?)
|
||||
case failResponseStream(
|
||||
TransactionBody.Source,
|
||||
Error,
|
||||
HTTPRequestExecutor,
|
||||
bodyStreamContinuation: CheckedContinuation<Void, Error>?
|
||||
)
|
||||
|
||||
case failRequestStreamContinuation(CheckedContinuation<Void, Error>, 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<Void, Error>) {
|
||||
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<ByteBuffer>) -> 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<ByteBuffer>?) -> 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)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,142 +19,156 @@
|
||||
|
||||
extension String {
|
||||
|
||||
/// Base64 encode a collection of UInt8 to a string, without the use of Foundation.
|
||||
@inlinable
|
||||
init<Buffer: Collection>(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<Buffer: Collection>(base64Encoding bytes: Buffer)
|
||||
where Buffer.Element == UInt8 {
|
||||
self = Base64.encode(bytes: bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// swift-format-ignore: DontRepeatTypeInStaticProperties
|
||||
@usableFromInline
|
||||
internal struct Base64 {
|
||||
|
||||
@inlinable
|
||||
static func encode<Buffer: Collection>(bytes: Buffer)
|
||||
-> String where Buffer.Element == UInt8
|
||||
{
|
||||
guard !bytes.isEmpty else {
|
||||
return ""
|
||||
@inlinable
|
||||
static func encode<Buffer: Collection>(
|
||||
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
|
||||
}
|
||||
}
|
||||
// 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()
|
||||
// MARK: Internal
|
||||
|
||||
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
|
||||
// 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)]
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@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)]
|
||||
}
|
||||
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], 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)]
|
||||
}
|
||||
var index = (secondByte & 0b00001111) << 2
|
||||
if let thirdByte = thirdByte {
|
||||
index += (thirdByte & 0b11000000) >> 6
|
||||
}
|
||||
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<UInt8>) throws -> Int) rethrows {
|
||||
// The buffer will store zero terminated C string
|
||||
let buffer = UnsafeMutableBufferPointer<UInt8>.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<UInt8>) throws -> Int
|
||||
) rethrows {
|
||||
// The buffer will store zero terminated C string
|
||||
let buffer = UnsafeMutableBufferPointer<UInt8>.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<UInt8>) 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<UInt8>) 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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) "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-7
@@ -42,7 +42,7 @@ final class HTTP1ProxyConnectHandler: ChannelDuplexHandler, RemovableChannelHand
|
||||
|
||||
private var proxyEstablishedPromise: EventLoopPromise<Void>?
|
||||
var proxyEstablishedFuture: EventLoopFuture<Void>? {
|
||||
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
|
||||
|
||||
@@ -31,7 +31,7 @@ final class SOCKSEventsHandler: ChannelInboundHandler, RemovableChannelHandler {
|
||||
|
||||
private var socksEstablishedPromise: EventLoopPromise<Void>?
|
||||
var socksEstablishedFuture: EventLoopFuture<Void>? {
|
||||
return self.socksEstablishedPromise?.futureResult
|
||||
self.socksEstablishedPromise?.futureResult
|
||||
}
|
||||
|
||||
private let deadline: NIODeadline
|
||||
|
||||
@@ -31,7 +31,7 @@ final class TLSEventsHandler: ChannelInboundHandler, RemovableChannelHandler {
|
||||
|
||||
private var tlsEstablishedPromise: EventLoopPromise<String?>?
|
||||
var tlsEstablishedFuture: EventLoopFuture<String?>? {
|
||||
return self.tlsEstablishedPromise?.futureResult
|
||||
self.tlsEstablishedPromise?.futureResult
|
||||
}
|
||||
|
||||
private let deadline: NIODeadline?
|
||||
|
||||
@@ -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<Void>?) {
|
||||
private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest, promise: EventLoopPromise<Void>?)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<Void>?) {
|
||||
return self.channel.close(mode: .all, promise: promise)
|
||||
self.channel.close(mode: .all, promise: promise)
|
||||
}
|
||||
|
||||
func close() -> EventLoopFuture<Void> {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<Void>?) {
|
||||
private func writeRequestBodyPart0(_ data: IOData, request: HTTPExecutableRequest, promise: EventLoopPromise<Void>?)
|
||||
{
|
||||
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
|
||||
|
||||
@@ -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<Void> in
|
||||
@@ -162,7 +164,7 @@ final class HTTP2Connection {
|
||||
}
|
||||
|
||||
func close(promise: EventLoopPromise<Void>?) {
|
||||
return self.channel.close(mode: .all, promise: promise)
|
||||
self.channel.close(mode: .all, promise: promise)
|
||||
}
|
||||
|
||||
func close() -> EventLoopFuture<Void> {
|
||||
@@ -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<Void> in
|
||||
self.multiplexer.createStreamChannel(promise: createStreamChannelPromise) {
|
||||
channel -> EventLoopFuture<Void> 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<NegotiatedProtocol> {
|
||||
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<Channel> {
|
||||
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<Requester: HTTPConnectionRequester>(
|
||||
@@ -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<Channel> 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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
//
|
||||
|
||||
@@ -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<Void>?) -> 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<Void>?) -> 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"
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import NIOCore
|
||||
|
||||
#if canImport(Darwin)
|
||||
import func Darwin.pow
|
||||
#elseif canImport(Musl)
|
||||
|
||||
+19
-9
@@ -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..<self.connections.endIndex].reduce(into: 0) { count, connection in
|
||||
self.connections[self.overflowIndex..<self.connections.endIndex].reduce(into: 0) { count, connection in
|
||||
guard connection.eventLoop === eventLoop else { return }
|
||||
if connection.isConnecting || connection.isBackingOff {
|
||||
count &+= 1
|
||||
@@ -395,7 +399,10 @@ extension HTTPConnectionPool {
|
||||
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"
|
||||
)
|
||||
self.connections[index].connected(connection)
|
||||
let context = self.generateIdleConnectionContextForConnection(at: index)
|
||||
return (index, context)
|
||||
@@ -646,14 +653,16 @@ extension HTTPConnectionPool {
|
||||
},
|
||||
uniquingKeysWith: +
|
||||
)
|
||||
var connectionToCreate = requiredEventLoopOfPendingRequests
|
||||
var connectionToCreate =
|
||||
requiredEventLoopOfPendingRequests
|
||||
.flatMap { eventLoop, requestCount -> [(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
|
||||
|
||||
+17
-6
@@ -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)"
|
||||
}
|
||||
}
|
||||
|
||||
+61
-18
@@ -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,
|
||||
|
||||
+45
-17
@@ -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))
|
||||
}
|
||||
|
||||
+119
-74
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Error?> = .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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
public func execute(
|
||||
_ method: HTTPMethod = .GET,
|
||||
url: String,
|
||||
body: Body? = nil,
|
||||
deadline: NIODeadline? = nil,
|
||||
logger: Logger? = nil
|
||||
) -> EventLoopFuture<Response> {
|
||||
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<Response> {
|
||||
public func execute(
|
||||
_ method: HTTPMethod = .GET,
|
||||
socketPath: String,
|
||||
urlPath: String,
|
||||
body: Body? = nil,
|
||||
deadline: NIODeadline? = nil,
|
||||
logger: Logger? = nil
|
||||
) -> EventLoopFuture<Response> {
|
||||
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<Response> {
|
||||
public func execute(
|
||||
_ method: HTTPMethod = .GET,
|
||||
secureSocketPath: String,
|
||||
urlPath: String,
|
||||
body: Body? = nil,
|
||||
deadline: NIODeadline? = nil,
|
||||
logger: Logger? = nil
|
||||
) -> EventLoopFuture<Response> {
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
return self.execute(request: request,
|
||||
eventLoop: eventLoop,
|
||||
deadline: deadline,
|
||||
logger: HTTPClient.loggingDisabled)
|
||||
public func execute(
|
||||
request: Request,
|
||||
eventLoop: EventLoopPreference,
|
||||
deadline: NIODeadline? = nil
|
||||
) -> EventLoopFuture<Response> {
|
||||
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<Response> {
|
||||
public func execute(
|
||||
request: Request,
|
||||
eventLoop eventLoopPreference: EventLoopPreference,
|
||||
deadline: NIODeadline? = nil,
|
||||
logger: Logger?
|
||||
) -> EventLoopFuture<Response> {
|
||||
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<Delegate: HTTPClientResponseDelegate>(request: Request,
|
||||
delegate: Delegate,
|
||||
deadline: NIODeadline? = nil) -> Task<Delegate.Response> {
|
||||
return self.execute(request: request, delegate: delegate, deadline: deadline, logger: HTTPClient.loggingDisabled)
|
||||
public func execute<Delegate: HTTPClientResponseDelegate>(
|
||||
request: Request,
|
||||
delegate: Delegate,
|
||||
deadline: NIODeadline? = nil
|
||||
) -> Task<Delegate.Response> {
|
||||
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<Delegate: HTTPClientResponseDelegate>(request: Request,
|
||||
delegate: Delegate,
|
||||
deadline: NIODeadline? = nil,
|
||||
logger: Logger) -> Task<Delegate.Response> {
|
||||
return self.execute(request: request, delegate: delegate, eventLoop: .indifferent, deadline: deadline, logger: logger)
|
||||
public func execute<Delegate: HTTPClientResponseDelegate>(
|
||||
request: Request,
|
||||
delegate: Delegate,
|
||||
deadline: NIODeadline? = nil,
|
||||
logger: Logger
|
||||
) -> Task<Delegate.Response> {
|
||||
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<Delegate: HTTPClientResponseDelegate>(request: Request,
|
||||
delegate: Delegate,
|
||||
eventLoop eventLoopPreference: EventLoopPreference,
|
||||
deadline: NIODeadline? = nil) -> Task<Delegate.Response> {
|
||||
return self.execute(request: request,
|
||||
delegate: delegate,
|
||||
eventLoop: eventLoopPreference,
|
||||
deadline: deadline,
|
||||
logger: HTTPClient.loggingDisabled)
|
||||
public func execute<Delegate: HTTPClientResponseDelegate>(
|
||||
request: Request,
|
||||
delegate: Delegate,
|
||||
eventLoop eventLoopPreference: EventLoopPreference,
|
||||
deadline: NIODeadline? = nil
|
||||
) -> Task<Delegate.Response> {
|
||||
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<Delegate: HTTPClientResponseDelegate>(
|
||||
@@ -561,14 +643,14 @@ public class HTTPClient {
|
||||
delegate: Delegate,
|
||||
eventLoop eventLoopPreference: EventLoopPreference,
|
||||
deadline: NIODeadline? = nil,
|
||||
logger originalLogger: Logger?
|
||||
logger: Logger?
|
||||
) -> Task<Delegate.Response> {
|
||||
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<Delegate.Response> {
|
||||
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<Delegate.Response>? = 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<Delegate.Response>.failedTask(eventLoop: taskEL,
|
||||
error: HTTPClientError.alreadyShutdown,
|
||||
logger: logger,
|
||||
makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool)
|
||||
return Task<Delegate.Response>.failedTask(
|
||||
eventLoop: taskEL,
|
||||
error: HTTPClientError.alreadyShutdown,
|
||||
logger: logger,
|
||||
makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,7 +741,11 @@ public class HTTPClient {
|
||||
}
|
||||
}()
|
||||
|
||||
let task = Task<Delegate.Response>(eventLoop: taskEL, logger: logger, makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool)
|
||||
let task = Task<Delegate.Response>(
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -43,17 +43,18 @@ extension HTTPClient {
|
||||
/// - parameters:
|
||||
/// - data: `IOData` to write.
|
||||
public func write(_ data: IOData) -> EventLoopFuture<Void> {
|
||||
return self.closure(data)
|
||||
self.closure(data)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func writeChunks<Bytes: Collection>(of bytes: Bytes, maxChunkSize: Int) -> EventLoopFuture<Void> where Bytes.Element == UInt8 {
|
||||
func writeChunks<Bytes: Collection>(of bytes: Bytes, maxChunkSize: Int) -> EventLoopFuture<Void>
|
||||
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<Void> {
|
||||
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<Void>) -> Body {
|
||||
return Body(contentLength: length.flatMap { Int64($0) }, stream: stream)
|
||||
public static func stream(
|
||||
length: Int? = nil,
|
||||
_ stream: @Sendable @escaping (StreamWriter) -> EventLoopFuture<Void>
|
||||
) -> 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<Void>) -> Body {
|
||||
return Body(contentLength: contentLength, stream: stream)
|
||||
/// - stream: Body chunk provider.
|
||||
public static func stream(
|
||||
contentLength: Int64? = nil,
|
||||
_ stream: @Sendable @escaping (StreamWriter) -> EventLoopFuture<Void>
|
||||
) -> 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: Bytes) -> Body where Bytes: RandomAccessCollection, Bytes: Sendable, Bytes.Element == UInt8 {
|
||||
return Body(contentLength: Int64(bytes.count)) { writer in
|
||||
public static func bytes<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<Response>, _: HTTPResponseHead) -> EventLoopFuture<Void> {
|
||||
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<Response>, _: ByteBuffer) -> EventLoopFuture<Void> {
|
||||
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<Response>
|
||||
|
||||
@@ -772,14 +818,18 @@ extension HTTPClient {
|
||||
logger: Logger,
|
||||
makeOrGetFileIOThreadPool: @escaping () -> NIOThreadPool
|
||||
) -> Task<Response> {
|
||||
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<Response> {
|
||||
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<Delegate: HTTPClientResponseDelegate>(promise: EventLoopPromise<Response>?,
|
||||
with value: Response,
|
||||
delegateType: Delegate.Type,
|
||||
closing: Bool) {
|
||||
func succeed<Delegate: HTTPClientResponseDelegate>(
|
||||
promise: EventLoopPromise<Response>?,
|
||||
with value: Response,
|
||||
delegateType: Delegate.Type,
|
||||
closing: Bool
|
||||
) {
|
||||
promise?.succeed(value)
|
||||
}
|
||||
|
||||
func fail<Delegate: HTTPClientResponseDelegate>(with error: Error,
|
||||
delegateType: Delegate.Type) {
|
||||
func fail<Delegate: HTTPClientResponseDelegate>(
|
||||
with error: Error,
|
||||
delegateType: Delegate.Type
|
||||
) {
|
||||
self.promise.fail(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,9 +52,11 @@ struct LRUCache<Key: Equatable, Value> {
|
||||
|
||||
@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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -33,7 +33,10 @@ final class NWWaitingHandler<Requester: HTTPConnectionRequester>: 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)
|
||||
}
|
||||
|
||||
@@ -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<NWProtocolTLS.Options> {
|
||||
func getNWProtocolTLSOptions(
|
||||
on eventLoop: EventLoop,
|
||||
serverNameIndicatorOverride: String?
|
||||
) -> EventLoopFuture<NWProtocolTLS.Options> {
|
||||
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
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import struct Foundation.URL
|
||||
import NIOHTTP1
|
||||
|
||||
import struct Foundation.URL
|
||||
|
||||
typealias RedirectMode = HTTPClient.Configuration.RedirectConfiguration.Mode
|
||||
|
||||
struct RedirectState {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -58,13 +58,15 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
|
||||
|
||||
let eventLoopPreference: HTTPClient.EventLoopPreference
|
||||
|
||||
init(request: HTTPClient.Request,
|
||||
eventLoopPreference: HTTPClient.EventLoopPreference,
|
||||
task: HTTPClient.Task<Delegate.Response>,
|
||||
redirectHandler: RedirectHandler<Delegate.Response>?,
|
||||
connectionDeadline: NIODeadline,
|
||||
requestOptions: RequestOptions,
|
||||
delegate: Delegate) throws {
|
||||
init(
|
||||
request: HTTPClient.Request,
|
||||
eventLoopPreference: HTTPClient.EventLoopPreference,
|
||||
task: HTTPClient.Task<Delegate.Response>,
|
||||
redirectHandler: RedirectHandler<Delegate.Response>?,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -25,30 +25,38 @@ final class SSLContextCache {
|
||||
}
|
||||
|
||||
extension SSLContextCache {
|
||||
func sslContext(tlsConfiguration: TLSConfiguration,
|
||||
eventLoop: EventLoop,
|
||||
logger: Logger) -> EventLoopFuture<NIOSSLContext> {
|
||||
func sslContext(
|
||||
tlsConfiguration: TLSConfiguration,
|
||||
eventLoop: EventLoop,
|
||||
logger: Logger
|
||||
) -> EventLoopFuture<NIOSSLContext> {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
|
||||
extension HTTPClient.EventLoopPreference: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "\(self.preference)"
|
||||
"\(self.preference)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate {
|
||||
}
|
||||
|
||||
public func didReceiveBodyPart(task: HTTPClient.Task<Void>, _ buffer: ByteBuffer) -> EventLoopFuture<Void> {
|
||||
return self.chunkHandler(buffer)
|
||||
self.chunkHandler(buffer)
|
||||
}
|
||||
|
||||
public func didFinishRequest(task: HTTPClient.Task<Void>) 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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ByteBuffer>()
|
||||
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<ByteBuffer>()
|
||||
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)
|
||||
) {
|
||||
|
||||
@@ -33,7 +33,7 @@ final class AsyncSequenceWriter<Element>: AsyncSequence, @unchecked Sendable {
|
||||
}
|
||||
|
||||
func makeAsyncIterator() -> Iterator {
|
||||
return Iterator(self)
|
||||
Iterator(self)
|
||||
}
|
||||
|
||||
private enum State {
|
||||
@@ -117,7 +117,9 @@ final class AsyncSequenceWriter<Element>: 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)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ResponseBackpressureDelegate>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseBackpressureDelegate>?
|
||||
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<ResponseBackpressureDelegate>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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))
|
||||
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))
|
||||
// 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)
|
||||
}))
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseBackpressureDelegate>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<Void>, _ buffer: ByteBuffer) -> EventLoopFuture<Void> {
|
||||
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<Void>) 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."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
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 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
|
||||
|
||||
@@ -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<HTTPResponseDecoder>.self))
|
||||
XCTAssertNotNil(
|
||||
try embedded.pipeline.syncOperations.handler(type: ByteToMessageHandler<HTTPResponseDecoder>.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<HTTPResponseDecoder>.self))
|
||||
XCTAssertThrowsError(try embedded.pipeline.syncOperations.handler(type: NIOHTTPResponseDecompressor.self)) { error in
|
||||
XCTAssertNotNil(
|
||||
try embedded.pipeline.syncOperations.handler(type: ByteToMessageHandler<HTTPResponseDecoder>.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<Void> in
|
||||
@Sendable func recursive(count: UInt8, promise: EventLoopPromise<Void>) {
|
||||
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<Void> in
|
||||
@Sendable func recursive(count: UInt8, promise: EventLoopPromise<Void>) {
|
||||
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<HTTPClient.Response>(eventLoop: clientEL, logger: logger)
|
||||
|
||||
var maybeRequestBag: RequestBag<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<Void>, _ head: HTTPResponseHead) -> EventLoopFuture<Void> {
|
||||
return task.futureResult.eventLoop.makeSucceededVoidFuture()
|
||||
task.futureResult.eventLoop.makeSucceededVoidFuture()
|
||||
}
|
||||
|
||||
func didReceiveBodyPart(task: HTTPClient.Task<Response>, _ buffer: ByteBuffer) -> EventLoopFuture<Void> {
|
||||
@@ -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<BackpressureTestDelegate>?
|
||||
|
||||
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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<ResponseBackpressureDelegate>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseBackpressureDelegate>?
|
||||
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<ResponseBackpressureDelegate>?
|
||||
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<ResponseBackpressureDelegate>?
|
||||
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))
|
||||
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))
|
||||
// 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)
|
||||
}))
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseBackpressureDelegate>?
|
||||
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<ResponseAccumulator>?
|
||||
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()
|
||||
|
||||
@@ -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..<numberOfRequestsPerWorkers {
|
||||
var response: HTTPClient.Response?
|
||||
@@ -195,13 +196,14 @@ class HTTP2ClientTests: XCTestCase {
|
||||
let el = elg.next()
|
||||
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..<numberOfRequestsPerWorkers {
|
||||
var response: HTTPClient.Response?
|
||||
let request = try! HTTPClient.Request(url: url)
|
||||
let requestPromise = localClient
|
||||
let requestPromise =
|
||||
localClient
|
||||
.execute(
|
||||
request: request,
|
||||
eventLoop: .delegateAndChannel(on: el)
|
||||
@@ -251,10 +253,13 @@ class HTTP2ClientTests: XCTestCase {
|
||||
XCTAssertNoThrow(try client.syncShutdown())
|
||||
|
||||
var results: [Result<HTTPClient.Response, Error>] = []
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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..<Int32.max).randomElement()!)
|
||||
let goAwayFrame = HTTP2Frame(streamID: randomStreamID, payload: .goAway(lastStreamID: 0, errorCode: .http11Required, opaqueData: nil))
|
||||
let goAwayFrame = HTTP2Frame(
|
||||
streamID: randomStreamID,
|
||||
payload: .goAway(lastStreamID: 0, errorCode: .http11Required, opaqueData: nil)
|
||||
)
|
||||
XCTAssertEqual(delegate.goAwayReceived, false)
|
||||
XCTAssertNoThrow(try embedded.writeInbound(goAwayFrame))
|
||||
XCTAssertEqual(delegate.goAwayReceived, true)
|
||||
@@ -100,13 +120,19 @@ 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 randomStreamID = HTTP2StreamID((0..<Int32.max).randomElement()!)
|
||||
let goAwayFrame = HTTP2Frame(streamID: randomStreamID, payload: .goAway(lastStreamID: 0, errorCode: .http11Required, opaqueData: nil))
|
||||
let goAwayFrame = HTTP2Frame(
|
||||
streamID: randomStreamID,
|
||||
payload: .goAway(lastStreamID: 0, errorCode: .http11Required, opaqueData: nil)
|
||||
)
|
||||
XCTAssertEqual(delegate.goAwayReceived, false)
|
||||
XCTAssertNoThrow(try embedded.writeInbound(goAwayFrame))
|
||||
XCTAssertEqual(delegate.goAwayReceived, true)
|
||||
@@ -138,7 +164,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)
|
||||
@@ -154,7 +183,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)
|
||||
@@ -163,7 +195,11 @@ class HTTP2IdleHandlerTests: XCTestCase {
|
||||
|
||||
for i in 0..<(1...100).randomElement()! {
|
||||
let streamID = HTTP2StreamID(i)
|
||||
let event = NIOHTTP2StreamCreatedEvent(streamID: streamID, localInitialWindowSize: nil, remoteInitialWindowSize: nil)
|
||||
let event = NIOHTTP2StreamCreatedEvent(
|
||||
streamID: streamID,
|
||||
localInitialWindowSize: nil,
|
||||
remoteInitialWindowSize: nil
|
||||
)
|
||||
embedded.pipeline.fireUserInboundEventTriggered(event)
|
||||
openStreams.insert(streamID)
|
||||
}
|
||||
@@ -191,7 +227,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)
|
||||
@@ -200,13 +239,20 @@ class HTTP2IdleHandlerTests: XCTestCase {
|
||||
|
||||
for i in 0..<(1...100).randomElement()! {
|
||||
let streamID = HTTP2StreamID(i)
|
||||
let event = NIOHTTP2StreamCreatedEvent(streamID: streamID, localInitialWindowSize: nil, remoteInitialWindowSize: nil)
|
||||
let event = NIOHTTP2StreamCreatedEvent(
|
||||
streamID: streamID,
|
||||
localInitialWindowSize: nil,
|
||||
remoteInitialWindowSize: nil
|
||||
)
|
||||
embedded.pipeline.fireUserInboundEventTriggered(event)
|
||||
openStreams.insert(streamID)
|
||||
}
|
||||
|
||||
let goAwayStreamID = HTTP2StreamID(openStreams.count)
|
||||
let goAwayFrame = HTTP2Frame(streamID: goAwayStreamID, payload: .goAway(lastStreamID: 0, errorCode: .http11Required, opaqueData: nil))
|
||||
let goAwayFrame = HTTP2Frame(
|
||||
streamID: goAwayStreamID,
|
||||
payload: .goAway(lastStreamID: 0, errorCode: .http11Required, opaqueData: nil)
|
||||
)
|
||||
XCTAssertEqual(delegate.goAwayReceived, false)
|
||||
XCTAssertNoThrow(try embedded.writeInbound(goAwayFrame))
|
||||
XCTAssertEqual(delegate.goAwayReceived, true)
|
||||
@@ -232,7 +278,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)
|
||||
@@ -241,12 +290,18 @@ class HTTP2IdleHandlerTests: XCTestCase {
|
||||
embedded.pipeline.triggerUserOutboundEvent(HTTPConnectionEvent.shutdownRequested, promise: nil)
|
||||
XCTAssertFalse(embedded.isActive)
|
||||
|
||||
let newSettingsFrame = HTTP2Frame(streamID: 0, payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 20)])))
|
||||
let newSettingsFrame = HTTP2Frame(
|
||||
streamID: 0,
|
||||
payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 20)]))
|
||||
)
|
||||
XCTAssertEqual(delegate.maxStreams, 10)
|
||||
XCTAssertNoThrow(try embedded.writeInbound(newSettingsFrame))
|
||||
XCTAssertEqual(delegate.maxStreams, 10, "Expected message to not be forwarded.")
|
||||
|
||||
let goAwayFrame = HTTP2Frame(streamID: HTTP2StreamID(0), payload: .goAway(lastStreamID: 2, errorCode: .http11Required, opaqueData: nil))
|
||||
let goAwayFrame = HTTP2Frame(
|
||||
streamID: HTTP2StreamID(0),
|
||||
payload: .goAway(lastStreamID: 2, errorCode: .http11Required, opaqueData: nil)
|
||||
)
|
||||
XCTAssertEqual(delegate.goAwayReceived, false)
|
||||
XCTAssertNoThrow(try embedded.writeInbound(goAwayFrame))
|
||||
XCTAssertEqual(delegate.goAwayReceived, false, "Expected go away to not be forwarded.")
|
||||
@@ -258,19 +313,30 @@ 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: 100)])))
|
||||
let settingsFrame = HTTP2Frame(
|
||||
streamID: 0,
|
||||
payload: .settings(.settings([.init(parameter: .maxConcurrentStreams, value: 100)]))
|
||||
)
|
||||
XCTAssertEqual(delegate.maxStreams, nil)
|
||||
XCTAssertNoThrow(try embedded.writeInbound(settingsFrame))
|
||||
XCTAssertEqual(delegate.maxStreams, 100)
|
||||
|
||||
for streamID in HTTP2StreamID(1)..<HTTP2StreamID(5) {
|
||||
let event = NIOHTTP2StreamCreatedEvent(streamID: streamID, localInitialWindowSize: nil, remoteInitialWindowSize: nil)
|
||||
let event = NIOHTTP2StreamCreatedEvent(
|
||||
streamID: streamID,
|
||||
localInitialWindowSize: nil,
|
||||
remoteInitialWindowSize: nil
|
||||
)
|
||||
embedded.pipeline.fireUserInboundEventTriggered(event)
|
||||
XCTAssertFalse(delegate.goAwayReceived)
|
||||
}
|
||||
|
||||
// Open one the last stream.
|
||||
let event = NIOHTTP2StreamCreatedEvent(streamID: HTTP2StreamID(5), localInitialWindowSize: nil, remoteInitialWindowSize: nil)
|
||||
let event = NIOHTTP2StreamCreatedEvent(
|
||||
streamID: HTTP2StreamID(5),
|
||||
localInitialWindowSize: nil,
|
||||
remoteInitialWindowSize: nil
|
||||
)
|
||||
embedded.pipeline.fireUserInboundEventTriggered(event)
|
||||
XCTAssertTrue(delegate.goAwayReceived)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/* 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 Logging
|
||||
import NIOCore
|
||||
import NIOPosix
|
||||
@@ -29,7 +29,7 @@ class HTTPClientSOCKSTests: XCTestCase {
|
||||
var backgroundLogStore: CollectEverythingLogHandler.LogStore!
|
||||
|
||||
var defaultHTTPBinURLPrefix: String {
|
||||
return "http://localhost:\(self.defaultHTTPBin.port)/"
|
||||
"http://localhost:\(self.defaultHTTPBin.port)/"
|
||||
}
|
||||
|
||||
override func setUp() {
|
||||
@@ -43,12 +43,17 @@ class HTTPClientSOCKSTests: XCTestCase {
|
||||
self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||
self.defaultHTTPBin = HTTPBin()
|
||||
self.backgroundLogStore = CollectEverythingLogHandler.LogStore()
|
||||
var backgroundLogger = Logger(label: "\(#function)", factory: { _ in
|
||||
CollectEverythingLogHandler(logStore: self.backgroundLogStore!)
|
||||
})
|
||||
var backgroundLogger = Logger(
|
||||
label: "\(#function)",
|
||||
factory: { _ in
|
||||
CollectEverythingLogHandler(logStore: self.backgroundLogStore!)
|
||||
}
|
||||
)
|
||||
backgroundLogger.logLevel = .trace
|
||||
self.defaultClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
backgroundActivityLogger: backgroundLogger)
|
||||
self.defaultClient = HTTPClient(
|
||||
eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
backgroundActivityLogger: backgroundLogger
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -75,10 +80,12 @@ class HTTPClientSOCKSTests: XCTestCase {
|
||||
|
||||
func testProxySOCKS() throws {
|
||||
let socksBin = try MockSOCKSServer(expectedURL: "/socks/test", expectedResponse: "it works!")
|
||||
let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: .init(
|
||||
proxy: .socksServer(host: "localhost", port: socksBin.port)
|
||||
).enableFastFailureModeForTesting())
|
||||
let localClient = HTTPClient(
|
||||
eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: .init(
|
||||
proxy: .socksServer(host: "localhost", port: socksBin.port)
|
||||
).enableFastFailureModeForTesting()
|
||||
)
|
||||
|
||||
defer {
|
||||
XCTAssertNoThrow(try localClient.syncShutdown())
|
||||
@@ -94,8 +101,10 @@ class HTTPClientSOCKSTests: XCTestCase {
|
||||
func testProxySOCKSBogusAddress() throws {
|
||||
let config = HTTPClient.Configuration(proxy: .socksServer(host: "127.0.."))
|
||||
.enableFastFailureModeForTesting()
|
||||
let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: config)
|
||||
let localClient = HTTPClient(
|
||||
eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: config
|
||||
)
|
||||
|
||||
defer {
|
||||
XCTAssertNoThrow(try localClient.syncShutdown())
|
||||
@@ -109,8 +118,10 @@ class HTTPClientSOCKSTests: XCTestCase {
|
||||
let config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost", port: localHTTPBin.port))
|
||||
.enableFastFailureModeForTesting()
|
||||
|
||||
let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: config)
|
||||
let localClient = HTTPClient(
|
||||
eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: config
|
||||
)
|
||||
defer {
|
||||
XCTAssertNoThrow(try localClient.syncShutdown())
|
||||
XCTAssertNoThrow(try localHTTPBin.shutdown())
|
||||
@@ -123,8 +134,10 @@ class HTTPClientSOCKSTests: XCTestCase {
|
||||
let config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost"))
|
||||
.enableFastFailureModeForTesting()
|
||||
|
||||
let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: config)
|
||||
let localClient = HTTPClient(
|
||||
eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: config
|
||||
)
|
||||
defer {
|
||||
XCTAssertNoThrow(try localClient.syncShutdown())
|
||||
}
|
||||
@@ -137,8 +150,10 @@ class HTTPClientSOCKSTests: XCTestCase {
|
||||
let config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost", port: socksBin.port))
|
||||
.enableFastFailureModeForTesting()
|
||||
|
||||
let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: config)
|
||||
let localClient = HTTPClient(
|
||||
eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: config
|
||||
)
|
||||
|
||||
defer {
|
||||
XCTAssertNoThrow(try localClient.syncShutdown())
|
||||
|
||||
@@ -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
|
||||
|
||||
class XCTestCaseHTTPClientTestsBaseClass: XCTestCase {
|
||||
typealias Request = HTTPClient.Request
|
||||
|
||||
@@ -53,13 +54,18 @@ class XCTestCaseHTTPClientTestsBaseClass: XCTestCase {
|
||||
self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||
self.defaultHTTPBin = HTTPBin()
|
||||
self.backgroundLogStore = CollectEverythingLogHandler.LogStore()
|
||||
var backgroundLogger = Logger(label: "\(#function)", factory: { _ in
|
||||
CollectEverythingLogHandler(logStore: self.backgroundLogStore!)
|
||||
})
|
||||
var backgroundLogger = Logger(
|
||||
label: "\(#function)",
|
||||
factory: { _ in
|
||||
CollectEverythingLogHandler(logStore: self.backgroundLogStore!)
|
||||
}
|
||||
)
|
||||
backgroundLogger.logLevel = .trace
|
||||
self.defaultClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: HTTPClient.Configuration().enableFastFailureModeForTesting(),
|
||||
backgroundActivityLogger: backgroundLogger)
|
||||
self.defaultClient = HTTPClient(
|
||||
eventLoopGroupProvider: .shared(self.clientGroup),
|
||||
configuration: HTTPClient.Configuration().enableFastFailureModeForTesting(),
|
||||
backgroundActivityLogger: backgroundLogger
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
@@ -19,7 +19,8 @@ import XCTest
|
||||
|
||||
class HTTPClientCookieTests: XCTestCase {
|
||||
func testCookie() {
|
||||
let v = "key=value; PaTh=/path; DoMaIn=EXampLE.CoM; eXpIRes=Wed, 21 Oct 2015 07:28:00 GMT; max-AGE=42; seCURE; HTTPOnly"
|
||||
let v =
|
||||
"key=value; PaTh=/path; DoMaIn=EXampLE.CoM; eXpIRes=Wed, 21 Oct 2015 07:28:00 GMT; max-AGE=42; seCURE; HTTPOnly"
|
||||
guard let c = HTTPClient.Cookie(header: v, defaultDomain: "exAMPle.cOm") else {
|
||||
XCTFail("Failed to parse cookie")
|
||||
return
|
||||
@@ -67,7 +68,16 @@ class HTTPClientCookieTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCookieInit() {
|
||||
let c = HTTPClient.Cookie(name: "key", value: "value", path: "/path", domain: "example.com", expires: Date(timeIntervalSince1970: 1_445_412_480), maxAge: 42, httpOnly: true, secure: true)
|
||||
let c = HTTPClient.Cookie(
|
||||
name: "key",
|
||||
value: "value",
|
||||
path: "/path",
|
||||
domain: "example.com",
|
||||
expires: Date(timeIntervalSince1970: 1_445_412_480),
|
||||
maxAge: 42,
|
||||
httpOnly: true,
|
||||
secure: true
|
||||
)
|
||||
XCTAssertEqual("key", c.name)
|
||||
XCTAssertEqual("value", c.value)
|
||||
XCTAssertEqual("/path", c.path)
|
||||
@@ -118,17 +128,26 @@ class HTTPClientCookieTests: XCTestCase {
|
||||
XCTAssertNil(c?.expires)
|
||||
|
||||
// Later values override earlier values, except if they are ignored.
|
||||
c = HTTPClient.Cookie(header: "key=value; expires=Sunday, 06-Nov-94 08:49:37 GMT; expires=04/01/2022", defaultDomain: "example.com")
|
||||
c = HTTPClient.Cookie(
|
||||
header: "key=value; expires=Sunday, 06-Nov-94 08:49:37 GMT; expires=04/01/2022",
|
||||
defaultDomain: "example.com"
|
||||
)
|
||||
XCTAssertEqual("key", c?.name)
|
||||
XCTAssertEqual("value", c?.value)
|
||||
XCTAssertEqual(Date(timeIntervalSince1970: 784_111_777), c?.expires)
|
||||
|
||||
c = HTTPClient.Cookie(header: "key=value; expires=Sunday, 06-Nov-94 08:49:37 GMT; expires=", defaultDomain: "example.com")
|
||||
c = HTTPClient.Cookie(
|
||||
header: "key=value; expires=Sunday, 06-Nov-94 08:49:37 GMT; expires=",
|
||||
defaultDomain: "example.com"
|
||||
)
|
||||
XCTAssertEqual("key", c?.name)
|
||||
XCTAssertEqual("value", c?.value)
|
||||
XCTAssertEqual(Date(timeIntervalSince1970: 784_111_777), c?.expires)
|
||||
|
||||
c = HTTPClient.Cookie(header: "key=value; expires=Sunday, 06-Nov-94 08:49:37 GMT; expires", defaultDomain: "example.com")
|
||||
c = HTTPClient.Cookie(
|
||||
header: "key=value; expires=Sunday, 06-Nov-94 08:49:37 GMT; expires",
|
||||
defaultDomain: "example.com"
|
||||
)
|
||||
XCTAssertEqual("key", c?.name)
|
||||
XCTAssertEqual("value", c?.value)
|
||||
XCTAssertEqual(Date(timeIntervalSince1970: 784_111_777), c?.expires)
|
||||
@@ -467,11 +486,17 @@ class HTTPClientCookieTests: XCTestCase {
|
||||
try XCTSkipIf(localeCheck.tm_mon != 1, "Unable to set locale")
|
||||
|
||||
// Cookie parsing should be independent of C locale.
|
||||
var c = HTTPClient.Cookie(header: "key=value; eXpIRes=Sunday, 06-Nov-94 08:49:37 GMT;", defaultDomain: "example.org")
|
||||
var c = HTTPClient.Cookie(
|
||||
header: "key=value; eXpIRes=Sunday, 06-Nov-94 08:49:37 GMT;",
|
||||
defaultDomain: "example.org"
|
||||
)
|
||||
XCTAssertEqual(Date(timeIntervalSince1970: 784_111_777), c?.expires)
|
||||
c = HTTPClient.Cookie(header: "key=value; eXpIRes=Sun Nov 6 08:49:37 1994;", defaultDomain: "example.org")!
|
||||
XCTAssertEqual(Date(timeIntervalSince1970: 784_111_777), c?.expires)
|
||||
c = HTTPClient.Cookie(header: "key=value; eXpIRes=Sonntag, 06-Nov-94 08:49:37 GMT;", defaultDomain: "example.org")!
|
||||
c = HTTPClient.Cookie(
|
||||
header: "key=value; eXpIRes=Sonntag, 06-Nov-94 08:49:37 GMT;",
|
||||
defaultDomain: "example.org"
|
||||
)!
|
||||
XCTAssertNil(c?.expires)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,10 @@ final class HTTPClientReproTests: XCTestCase {
|
||||
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
||||
switch self.unwrapInboundIn(data) {
|
||||
case .head:
|
||||
context.writeAndFlush(self.wrapOutboundOut(.head(.init(version: .http1_1, status: .continue))), promise: nil)
|
||||
context.writeAndFlush(
|
||||
self.wrapOutboundOut(.head(.init(version: .http1_1, status: .continue))),
|
||||
promise: nil
|
||||
)
|
||||
case .body:
|
||||
break
|
||||
case .end:
|
||||
@@ -47,14 +50,16 @@ final class HTTPClientReproTests: XCTestCase {
|
||||
let body = #"{"foo": "bar"}"#
|
||||
|
||||
var maybeRequest: HTTPClient.Request?
|
||||
XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(
|
||||
url: "http://localhost:\(httpBin.port)/",
|
||||
method: .POST,
|
||||
headers: [
|
||||
"Content-Type": "application/json",
|
||||
],
|
||||
body: .string(body)
|
||||
))
|
||||
XCTAssertNoThrow(
|
||||
maybeRequest = try HTTPClient.Request(
|
||||
url: "http://localhost:\(httpBin.port)/",
|
||||
method: .POST,
|
||||
headers: [
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: .string(body)
|
||||
)
|
||||
)
|
||||
guard let request = maybeRequest else { return XCTFail("Expected to have a request here") }
|
||||
|
||||
var logger = Logger(label: "test")
|
||||
@@ -73,10 +78,14 @@ final class HTTPClientReproTests: XCTestCase {
|
||||
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
||||
switch self.unwrapInboundIn(data) {
|
||||
case .head:
|
||||
let head = HTTPResponseHead(version: .http1_1, status: .switchingProtocols, headers: [
|
||||
"Connection": "Upgrade",
|
||||
"Upgrade": "Websocket",
|
||||
])
|
||||
let head = HTTPResponseHead(
|
||||
version: .http1_1,
|
||||
status: .switchingProtocols,
|
||||
headers: [
|
||||
"Connection": "Upgrade",
|
||||
"Upgrade": "Websocket",
|
||||
]
|
||||
)
|
||||
let body = context.channel.allocator.buffer(string: "foo bar")
|
||||
|
||||
context.write(self.wrapOutboundOut(.head(head)), promise: nil)
|
||||
@@ -101,14 +110,16 @@ final class HTTPClientReproTests: XCTestCase {
|
||||
let body = #"{"foo": "bar"}"#
|
||||
|
||||
var maybeRequest: HTTPClient.Request?
|
||||
XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(
|
||||
url: "http://localhost:\(httpBin.port)/",
|
||||
method: .POST,
|
||||
headers: [
|
||||
"Content-Type": "application/json",
|
||||
],
|
||||
body: .string(body)
|
||||
))
|
||||
XCTAssertNoThrow(
|
||||
maybeRequest = try HTTPClient.Request(
|
||||
url: "http://localhost:\(httpBin.port)/",
|
||||
method: .POST,
|
||||
headers: [
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: .string(body)
|
||||
)
|
||||
)
|
||||
guard let request = maybeRequest else { return XCTFail("Expected to have a request here") }
|
||||
|
||||
var logger = Logger(label: "test")
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import AsyncHTTPClient
|
||||
import NIOConcurrencyHelpers
|
||||
import NIOCore
|
||||
import NIOEmbedded
|
||||
@@ -21,6 +20,8 @@ import NIOPosix
|
||||
import NIOTestUtils
|
||||
import XCTest
|
||||
|
||||
@testable import AsyncHTTPClient
|
||||
|
||||
class HTTPClientInternalTests: XCTestCase {
|
||||
typealias Request = HTTPClient.Request
|
||||
typealias Task = HTTPClient.Task
|
||||
@@ -204,15 +205,19 @@ class HTTPClientInternalTests: XCTestCase {
|
||||
self.receivedMessages.append(.error(error))
|
||||
}
|
||||
|
||||
public func didReceiveHead(task: HTTPClient.Task<Response>,
|
||||
_ head: HTTPResponseHead) -> EventLoopFuture<Void> {
|
||||
public func didReceiveHead(
|
||||
task: HTTPClient.Task<Response>,
|
||||
_ head: HTTPResponseHead
|
||||
) -> EventLoopFuture<Void> {
|
||||
self.eventLoop.assertInEventLoop()
|
||||
self.receivedMessages.append(.head(head))
|
||||
return self.randoEL.makeSucceededFuture(())
|
||||
}
|
||||
|
||||
func didReceiveBodyPart(task: HTTPClient.Task<Response>,
|
||||
_ buffer: ByteBuffer) -> EventLoopFuture<Void> {
|
||||
func didReceiveBodyPart(
|
||||
task: HTTPClient.Task<Response>,
|
||||
_ buffer: ByteBuffer
|
||||
) -> EventLoopFuture<Void> {
|
||||
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
|
||||
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
|
||||
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<Int> = []
|
||||
let elements: [Int] = []
|
||||
XCTAssertTrue(elements.hasSuffix([]))
|
||||
XCTAssertFalse(elements.hasSuffix([0]))
|
||||
XCTAssertFalse(elements.hasSuffix([42]))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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<UInt8> & 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<UInt8> & 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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Response>) 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<T>(directory: String = temporaryDirectory,
|
||||
_ body: (String) throws -> T) throws -> T {
|
||||
internal static func withTemporaryUnixDomainSocketPathName<T>(
|
||||
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<RequestHandler: ChannelInboundHandler> where
|
||||
internal final class HTTPBin<RequestHandler: ChannelInboundHandler>
|
||||
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<RequestHandler: ChannelInboundHandler> 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<RequestHandler: ChannelInboundHandler> 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<Void>, 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-----
|
||||
"""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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<Left, Right: Equatable>(
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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") }
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
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<ResponseAccumulator>?
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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.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.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.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.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))
|
||||
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 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))
|
||||
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 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))
|
||||
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 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))
|
||||
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 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))
|
||||
XCTAssertEqual(
|
||||
state.startRequest(head: requestHead, metadata: metadata),
|
||||
.sendRequestHead(requestHead, sendEnd: true)
|
||||
)
|
||||
|
||||
state.errorHappened(HTTPParserError.invalidChunkSize).assertFailRequest(HTTPParserError.invalidChunkSize, .close(nil))
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import AsyncHTTPClient
|
||||
import XCTest
|
||||
|
||||
@testable import AsyncHTTPClient
|
||||
|
||||
class LRUCacheTests: XCTestCase {
|
||||
func testBasicsWork() {
|
||||
var cache = LRUCache<Int, Int>(capacity: 1)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<RequestParts>)
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user