diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1ClientChannelHandler.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1ClientChannelHandler.swift index 910be3d1d..9c300b5e6 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1ClientChannelHandler.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1ClientChannelHandler.swift @@ -39,7 +39,7 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler { requestLogger[metadataKey: "ahc-el"] = "\(self.connection.channel.eventLoop)" self.logger = requestLogger - if let idleReadTimeout = newRequest.idleReadTimeout { + if let idleReadTimeout = newRequest.requestOptions.idleReadTimeout { self.idleReadTimeoutStateMachine = .init(timeAmount: idleReadTimeout) } } else { @@ -146,7 +146,11 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler { self.logger.debug("Request was scheduled on connection") req.willExecuteRequest(self) - let action = self.state.runNewRequest(head: req.requestHead, metadata: req.requestFramingMetadata) + let action = self.state.runNewRequest( + head: req.requestHead, + metadata: req.requestFramingMetadata, + ignoreUncleanSSLShutdown: req.requestOptions.ignoreUncleanSSLShutdown + ) self.run(action, context: context) } diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1ConnectionStateMachine.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1ConnectionStateMachine.swift index cebc8f1c1..012544441 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1ConnectionStateMachine.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1ConnectionStateMachine.swift @@ -154,13 +154,18 @@ struct HTTP1ConnectionStateMachine { } } - mutating func runNewRequest(head: HTTPRequestHead, metadata: RequestFramingMetadata) -> Action { + mutating func runNewRequest( + head: HTTPRequestHead, + metadata: RequestFramingMetadata, + ignoreUncleanSSLShutdown: Bool + ) -> Action { guard case .idle = self.state else { preconditionFailure("Invalid state") } var requestStateMachine = HTTPRequestStateMachine( - isChannelWritable: self.isChannelWritable + isChannelWritable: self.isChannelWritable, + ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown ) let action = requestStateMachine.startRequest(head: head, metadata: metadata) diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift index d17e030fa..281287750 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2ClientRequestHandler.swift @@ -24,7 +24,7 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler { private let eventLoop: EventLoop - private var state: HTTPRequestStateMachine = .init(isChannelWritable: false) { + private var state: HTTPRequestStateMachine = .init(isChannelWritable: false, ignoreUncleanSSLShutdown: false) { willSet { self.eventLoop.assertInEventLoop() } @@ -35,7 +35,7 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler { private var request: HTTPExecutableRequest? { didSet { - if let newRequest = self.request, let idleReadTimeout = newRequest.idleReadTimeout { + if let newRequest = self.request, let idleReadTimeout = newRequest.requestOptions.idleReadTimeout { self.idleReadTimeoutStateMachine = .init(timeAmount: idleReadTimeout) } else { self.idleReadTimeoutStateMachine = nil diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPExecutableRequest.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPExecutableRequest.swift index 79aa1c1b3..2477e1154 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPExecutableRequest.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPExecutableRequest.swift @@ -219,8 +219,8 @@ protocol HTTPExecutableRequest: AnyObject { /// ``requestHeadSent``. var requestFramingMetadata: RequestFramingMetadata { get } - /// The maximal `TimeAmount` that is allowed to pass between `channelRead`s from the Channel. - var idleReadTimeout: TimeAmount? { get } + /// Request specific configurations + var requestOptions: RequestOptions { get } /// Will be called by the ChannelHandler to indicate that the request is going to be sent. /// diff --git a/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine.swift b/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine.swift index 34d2ec433..b7d05e415 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine.swift @@ -14,6 +14,7 @@ import NIOCore import NIOHTTP1 +import NIOSSL struct HTTPRequestStateMachine { fileprivate enum State { @@ -102,8 +103,11 @@ struct HTTPRequestStateMachine { private var isChannelWritable: Bool - init(isChannelWritable: Bool) { + private let ignoreUncleanSSLShutdown: Bool + + init(isChannelWritable: Bool, ignoreUncleanSSLShutdown: Bool) { self.isChannelWritable = isChannelWritable + self.ignoreUncleanSSLShutdown = ignoreUncleanSSLShutdown } mutating func startRequest(head: HTTPRequestHead, metadata: RequestFramingMetadata) -> Action { @@ -196,6 +200,12 @@ struct HTTPRequestStateMachine { // the request failed, before it was sent onto the wire. self.state = .failed(error) return .failRequest(error, .none) + + case .running(.streaming, .receivingBody), + .running(.endSent, .receivingBody) + where error as? NIOSSLError == .uncleanShutdown && self.ignoreUncleanSSLShutdown == true: + return .wait + case .running: self.state = .failed(error) return .failRequest(error, .close) diff --git a/Sources/AsyncHTTPClient/ConnectionPool/RequestOptions.swift b/Sources/AsyncHTTPClient/ConnectionPool/RequestOptions.swift new file mode 100644 index 000000000..98f92b661 --- /dev/null +++ b/Sources/AsyncHTTPClient/ConnectionPool/RequestOptions.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import NIOCore + +struct RequestOptions { + /// The maximal `TimeAmount` that is allowed to pass between `channelRead`s from the Channel. + var idleReadTimeout: TimeAmount? + + /// Should `NIOSSLError.uncleanShutdown` be forwarded to the user in HTTP/1 mode. + var ignoreUncleanSSLShutdown: Bool + + init(idleReadTimeout: TimeAmount?, ignoreUncleanSSLShutdown: Bool) { + self.idleReadTimeout = idleReadTimeout + self.ignoreUncleanSSLShutdown = ignoreUncleanSSLShutdown + } +} + +extension RequestOptions { + static func fromClientConfiguration(_ configuration: HTTPClient.Configuration) -> Self { + RequestOptions( + idleReadTimeout: configuration.timeout.read, + ignoreUncleanSSLShutdown: configuration.ignoreUncleanSSLShutdown + ) + } +} diff --git a/Sources/AsyncHTTPClient/RequestBag.swift b/Sources/AsyncHTTPClient/RequestBag.swift index 1be6ba9d2..939f46ee2 100644 --- a/Sources/AsyncHTTPClient/RequestBag.swift +++ b/Sources/AsyncHTTPClient/RequestBag.swift @@ -39,7 +39,7 @@ final class RequestBag { let connectionDeadline: NIODeadline - let idleReadTimeout: TimeAmount? + let requestOptions: RequestOptions let requestHead: HTTPRequestHead let requestFramingMetadata: RequestFramingMetadata @@ -51,14 +51,14 @@ final class RequestBag { task: HTTPClient.Task, redirectHandler: RedirectHandler?, connectionDeadline: NIODeadline, - idleReadTimeout: TimeAmount?, + requestOptions: RequestOptions, delegate: Delegate) throws { self.eventLoopPreference = eventLoopPreference self.task = task self.state = .init(redirectHandler: redirectHandler) self.request = request self.connectionDeadline = connectionDeadline - self.idleReadTimeout = idleReadTimeout + self.requestOptions = requestOptions self.delegate = delegate let (head, metadata) = try request.createRequestHead() diff --git a/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift b/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift index 5fa41d7a5..025c18faf 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ClientChannelHandlerTests.swift @@ -38,7 +38,7 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } @@ -126,7 +126,7 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: .milliseconds(200), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } @@ -207,7 +207,7 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: .milliseconds(200), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } @@ -253,7 +253,7 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { task: .init(eventLoop: embedded.eventLoop, logger: testUtils.logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: .milliseconds(200), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } diff --git a/Tests/AsyncHTTPClientTests/HTTP1ConnectionStateMachineTests.swift b/Tests/AsyncHTTPClientTests/HTTP1ConnectionStateMachineTests.swift index 11fac1d04..d3e6a37a4 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ConnectionStateMachineTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ConnectionStateMachineTests.swift @@ -25,7 +25,7 @@ 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.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false), .wait) XCTAssertEqual(state.writabilityChanged(writable: true), .sendRequestHead(requestHead, startBody: true)) let part0 = IOData.byteBuffer(ByteBuffer(bytes: [0])) @@ -63,7 +63,8 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) - XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false) + XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, startBody: false)) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["content-length": "12"]) XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) @@ -90,7 +91,8 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/", headers: ["connection": "close"]) let metadata = RequestFramingMetadata(connectionClose: true, body: .none) - XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false) + XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, startBody: false)) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) @@ -105,7 +107,8 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) - XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false) + XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, startBody: false)) let responseHead = HTTPResponseHead(version: .http1_0, status: .ok, headers: ["content-length": "4"]) XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) @@ -120,7 +123,8 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) - XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false) + XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, startBody: 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)) @@ -136,7 +140,8 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { XCTAssertEqual(state.writabilityChanged(writable: true), .wait) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) - XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false) + XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, startBody: false)) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok, headers: ["connection": "close"]) XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) @@ -164,7 +169,8 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) - XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false) + XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, startBody: false)) XCTAssertEqual(state.channelInactive(), .failRequest(HTTPClientError.remoteConnectionClosed, .none)) } @@ -175,7 +181,7 @@ 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.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false), .wait) XCTAssertEqual(state.writabilityChanged(writable: true), .sendRequestHead(requestHead, startBody: true)) let part0 = IOData.byteBuffer(ByteBuffer(bytes: [0])) @@ -219,7 +225,7 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { XCTAssertEqual(state.channelActive(isWritable: false), .fireChannelActive) 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.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false), .wait) XCTAssertEqual(state.requestCancelled(closeConnection: false), .failRequest(HTTPClientError.cancelled, .informConnectionIsIdle)) } @@ -228,7 +234,8 @@ class HTTP1ConnectionStateMachineTests: XCTestCase { XCTAssertEqual(state.channelActive(isWritable: true), .fireChannelActive) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) - XCTAssertEqual(state.runNewRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + let newRequestAction = state.runNewRequest(head: requestHead, metadata: metadata, ignoreUncleanSSLShutdown: false) + XCTAssertEqual(newRequestAction, .sendRequestHead(requestHead, startBody: false)) let responseHead = HTTPResponseHead(version: .http1_1, status: .ok) XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false)) XCTAssertEqual(state.channelRead(.body(ByteBuffer(string: "Hello world!\n"))), .wait) diff --git a/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift b/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift index 9881707aa..bb99454d6 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift @@ -151,7 +151,7 @@ class HTTP1ConnectionTests: XCTestCase { task: task, redirectHandler: nil, connectionDeadline: .now() + .seconds(60), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: request) )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } @@ -223,7 +223,7 @@ class HTTP1ConnectionTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } @@ -281,7 +281,7 @@ class HTTP1ConnectionTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } @@ -348,7 +348,7 @@ class HTTP1ConnectionTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } diff --git a/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift index 44f0adc3b..c1fbced67 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2ClientRequestHandlerTests.swift @@ -40,7 +40,7 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { task: .init(eventLoop: embedded.eventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } @@ -128,7 +128,7 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { task: .init(eventLoop: embedded.eventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: .milliseconds(200), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } @@ -204,7 +204,7 @@ class HTTP2ClientRequestHandlerTests: XCTestCase { task: .init(eventLoop: embedded.eventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: .milliseconds(200), + requestOptions: .forTests(idleReadTimeout: .milliseconds(200)), delegate: delegate )) guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } diff --git a/Tests/AsyncHTTPClientTests/HTTP2ConnectionTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ConnectionTests.swift index c4d5f926f..d940f01f6 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2ConnectionTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2ConnectionTests.swift @@ -72,7 +72,7 @@ class HTTP2ConnectionTests: XCTestCase { task: .init(eventLoop: eventLoop, logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .distantFuture, - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) guard let requestBag = maybeRequestBag else { @@ -133,7 +133,7 @@ class HTTP2ConnectionTests: XCTestCase { task: .init(eventLoop: eventLoop, logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .distantFuture, - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) guard let requestBag = maybeRequestBag else { @@ -195,7 +195,7 @@ class HTTP2ConnectionTests: XCTestCase { task: .init(eventLoop: eventLoop, logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .distantFuture, - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) guard let requestBag = maybeRequestBag else { diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index f49bbd0c3..a7b343bc2 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -865,17 +865,21 @@ class HTTPClientTests: XCTestCase { guard !isTestingNIOTS() else { return } let localHTTPBin = HttpBinForSSLUncleanShutdown() - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: HTTPClient.Configuration(certificateVerification: .none, - ignoreUncleanSSLShutdown: true)) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration( + certificateVerification: .none, + ignoreUncleanSSLShutdown: true + ) + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) localHTTPBin.shutdown() } - XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/wrongcontentlength").wait(), "Should fail") { error in - XCTAssertEqual(.invalidEOFState, error as? HTTPParserError) + XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/wrongcontentlength").wait()) { + XCTAssertEqual($0 as? HTTPParserError, .invalidEOFState) } } diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+ManagerTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+ManagerTests.swift index c4669455e..10f5d92d0 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+ManagerTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+ManagerTests.swift @@ -54,7 +54,7 @@ class HTTPConnectionPool_ManagerTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .now() + .seconds(5), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) @@ -110,7 +110,7 @@ class HTTPConnectionPool_ManagerTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .now() + .seconds(5), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+RequestQueueTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+RequestQueueTests.swift index a545e06ea..f8d6044cd 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+RequestQueueTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+RequestQueueTests.swift @@ -107,7 +107,7 @@ private class MockScheduledRequest: HTTPSchedulableRequest { var requestHead: HTTPRequestHead { preconditionFailure("Unimplemented") } var requestFramingMetadata: RequestFramingMetadata { preconditionFailure("Unimplemented") } - var idleReadTimeout: TimeAmount? { preconditionFailure("Unimplemented") } + var requestOptions: RequestOptions { preconditionFailure("Unimplemented") } func willExecuteRequest(_: HTTPRequestExecutor) { preconditionFailure("Unimplemented") diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift index 943b1e489..c0a6e9c87 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPoolTests.swift @@ -58,7 +58,7 @@ class HTTPConnectionPoolTests: XCTestCase { task: .init(eventLoop: eventLoop, logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .distantFuture, - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) @@ -112,7 +112,7 @@ class HTTPConnectionPoolTests: XCTestCase { task: .init(eventLoop: eventLoop, logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .distantFuture, - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) @@ -167,7 +167,7 @@ class HTTPConnectionPoolTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .distantFuture, - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) @@ -221,7 +221,7 @@ class HTTPConnectionPoolTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .now() + .seconds(5), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) @@ -269,7 +269,7 @@ class HTTPConnectionPoolTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .now() + .seconds(5), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) @@ -325,7 +325,7 @@ class HTTPConnectionPoolTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .now() + .seconds(5), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) @@ -371,7 +371,7 @@ class HTTPConnectionPoolTests: XCTestCase { task: .init(eventLoop: eventLoopGroup.next(), logger: .init(label: "test")), redirectHandler: nil, connectionDeadline: .now() + .seconds(5), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: ResponseAccumulator(request: XCTUnwrap(maybeRequest)) )) diff --git a/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests+XCTest.swift index d600092ff..e3b5c72ac 100644 --- a/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests+XCTest.swift @@ -53,6 +53,9 @@ extension HTTPRequestStateMachineTests { ("testCanReadHTTP1_0ResponseWithoutBody", testCanReadHTTP1_0ResponseWithoutBody), ("testCanReadHTTP1_0ResponseWithBody", testCanReadHTTP1_0ResponseWithBody), ("testFailHTTP1_0RequestThatIsStillUploading", testFailHTTP1_0RequestThatIsStillUploading), + ("testFailHTTP1RequestWithoutContentLengthWithNIOSSLErrorUncleanShutdown", testFailHTTP1RequestWithoutContentLengthWithNIOSSLErrorUncleanShutdown), + ("testFailHTTP1RequestWithoutContentLengthWithNIOSSLErrorUncleanShutdownButIgnoreIt", testFailHTTP1RequestWithoutContentLengthWithNIOSSLErrorUncleanShutdownButIgnoreIt), + ("testFailHTTP1RequestWithContentLengthWithNIOSSLErrorUncleanShutdownButIgnoreIt", testFailHTTP1RequestWithContentLengthWithNIOSSLErrorUncleanShutdownButIgnoreIt), ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests.swift b/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests.swift index 3a11cfd43..2e3eccd77 100644 --- a/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests.swift @@ -20,7 +20,7 @@ import XCTest class HTTPRequestStateMachineTests: XCTestCase { func testSimpleGETRequest() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -34,7 +34,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testPOSTRequestWithWriterBackpressure() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -68,7 +68,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testPOSTContentLengthIsTooLong() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -85,7 +85,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testPOSTContentLengthIsTooShort() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -101,7 +101,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testRequestBodyStreamIsCancelledIfServerRespondsWith301() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -126,7 +126,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testRequestBodyStreamIsCancelledIfServerRespondsWith301WhileWriteBackpressure() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -151,7 +151,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testRequestBodyStreamIsContinuedIfServerRespondsWith200() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -171,7 +171,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testRequestBodyStreamIsContinuedIfServerSendHeadWithStatus200() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -192,7 +192,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testRequestIsFailedIfRequestBodySizeIsWrongEvenAfterServerRespondedWith200() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -211,7 +211,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testRequestIsFailedIfRequestBodySizeIsWrongEvenAfterServerSendHeadWithStatus200() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -229,7 +229,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testRequestIsNotSendUntilChannelIsWritable() { - var state = HTTPRequestStateMachine(isChannelWritable: false) + var state = HTTPRequestStateMachine(isChannelWritable: false, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .wait) @@ -245,7 +245,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testConnectionBecomesInactiveWhileWaitingForWritable() { - var state = HTTPRequestStateMachine(isChannelWritable: false) + var state = HTTPRequestStateMachine(isChannelWritable: false, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .wait) @@ -253,7 +253,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testResponseReadingWithBackpressure() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -280,7 +280,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testChannelReadCompleteTriggersButNoBodyDataWasReceivedSoFar() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -307,7 +307,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testResponseReadingWithBackpressureEndOfResponseAllowsReadEventsToTriggerDirectly() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -338,12 +338,12 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testCancellingARequestInStateInitializedKeepsTheConnectionAlive() { - var state = HTTPRequestStateMachine(isChannelWritable: false) + var state = HTTPRequestStateMachine(isChannelWritable: false, ignoreUncleanSSLShutdown: false) XCTAssertEqual(state.requestCancelled(), .failRequest(HTTPClientError.cancelled, .none)) } func testCancellingARequestBeforeBeingSendKeepsTheConnectionAlive() { - var state = HTTPRequestStateMachine(isChannelWritable: false) + var state = HTTPRequestStateMachine(isChannelWritable: false, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .wait) @@ -351,7 +351,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testConnectionBecomesWritableBeforeFirstRequest() { - var state = HTTPRequestStateMachine(isChannelWritable: false) + var state = HTTPRequestStateMachine(isChannelWritable: false, ignoreUncleanSSLShutdown: false) XCTAssertEqual(state.writabilityChanged(writable: true), .wait) // --- sending request @@ -369,7 +369,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testCancellingARequestThatIsSent() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -377,7 +377,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testRemoteSuddenlyClosesTheConnection() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -386,7 +386,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testReadTimeoutLeadsToFailureWithEverythingAfterBeingIgnored() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -403,7 +403,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testResponseWithStatus1XXAreIgnored() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -419,7 +419,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testReadTimeoutThatFiresToLateIsIgnored() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -431,7 +431,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testCancellationThatIsInvokedToLateIsIgnored() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -443,7 +443,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testErrorWhileRunningARequestClosesTheStream() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -453,7 +453,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testCanReadHTTP1_0ResponseWithoutBody() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -469,7 +469,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testCanReadHTTP1_0ResponseWithBody() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") let metadata = RequestFramingMetadata(connectionClose: false, body: .none) XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) @@ -487,7 +487,7 @@ class HTTPRequestStateMachineTests: XCTestCase { } func testFailHTTP1_0RequestThatIsStillUploading() { - var state = HTTPRequestStateMachine(isChannelWritable: true) + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) 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, startBody: true)) @@ -505,6 +505,58 @@ class HTTPRequestStateMachineTests: XCTestCase { XCTAssertEqual(state.channelRead(.end(nil)), .failRequest(HTTPClientError.remoteConnectionClosed, .close)) XCTAssertEqual(state.channelInactive(), .wait) } + + func testFailHTTP1RequestWithoutContentLengthWithNIOSSLErrorUncleanShutdown() { + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: false) + let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") + let metadata = RequestFramingMetadata(connectionClose: false, body: .none) + XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + + 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.demandMoreResponseBodyParts(), .wait) + XCTAssertEqual(state.channelRead(.body(body)), .wait) + XCTAssertEqual(state.errorHappened(NIOSSLError.uncleanShutdown), .failRequest(NIOSSLError.uncleanShutdown, .close)) + XCTAssertEqual(state.channelRead(.end(nil)), .wait) + XCTAssertEqual(state.channelInactive(), .wait) + } + + func testFailHTTP1RequestWithoutContentLengthWithNIOSSLErrorUncleanShutdownButIgnoreIt() { + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: true) + let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") + let metadata = RequestFramingMetadata(connectionClose: false, body: .none) + XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + + 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.demandMoreResponseBodyParts(), .wait) + XCTAssertEqual(state.read(), .read) + XCTAssertEqual(state.channelRead(.body(body)), .wait) + XCTAssertEqual(state.channelReadComplete(), .forwardResponseBodyParts([body])) + XCTAssertEqual(state.errorHappened(NIOSSLError.uncleanShutdown), .wait) + XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, [])) + XCTAssertEqual(state.channelInactive(), .wait) + } + + func testFailHTTP1RequestWithContentLengthWithNIOSSLErrorUncleanShutdownButIgnoreIt() { + var state = HTTPRequestStateMachine(isChannelWritable: true, ignoreUncleanSSLShutdown: true) + let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/") + let metadata = RequestFramingMetadata(connectionClose: false, body: .none) + XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false)) + + 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.demandMoreResponseBodyParts(), .wait) + XCTAssertEqual(state.read(), .read) + XCTAssertEqual(state.channelRead(.body(body)), .wait) + XCTAssertEqual(state.channelReadComplete(), .forwardResponseBodyParts([body])) + XCTAssertEqual(state.errorHappened(NIOSSLError.uncleanShutdown), .wait) + XCTAssertEqual(state.errorHappened(HTTPParserError.invalidEOFState), .failRequest(HTTPParserError.invalidEOFState, .close)) + XCTAssertEqual(state.channelInactive(), .wait) + } } extension HTTPRequestStateMachine.Action: Equatable { diff --git a/Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift b/Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift index 4f3eb9389..9365dd67a 100644 --- a/Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift +++ b/Tests/AsyncHTTPClientTests/Mocks/MockConnectionPool.swift @@ -548,7 +548,7 @@ extension MockConnectionPool { class MockHTTPRequest: HTTPSchedulableRequest { let logger: Logger let connectionDeadline: NIODeadline - let idleReadTimeout: TimeAmount? + let requestOptions: RequestOptions let preferredEventLoop: EventLoop let requiredEventLoop: EventLoop? @@ -556,12 +556,11 @@ class MockHTTPRequest: HTTPSchedulableRequest { init(eventLoop: EventLoop, logger: Logger = Logger(label: "mock"), connectionTimeout: TimeAmount = .seconds(60), - idleReadTimeout: TimeAmount? = nil, requiresEventLoopForChannel: Bool = false) { self.logger = logger self.connectionDeadline = .now() + connectionTimeout - self.idleReadTimeout = idleReadTimeout + self.requestOptions = .forTests() self.preferredEventLoop = eventLoop if requiresEventLoopForChannel { diff --git a/Tests/AsyncHTTPClientTests/RequestBagTests.swift b/Tests/AsyncHTTPClientTests/RequestBagTests.swift index 608e81af5..895061e31 100644 --- a/Tests/AsyncHTTPClientTests/RequestBagTests.swift +++ b/Tests/AsyncHTTPClientTests/RequestBagTests.swift @@ -63,7 +63,7 @@ final class RequestBagTests: XCTestCase { task: .init(eventLoop: embeddedEventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } @@ -172,7 +172,7 @@ final class RequestBagTests: XCTestCase { task: .init(eventLoop: embeddedEventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } @@ -215,7 +215,7 @@ final class RequestBagTests: XCTestCase { task: .init(eventLoop: embeddedEventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } @@ -248,7 +248,7 @@ final class RequestBagTests: XCTestCase { task: .init(eventLoop: embeddedEventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } @@ -290,7 +290,7 @@ final class RequestBagTests: XCTestCase { task: .init(eventLoop: embeddedEventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } @@ -324,7 +324,7 @@ final class RequestBagTests: XCTestCase { task: .init(eventLoop: embeddedEventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } @@ -381,7 +381,7 @@ final class RequestBagTests: XCTestCase { task: .init(eventLoop: embeddedEventLoop, logger: logger), redirectHandler: nil, connectionDeadline: .now() + .seconds(30), - idleReadTimeout: nil, + requestOptions: .forTests(), delegate: delegate )) guard let bag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag.") } @@ -529,3 +529,12 @@ class MockTaskQueuer: HTTPRequestScheduler { self.hitCancelCount += 1 } } + +extension RequestOptions { + static func forTests(idleReadTimeout: TimeAmount? = nil, ignoreUncleanSSLShutdown: Bool = false) -> Self { + RequestOptions( + idleReadTimeout: idleReadTimeout, + ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown + ) + } +}