From 03362bf4a8ded5f7b40e255d78dc579024c58d48 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Mon, 10 Oct 2022 17:56:51 +0100 Subject: [PATCH 1/4] Enable fast failure mode for testing --- .../AsyncAwaitEndToEndTests.swift | 5 +-- .../HTTPClient+SOCKSTests.swift | 20 ++++++----- .../HTTPClientInternalTests.swift | 14 ++++++-- .../HTTPClientTests.swift | 34 +++++++++++-------- .../HTTPConnectionPool+FactoryTests.swift | 9 +++-- 5 files changed, 52 insertions(+), 30 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index e87d3d392..72e18d23e 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -497,8 +497,9 @@ final class AsyncAwaitEndToEndTests: XCTestCase { defer { XCTAssertNoThrow(try serverChannel.close().wait()) } let port = serverChannel.localAddress!.port! - var config = HTTPClient.Configuration() - config.timeout.connect = .seconds(3) + let config = HTTPClient.Configuration() + .enableFastFailureModeForTesting() + let localClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } let request = HTTPClientRequest(url: "https://localhost:\(port)") diff --git a/Tests/AsyncHTTPClientTests/HTTPClient+SOCKSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClient+SOCKSTests.swift index 7cef6b58a..ca6f83c7f 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClient+SOCKSTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClient+SOCKSTests.swift @@ -76,7 +76,9 @@ 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))) + configuration: .init( + proxy: .socksServer(host: "localhost", port: socksBin.port) + ).enableFastFailureModeForTesting()) defer { XCTAssertNoThrow(try localClient.syncShutdown()) @@ -90,8 +92,8 @@ class HTTPClientSOCKSTests: XCTestCase { } func testProxySOCKSBogusAddress() throws { - var config = HTTPClient.Configuration(proxy: .socksServer(host: "127.0..")) - config.networkFrameworkWaitForConnectivity = false + let config = HTTPClient.Configuration(proxy: .socksServer(host: "127.0..")) + .enableFastFailureModeForTesting() let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) @@ -104,8 +106,8 @@ class HTTPClientSOCKSTests: XCTestCase { // there is no socks server, so we should fail func testProxySOCKSFailureNoServer() throws { let localHTTPBin = HTTPBin() - var config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost", port: localHTTPBin.port)) - config.networkFrameworkWaitForConnectivity = false + let config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost", port: localHTTPBin.port)) + .enableFastFailureModeForTesting() let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) @@ -118,8 +120,8 @@ class HTTPClientSOCKSTests: XCTestCase { // speak to a server that doesn't speak SOCKS func testProxySOCKSFailureInvalidServer() throws { - var config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost")) - config.networkFrameworkWaitForConnectivity = false + let config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost")) + .enableFastFailureModeForTesting() let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) @@ -132,8 +134,8 @@ class HTTPClientSOCKSTests: XCTestCase { // test a handshake failure with a misbehaving server func testProxySOCKSMisbehavingServer() throws { let socksBin = try MockSOCKSServer(expectedURL: "/socks/test", expectedResponse: "it works!", misbehave: true) - var config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost", port: socksBin.port)) - config.networkFrameworkWaitForConnectivity = false + let config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost", port: socksBin.port)) + .enableFastFailureModeForTesting() let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index 234185eb6..a67565d1e 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -429,8 +429,8 @@ class HTTPClientInternalTests: XCTestCase { let el2 = elg.next() let httpBin = HTTPBin(.refuse) - var config = HTTPClient.Configuration() - config.networkFrameworkWaitForConnectivity = false + let config = HTTPClient.Configuration() + .enableFastFailureModeForTesting() let client = HTTPClient(eventLoopGroupProvider: .shared(elg), configuration: config) defer { @@ -590,3 +590,13 @@ class HTTPClientInternalTests: XCTestCase { XCTAssert(threadPools.dropFirst().allSatisfy { $0 === firstThreadPool }) } } + + +extension HTTPClient.Configuration { + func enableFastFailureModeForTesting() -> Self { + var copy = self + copy.networkFrameworkWaitForConnectivity = false + copy.connectionPool.retryConnectionEstablishment = false + return copy + } +} diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index d5108bb27..3d21bc883 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -58,6 +58,7 @@ class HTTPClientTests: XCTestCase { }) backgroundLogger.logLevel = .trace self.defaultClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration().enableFastFailureModeForTesting(), backgroundActivityLogger: backgroundLogger) } @@ -778,14 +779,21 @@ class HTTPClientTests: XCTestCase { let res = try localClient.get(url: "http://test/ok").wait() XCTAssertEqual(res.status, .ok) } - + func testProxyPlaintextWithIncorrectlyAuthorization() throws { let localHTTPBin = HTTPBin(proxy: .simulate(authorization: "Basic YWxhZGRpbjpvcGVuc2VzYW1l")) - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(proxy: .server(host: "localhost", - port: localHTTPBin.port, - authorization: .basic(username: "aladdin", - password: "opensesamefoo")))) + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init( + proxy: .server( + host: "localhost", + port: localHTTPBin.port, + authorization: .basic( + username: "aladdin", + password: "opensesamefoo") + ) + ).enableFastFailureModeForTesting() + ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) @@ -1228,11 +1236,9 @@ class HTTPClientTests: XCTestCase { } func testStressGetHttpsSSLError() throws { - var config = HTTPClient.Configuration() - config.networkFrameworkWaitForConnectivity = false - + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: config) + configuration: HTTPClient.Configuration().enableFastFailureModeForTesting()) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } @@ -1292,8 +1298,8 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try serverChannel.close().wait()) } let port = serverChannel.localAddress!.port! - var config = HTTPClient.Configuration() - config.timeout.connect = .seconds(2) + let config = HTTPClient.Configuration().enableFastFailureModeForTesting() + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(port)").wait()) { error in @@ -1332,8 +1338,8 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try serverChannel.close().wait()) } let port = serverChannel.localAddress!.port! - var config = HTTPClient.Configuration() - config.timeout.connect = .seconds(3) + let config = HTTPClient.Configuration().enableFastFailureModeForTesting() + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } diff --git a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+FactoryTests.swift b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+FactoryTests.swift index 3cfb25e03..476584972 100644 --- a/Tests/AsyncHTTPClientTests/HTTPConnectionPool+FactoryTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPConnectionPool+FactoryTests.swift @@ -77,7 +77,8 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { let factory = HTTPConnectionPool.ConnectionFactory( key: .init(request), tlsConfiguration: nil, - clientConfiguration: .init(proxy: .socksServer(host: "127.0.0.1", port: server!.localAddress!.port!)), + clientConfiguration: .init(proxy: .socksServer(host: "127.0.0.1", port: server!.localAddress!.port!)) + .enableFastFailureModeForTesting(), sslContextCache: .init() ) @@ -113,7 +114,8 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { let factory = HTTPConnectionPool.ConnectionFactory( key: .init(request), tlsConfiguration: nil, - clientConfiguration: .init(proxy: .server(host: "127.0.0.1", port: server!.localAddress!.port!)), + clientConfiguration: .init(proxy: .server(host: "127.0.0.1", port: server!.localAddress!.port!)) + .enableFastFailureModeForTesting(), sslContextCache: .init() ) @@ -151,7 +153,8 @@ class HTTPConnectionPool_FactoryTests: XCTestCase { let factory = HTTPConnectionPool.ConnectionFactory( key: .init(request), tlsConfiguration: nil, - clientConfiguration: .init(tlsConfiguration: tlsConfig), + clientConfiguration: .init(tlsConfiguration: tlsConfig) + .enableFastFailureModeForTesting(), sslContextCache: .init() ) From 320f47a01d21074d972716c553525e865f350681 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Mon, 10 Oct 2022 18:33:25 +0100 Subject: [PATCH 2/4] Split-up HTTPClientTests into multiple subclasses --- .../HTTP2ClientTests.swift | 2 +- .../HTTPClientTests+XCTest.swift | 11 +- .../HTTPClientTests.swift | 1449 +++++++++-------- 3 files changed, 738 insertions(+), 724 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift index 7c0e1e56f..7d0aaad0c 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift @@ -287,7 +287,7 @@ class HTTP2ClientTests: XCTestCase { ) XCTAssertThrowsError(try task.futureResult.timeout(after: .seconds(2)).wait()) { - XCTAssertEqual($0 as? HTTPClientError, .cancelled) + XCTAssertEqualTypeAndValue($0, HTTPClientError.cancelled) } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index ef6690c00..532f67147 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -77,13 +77,12 @@ extension HTTPClientTests { ("testWorksWhenServerClosesConnectionAfterReceivingRequest", testWorksWhenServerClosesConnectionAfterReceivingRequest), ("testSubsequentRequestsWorkWithServerSendingConnectionClose", testSubsequentRequestsWorkWithServerSendingConnectionClose), ("testSubsequentRequestsWorkWithServerAlternatingBetweenKeepAliveAndClose", testSubsequentRequestsWorkWithServerAlternatingBetweenKeepAliveAndClose), - ("testStressGetHttps", testStressGetHttps), + ("testStressGetHttpsSSLError", testStressGetHttpsSSLError), ("testSelfSignedCertificateIsRejectedWithCorrectError", testSelfSignedCertificateIsRejectedWithCorrectError), ("testSelfSignedCertificateIsRejectedWithCorrectErrorIfRequestDeadlineIsExceeded", testSelfSignedCertificateIsRejectedWithCorrectErrorIfRequestDeadlineIsExceeded), ("testFailingConnectionIsReleased", testFailingConnectionIsReleased), - ("testResponseDelayGet", testResponseDelayGet), - ("testIdleTimeoutNoReuse", testIdleTimeoutNoReuse), + ("testStressGetClose", testStressGetClose), ("testManyConcurrentRequestsWork", testManyConcurrentRequestsWork), ("testRepeatedRequestsWorkWhenServerAlwaysCloses", testRepeatedRequestsWorkWhenServerAlwaysCloses), @@ -104,7 +103,7 @@ extension HTTPClientTests { ("testUseExistingConnectionOnDifferentEL", testUseExistingConnectionOnDifferentEL), ("testWeRecoverFromServerThatClosesTheConnectionOnUs", testWeRecoverFromServerThatClosesTheConnectionOnUs), ("testPoolClosesIdleConnections", testPoolClosesIdleConnections), - ("testRacePoolIdleConnectionsAndGet", testRacePoolIdleConnectionsAndGet), + ("testAvoidLeakingTLSHandshakeCompletionPromise", testAvoidLeakingTLSHandshakeCompletionPromise), ("testAsyncShutdown", testAsyncShutdown), ("testAsyncShutdownDefaultQueue", testAsyncShutdownDefaultQueue), @@ -126,7 +125,7 @@ extension HTTPClientTests { ("testContentLengthTooLongFails", testContentLengthTooLongFails), ("testContentLengthTooShortFails", testContentLengthTooShortFails), ("testBodyUploadAfterEndFails", testBodyUploadAfterEndFails), - ("testNoBytesSentOverBodyLimit", testNoBytesSentOverBodyLimit), + ("testDoubleError", testDoubleError), ("testSSLHandshakeErrorPropagation", testSSLHandshakeErrorPropagation), ("testSSLHandshakeErrorPropagationDelayedClose", testSSLHandshakeErrorPropagationDelayedClose), @@ -145,7 +144,7 @@ extension HTTPClientTests { ("testCloseWhileBackpressureIsExertedIsFine", testCloseWhileBackpressureIsExertedIsFine), ("testErrorAfterCloseWhileBackpressureExerted", testErrorAfterCloseWhileBackpressureExerted), ("testRequestSpecificTLS", testRequestSpecificTLS), - ("testConnectionPoolSizeConfigValueIsRespected", testConnectionPoolSizeConfigValueIsRespected), + ("testRequestWithHeaderTransferEncodingIdentityDoesNotFail", testRequestWithHeaderTransferEncodingIdentityDoesNotFail), ("testMassiveDownload", testMassiveDownload), ("testShutdownWithFutures", testShutdownWithFutures), diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 3d21bc883..f1b773f65 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -29,26 +29,26 @@ import NIOTestUtils import NIOTransportServices import XCTest -class HTTPClientTests: XCTestCase { +class HTTPClientTestsBaseClass: XCTestCase { typealias Request = HTTPClient.Request - + var clientGroup: EventLoopGroup! var serverGroup: EventLoopGroup! var defaultHTTPBin: HTTPBin! var defaultClient: HTTPClient! var backgroundLogStore: CollectEverythingLogHandler.LogStore! - + var defaultHTTPBinURLPrefix: String { return "http://localhost:\(self.defaultHTTPBin.port)/" } - + override func setUp() { XCTAssertNil(self.clientGroup) XCTAssertNil(self.serverGroup) XCTAssertNil(self.defaultHTTPBin) XCTAssertNil(self.defaultClient) XCTAssertNil(self.backgroundLogStore) - + self.clientGroup = getDefaultEventLoopGroup(numberOfThreads: 1) self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) self.defaultHTTPBin = HTTPBin() @@ -61,29 +61,31 @@ class HTTPClientTests: XCTestCase { configuration: HTTPClient.Configuration().enableFastFailureModeForTesting(), backgroundActivityLogger: backgroundLogger) } - + override func tearDown() { if let defaultClient = self.defaultClient { XCTAssertNoThrow(try defaultClient.syncShutdown()) self.defaultClient = nil } - + XCTAssertNotNil(self.defaultHTTPBin) XCTAssertNoThrow(try self.defaultHTTPBin.shutdown()) self.defaultHTTPBin = nil - + XCTAssertNotNil(self.clientGroup) XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully()) self.clientGroup = nil - + XCTAssertNotNil(self.serverGroup) XCTAssertNoThrow(try self.serverGroup.syncShutdownGracefully()) self.serverGroup = nil - + XCTAssertNotNil(self.backgroundLogStore) self.backgroundLogStore = nil } +} +final class HTTPClientTests: HTTPClientTestsBaseClass { func testRequestURI() throws { let request1 = try Request(url: "https://someserver.com:8888/some/path?foo=bar") XCTAssertEqual(request1.url.host, "someserver.com") @@ -91,30 +93,30 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(request1.url.query!, "foo=bar") XCTAssertEqual(request1.port, 8888) XCTAssertTrue(request1.useTLS) - + let request2 = try Request(url: "https://someserver.com") XCTAssertEqual(request2.url.path, "") - + let request3 = try Request(url: "unix:///tmp/file") XCTAssertNil(request3.url.host) XCTAssertEqual(request3.host, "") XCTAssertEqual(request3.url.path, "/tmp/file") XCTAssertEqual(request3.port, 80) XCTAssertFalse(request3.useTLS) - + let request4 = try Request(url: "http+unix://%2Ftmp%2Ffile/file/path") XCTAssertEqual(request4.host, "") XCTAssertEqual(request4.url.host, "/tmp/file") XCTAssertEqual(request4.url.path, "/file/path") XCTAssertFalse(request4.useTLS) - + let request5 = try Request(url: "https+unix://%2Ftmp%2Ffile/file/path") XCTAssertEqual(request5.host, "") XCTAssertEqual(request5.url.host, "/tmp/file") XCTAssertEqual(request5.url.path, "/file/path") XCTAssertTrue(request5.useTLS) } - + func testBadRequestURI() throws { XCTAssertThrowsError(try Request(url: "some/path"), "should throw") { error in XCTAssertEqual(error as! HTTPClientError, HTTPClientError.emptyScheme) @@ -129,14 +131,14 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(error as! HTTPClientError, HTTPClientError.missingSocketPath) } } - + func testSchemaCasing() throws { XCTAssertNoThrow(try Request(url: "hTTpS://someserver.com:8888/some/path?foo=bar")) XCTAssertNoThrow(try Request(url: "uNIx:///some/path")) XCTAssertNoThrow(try Request(url: "hTtP+uNIx://%2Fsome%2Fpath/")) XCTAssertNoThrow(try Request(url: "hTtPS+uNIx://%2Fsome%2Fpath/")) } - + func testURLSocketPathInitializers() throws { let url1 = URL(httpURLWithSocketPath: "/tmp/file") XCTAssertNotNil(url1) @@ -146,7 +148,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(url.path, "/") XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile/") } - + let url2 = URL(httpURLWithSocketPath: "/tmp/file", uri: "/file/path") XCTAssertNotNil(url2) if let url = url2 { @@ -155,7 +157,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile/file/path") } - + let url3 = URL(httpURLWithSocketPath: "/tmp/file", uri: "file/path") XCTAssertNotNil(url3) if let url = url3 { @@ -164,7 +166,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile/file/path") } - + let url4 = URL(httpURLWithSocketPath: "/tmp/file with spacesと漢字", uri: "file/path") XCTAssertNotNil(url4) if let url = url4 { @@ -173,7 +175,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path") } - + let url5 = URL(httpsURLWithSocketPath: "/tmp/file") XCTAssertNotNil(url5) if let url = url5 { @@ -182,7 +184,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(url.path, "/") XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile/") } - + let url6 = URL(httpsURLWithSocketPath: "/tmp/file", uri: "/file/path") XCTAssertNotNil(url6) if let url = url6 { @@ -191,7 +193,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile/file/path") } - + let url7 = URL(httpsURLWithSocketPath: "/tmp/file", uri: "file/path") XCTAssertNotNil(url7) if let url = url7 { @@ -200,7 +202,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile/file/path") } - + let url8 = URL(httpsURLWithSocketPath: "/tmp/file with spacesと漢字", uri: "file/path") XCTAssertNotNil(url8) if let url = url8 { @@ -209,14 +211,14 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path") } - + let url9 = URL(httpURLWithSocketPath: "/tmp/file", uri: " ") XCTAssertNil(url9) - + let url10 = URL(httpsURLWithSocketPath: "/tmp/file", uri: " ") XCTAssertNil(url10) } - + func testBadUnixWithBaseURL() { let badUnixBaseURL = URL(string: "/foo", relativeTo: URL(string: "unix:")!)! XCTAssertEqual(badUnixBaseURL.baseURL?.path, "") @@ -224,7 +226,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(error as! HTTPClientError, HTTPClientError.missingSocketPath) } } - + func testConvenienceExecuteMethods() throws { XCTAssertEqual(["GET"[...]], try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) @@ -241,14 +243,14 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(["CHECKOUT"[...]], try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) } - + func testConvenienceExecuteMethodsOverSocket() throws { XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) defer { XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertEqual(["GET"[...]], try self.defaultClient.execute(socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) XCTAssertEqual(["GET"[...]], @@ -257,7 +259,7 @@ class HTTPClientTests: XCTestCase { try self.defaultClient.execute(.POST, socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) }) } - + func testConvenienceExecuteMethodsOverSecureSocket() throws { XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true, compress: false), bindTarget: .unixDomainSocket(path)) @@ -267,7 +269,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertEqual(["GET"[...]], try localClient.execute(secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) XCTAssertEqual(["GET"[...]], @@ -276,51 +278,51 @@ class HTTPClientTests: XCTestCase { try localClient.execute(.POST, secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) }) } - + func testGet() throws { let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait() XCTAssertEqual(.ok, response.status) } - + func testGetWithDifferentEventLoopBackpressure() throws { let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "events/10/1") let delegate = TestHTTPDelegate(backpressureEventLoop: self.serverGroup.next()) let task = self.defaultClient.execute(request: request, delegate: delegate) try task.wait() } - + func testPost() throws { let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .string("1234")).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("1234", data.data) } - + func testPostWithGenericBody() throws { let bodyData = Array("hello, world!").lazy.map { $0.uppercased().first!.asciiValue! } let erasedData = AnyRandomAccessCollection(bodyData) - + let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .bytes(erasedData)).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("HELLO, WORLD!", data.data) } - + func testPostWithFoundationDataBody() throws { let bodyData = Data("hello, world!".utf8) - + let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .data(bodyData)).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("hello, world!", data.data) } - + func testGetHttps() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), @@ -329,11 +331,11 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + let response = try localClient.get(url: "https://localhost:\(localHTTPBin.port)/get").wait() XCTAssertEqual(.ok, response.status) } - + func testGetHttpsWithIP() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), @@ -342,11 +344,11 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + let response = try localClient.get(url: "https://127.0.0.1:\(localHTTPBin.port)/get").wait() XCTAssertEqual(.ok, response.status) } - + func testGetHTTPSWorksOnMTELGWithIP() throws { // Same test as above but this one will use NIO on Sockets even on Apple platforms, just to make sure // this works. @@ -361,11 +363,11 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + let response = try localClient.get(url: "https://127.0.0.1:\(localHTTPBin.port)/get").wait() XCTAssertEqual(.ok, response.status) } - + func testGetHttpsWithIPv6() throws { try XCTSkipUnless(canBindIPv6Loopback, "Requires IPv6") let localHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .localhostIPv6RandomPort) @@ -379,7 +381,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(response = try localClient.get(url: "https://[::1]:\(localHTTPBin.port)/get").wait()) XCTAssertEqual(.ok, response?.status) } - + func testGetHTTPSWorksOnMTELGWithIPv6() throws { try XCTSkipUnless(canBindIPv6Loopback, "Requires IPv6") // Same test as above but this one will use NIO on Sockets even on Apple platforms, just to make sure @@ -399,7 +401,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(response = try localClient.get(url: "https://[::1]:\(localHTTPBin.port)/get").wait()) XCTAssertEqual(.ok, response?.status) } - + func testPostHttps() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), @@ -408,33 +410,33 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + let request = try Request(url: "https://localhost:\(localHTTPBin.port)/post", method: .POST, body: .string("1234")) - + let response = try localClient.execute(request: request).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("1234", data.data) } - + func testHttpRedirect() throws { let httpsBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try httpsBin.shutdown()) } - + var response = try localClient.get(url: self.defaultHTTPBinURLPrefix + "redirect/302").wait() XCTAssertEqual(response.status, .ok) - + response = try localClient.get(url: self.defaultHTTPBinURLPrefix + "redirect/https?port=\(httpsBin.port)").wait() XCTAssertEqual(response.status, .ok) - + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { httpSocketPath in XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { httpsSocketPath in let socketHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(httpSocketPath)) @@ -443,96 +445,96 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try socketHTTPBin.shutdown()) XCTAssertNoThrow(try socketHTTPSBin.shutdown()) } - + // From HTTP or HTTPS to HTTP+UNIX should fail to redirect var targetURL = "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" var request = try Request(url: self.defaultHTTPBinURLPrefix + "redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + var response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .found) XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - + request = try Request(url: "https://localhost:\(httpsBin.port)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .found) XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - + // From HTTP or HTTPS to HTTPS+UNIX should also fail to redirect targetURL = "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: self.defaultHTTPBinURLPrefix + "redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .found) XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - + request = try Request(url: "https://localhost:\(httpsBin.port)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .found) XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - + // ... while HTTP+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed targetURL = self.defaultHTTPBinURLPrefix + "ok" request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "https://localhost:\(httpsBin.port)/ok" request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + // ... and HTTPS+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed targetURL = self.defaultHTTPBinURLPrefix + "ok" request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "https://localhost:\(httpsBin.port)/ok" request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) }) }) } - + func testHttpHostRedirect() { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + let url = self.defaultHTTPBinURLPrefix + "redirect/loopback?port=\(self.defaultHTTPBin.port)" var maybeResponse: HTTPClient.Response? XCTAssertNoThrow(maybeResponse = try localClient.get(url: url).wait()) @@ -543,20 +545,20 @@ class HTTPClientTests: XCTestCase { let hostName = try? JSONDecoder().decode(RequestInfo.self, from: body).data XCTAssertEqual("127.0.0.1:\(self.defaultHTTPBin.port)", hostName) } - + func testPercentEncoded() throws { let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "percent%20encoded").wait() XCTAssertEqual(.ok, response.status) } - + func testPercentEncodedBackslash() throws { let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "percent%2Fencoded/hello").wait() XCTAssertEqual(.ok, response.status) } - + func testMultipleContentLengthHeaders() throws { let body = ByteBuffer(string: "hello world!") - + var headers = HTTPHeaders() headers.add(name: "Content-Length", value: "12") let request = try Request(url: self.defaultHTTPBinURLPrefix + "post", method: .POST, headers: headers, body: .byteBuffer(body)) @@ -564,95 +566,95 @@ class HTTPClientTests: XCTestCase { // if the library adds another content length header we'll get a bad request error. XCTAssertEqual(.ok, response.status) } - + func testStreaming() throws { var request = try Request(url: self.defaultHTTPBinURLPrefix + "events/10/1") request.headers.add(name: "Accept", value: "text/event-stream") - + let delegate = CountingDelegate() let count = try self.defaultClient.execute(request: request, delegate: delegate).wait() - + XCTAssertEqual(10, count) } - + func testFileDownload() throws { var request = try Request(url: self.defaultHTTPBinURLPrefix + "events/10/content-length") request.headers.add(name: "Accept", value: "text/event-stream") - + let progress = - try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in - let delegate = try FileDownloadDelegate(path: path) - - let progress = try self.defaultClient.execute( - request: request, - delegate: delegate - ) + try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in + let delegate = try FileDownloadDelegate(path: path) + + let progress = try self.defaultClient.execute( + request: request, + delegate: delegate + ) .wait() - - try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path)) - - return progress - } - + + try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path)) + + return progress + } + XCTAssertEqual(50, progress.totalBytes) XCTAssertEqual(50, progress.receivedBytes) } - + func testFileDownloadError() throws { var request = try Request(url: self.defaultHTTPBinURLPrefix + "not-found") request.headers.add(name: "Accept", value: "text/event-stream") - + let progress = - try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in - let delegate = try FileDownloadDelegate(path: path, reportHead: { - XCTAssertEqual($0.status, .notFound) - }) - - let progress = try self.defaultClient.execute( - request: request, - delegate: delegate - ) + try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in + let delegate = try FileDownloadDelegate(path: path, reportHead: { + XCTAssertEqual($0.status, .notFound) + }) + + let progress = try self.defaultClient.execute( + request: request, + delegate: delegate + ) .wait() - - XCTAssertFalse(TemporaryFileHelpers.fileExists(path: path)) - - return progress - } - + + XCTAssertFalse(TemporaryFileHelpers.fileExists(path: path)) + + return progress + } + XCTAssertEqual(nil, progress.totalBytes) XCTAssertEqual(0, progress.receivedBytes) } - + func testRemoteClose() { XCTAssertThrowsError(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "close").wait()) { XCTAssertEqual($0 as? HTTPClientError, .remoteConnectionClosed) } } - + func testReadTimeout() { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(read: .milliseconds(150)))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + XCTAssertThrowsError(try localClient.get(url: self.defaultHTTPBinURLPrefix + "wait").wait()) { XCTAssertEqual($0 as? HTTPClientError, .readTimeout) } } - + func testConnectTimeout() throws { - #if os(Linux) +#if os(Linux) // 198.51.100.254 is reserved for documentation only and therefore should not accept any TCP connection let url = "http://198.51.100.254/get" - #else +#else // on macOS we can use the TCP backlog behaviour when the queue is full to simulate a non reachable server. // this makes this test a bit more stable if `198.51.100.254` actually responds to connection attempt. // The backlog behaviour on Linux can not be used to simulate a non-reachable server. // Linux sends a `SYN/ACK` back even if the `backlog` queue is full as it has two queues. // The second queue is not limit by `ChannelOptions.backlog` but by `/proc/sys/net/ipv4/tcp_max_syn_backlog`. - + let serverChannel = try ServerBootstrap(group: self.serverGroup) .serverChannelOption(ChannelOptions.backlog, value: 1) .serverChannelOption(ChannelOptions.autoRead, value: false) @@ -669,42 +671,42 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try firstClientChannel.close().wait()) } let url = "http://localhost:\(port)/get" - #endif - +#endif + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(timeout: .init(connect: .milliseconds(100), read: .milliseconds(150)))) - + defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + XCTAssertThrowsError(try httpClient.get(url: url).wait()) { XCTAssertEqualTypeAndValue($0, HTTPClientError.connectTimeout) } } - + func testDeadline() { XCTAssertThrowsError(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "wait", deadline: .now() + .milliseconds(150)).wait()) { XCTAssertEqual($0 as? HTTPClientError, .deadlineExceeded) } } - + func testCancel() throws { let queue = DispatchQueue(label: "nio-test") let request = try Request(url: self.defaultHTTPBinURLPrefix + "wait") let task = self.defaultClient.execute(request: request, delegate: TestHTTPDelegate()) - + queue.asyncAfter(deadline: .now() + .milliseconds(100)) { task.cancel() } - + XCTAssertThrowsError(try task.wait(), "Should fail") { error in guard case let error = error as? HTTPClientError, error == .cancelled else { return XCTFail("Should fail with cancelled") } } } - + func testStressCancel() throws { let request = try Request(url: self.defaultHTTPBinURLPrefix + "wait", method: .GET) let tasks = (1...100).map { _ -> HTTPClient.Task in @@ -712,7 +714,7 @@ class HTTPClientTests: XCTestCase { task.cancel() return task } - + for task in tasks { switch (Result { try task.futureResult.timeout(after: .seconds(10)).wait() }) { case .success: @@ -726,15 +728,15 @@ class HTTPClientTests: XCTestCase { } } } - + func testHTTPClientAuthorization() { var authorization = HTTPClient.Authorization.basic(username: "aladdin", password: "opensesame") XCTAssertEqual(authorization.headerValue, "Basic YWxhZGRpbjpvcGVuc2VzYW1l") - + authorization = HTTPClient.Authorization.bearer(tokens: "mF_9.B5f-4.1JqM") XCTAssertEqual(authorization.headerValue, "Bearer mF_9.B5f-4.1JqM") } - + func testProxyPlaintext() throws { let localHTTPBin = HTTPBin(proxy: .simulate(authorization: nil)) let localClient = HTTPClient( @@ -748,7 +750,7 @@ class HTTPClientTests: XCTestCase { let res = try localClient.get(url: "http://test/ok").wait() XCTAssertEqual(res.status, .ok) } - + func testProxyTLS() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true), proxy: .simulate(authorization: nil)) let localClient = HTTPClient( @@ -765,7 +767,7 @@ class HTTPClientTests: XCTestCase { let res = try localClient.get(url: "https://test/ok").wait() XCTAssertEqual(res.status, .ok) } - + func testProxyPlaintextWithCorrectlyAuthorization() throws { let localHTTPBin = HTTPBin(proxy: .simulate(authorization: "Basic YWxhZGRpbjpvcGVuc2VzYW1l")) let localClient = HTTPClient( @@ -804,7 +806,7 @@ class HTTPClientTests: XCTestCase { } } } - + func testUploadStreaming() throws { let body: HTTPClient.Body = .stream(length: 8) { writer in let buffer = ByteBuffer(string: "1234") @@ -813,80 +815,80 @@ class HTTPClientTests: XCTestCase { return writer.write(.byteBuffer(buffer)) } } - + let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: body).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("12344321", data.data) } - + func testEventLoopArgument() throws { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(redirectConfiguration: .follow(max: 10, allowCycles: true))) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + class EventLoopValidatingDelegate: HTTPClientResponseDelegate { typealias Response = Bool - + let eventLoop: EventLoop var result = false - + init(eventLoop: EventLoop) { self.eventLoop = eventLoop } - + func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) -> EventLoopFuture { self.result = task.eventLoop === self.eventLoop return task.eventLoop.makeSucceededFuture(()) } - + func didFinishRequest(task: HTTPClient.Task) throws -> Bool { return self.result } } - + let eventLoop = self.clientGroup.next() let delegate = EventLoopValidatingDelegate(eventLoop: eventLoop) var request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get") var response = try localClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: eventLoop)).wait() XCTAssertEqual(true, response) - + // redirect request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "redirect/302") response = try localClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: eventLoop)).wait() XCTAssertEqual(true, response) } - + func testDecompression() throws { let localHTTPBin = HTTPBin(.http1_1(compress: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(decompression: .enabled(limit: .none))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + var body = "" for _ in 1...1000 { body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." } - + for algorithm in [nil, "gzip", "deflate"] { var request = try HTTPClient.Request(url: "http://localhost:\(localHTTPBin.port)/post", method: .POST) request.body = .string(body) if let algorithm = algorithm { request.headers.add(name: "Accept-Encoding", value: algorithm) } - + let response = try localClient.execute(request: request).wait() let bytes = response.body!.getData(at: 0, length: response.body!.readableBytes)! let data = try JSONDecoder().decode(RequestInfo.self, from: bytes) - + XCTAssertEqual(.ok, response.status) XCTAssertGreaterThan(body.count, response.headers["Content-Length"].first.flatMap { Int($0) }!) if let algorithm = algorithm { @@ -897,7 +899,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(body, data.data) } } - + func testDecompressionHTTP2() throws { let localHTTPBin = HTTPBin(.http2(compress: true)) let localClient = HTTPClient( @@ -907,28 +909,28 @@ class HTTPClientTests: XCTestCase { decompression: .enabled(limit: .none) ) ) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + var body = "" for _ in 1...1000 { body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." } - + for algorithm: String? in [nil] { var request = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/post", method: .POST) request.body = .string(body) if let algorithm = algorithm { request.headers.add(name: "Accept-Encoding", value: algorithm) } - + let response = try localClient.execute(request: request).wait() var responseBody = try XCTUnwrap(response.body) let data = try responseBody.readJSONDecodable(RequestInfo.self, length: responseBody.readableBytes) - + XCTAssertEqual(.ok, response.status) let contentLength = try XCTUnwrap(response.headers["Content-Length"].first.flatMap { Int($0) }) XCTAssertGreaterThan(body.count, contentLength) @@ -940,55 +942,55 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(body, data?.data) } } - + func testDecompressionLimit() throws { let localHTTPBin = HTTPBin(.http1_1(compress: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(decompression: .enabled(limit: .ratio(1)))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + var request = try HTTPClient.Request(url: "http://localhost:\(localHTTPBin.port)/post", method: .POST) request.body = .byteBuffer(ByteBuffer(bytes: [120, 156, 75, 76, 28, 5, 200, 0, 0, 248, 66, 103, 17])) request.headers.add(name: "Accept-Encoding", value: "deflate") - + XCTAssertThrowsError(try localClient.execute(request: request).wait()) { XCTAssertEqual($0 as? NIOHTTPDecompression.DecompressionError, .limit) } } - + func testLoopDetectionRedirectLimit() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 5, allowCycles: false))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectCycleDetected) } } - + func testCountRedirectLimit() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/redirect/infinite1").timeout(after: .seconds(10)).wait()) { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectLimitReached) } } - + func testRedirectToTheInitialURLDoesThrowOnFirstRedirect() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) defer { XCTAssertNoThrow(try localHTTPBin.shutdown()) } @@ -1000,7 +1002,7 @@ class HTTPClientTests: XCTestCase { ) ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + var maybeRequest: HTTPClient.Request? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( url: "https://localhost:\(localHTTPBin.port)/redirect/target", @@ -1010,22 +1012,22 @@ class HTTPClientTests: XCTestCase { ] )) guard let request = maybeRequest else { return } - + XCTAssertThrowsError( try localClient.execute(request: request).timeout(after: .seconds(10)).wait() ) { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectCycleDetected) } } - + func testMultipleConcurrentRequests() throws { let numberOfRequestsPerThread = 1000 let numberOfParallelWorkers = 5 - + final class HTTPServer: ChannelInboundHandler { typealias InboundIn = HTTPServerRequestPart typealias OutboundOut = HTTPServerResponsePart - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { if case .end = self.unwrapInboundIn(data) { let responseHead = HTTPServerResponsePart.head(.init(version: .init(major: 1, minor: 1), @@ -1035,12 +1037,12 @@ class HTTPClientTests: XCTestCase { } } } - + let group = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } - + var server: Channel? XCTAssertNoThrow(server = try ServerBootstrap(group: group) .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1) @@ -1057,7 +1059,7 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try server?.close().wait()) } - + let url = "http://127.0.0.1:\(server?.localAddress?.port ?? -1)/hello" let g = DispatchGroup() for workerID in 0..]() - for _ in 1...requestCount { - let req = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, headers: ["X-internal-delay": "100"]) - futureResults.append(localClient.execute(request: req)) - } - XCTAssertNoThrow(try EventLoopFuture.andAllSucceed(futureResults, on: eventLoop).wait()) - } - + func testStressGetHttpsSSLError() throws { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), @@ -1242,14 +1225,14 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + let request = try Request(url: "https://localhost:\(self.defaultHTTPBin.port)/wait", method: .GET) let tasks = (1...100).map { _ -> HTTPClient.Task in localClient.execute(request: request, delegate: TestHTTPDelegate()) } - + let results = try EventLoopFuture.whenAllComplete(tasks.map { $0.futureResult }, on: localClient.eventLoopGroup.next()).wait() - + for result in results { switch result { case .success: @@ -1257,7 +1240,7 @@ class HTTPClientTests: XCTestCase { continue case .failure(let error): if isTestingNIOTS() { - #if canImport(Network) +#if canImport(Network) guard let clientError = error as? HTTPClient.NWTLSError else { XCTFail("Unexpected error: \(error)") continue @@ -1266,9 +1249,9 @@ class HTTPClientTests: XCTestCase { // that the bytes "HTTP/1.1" aren't the start of a valid TLS packet, we can also get // errSSLPeerProtocolVersion because the first bytes contain the version. XCTAssert(clientError.status == errSSLHandshakeFail || - clientError.status == errSSLPeerProtocolVersion, - "unexpected NWTLSError with status \(clientError.status)") - #endif + clientError.status == errSSLPeerProtocolVersion, + "unexpected NWTLSError with status \(clientError.status)") +#endif } else { guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else { XCTFail("Unexpected error: \(error)") @@ -1278,7 +1261,7 @@ class HTTPClientTests: XCTestCase { } } } - + func testSelfSignedCertificateIsRejectedWithCorrectError() throws { /// key + cert was created with the follwing command: /// openssl req -x509 -newkey rsa:4096 -keyout self_signed_key.pem -out self_signed_cert.pem -sha256 -days 99999 -nodes -subj '/CN=localhost' @@ -1289,7 +1272,7 @@ class HTTPClientTests: XCTestCase { privateKey: .file(keyPath) ) let sslContext = try NIOSSLContext(configuration: configuration) - + let server = ServerBootstrap(group: serverGroup) .childChannelInitializer { channel in channel.pipeline.addHandler(NIOSSLServerHandler(context: sslContext)) @@ -1297,28 +1280,28 @@ class HTTPClientTests: XCTestCase { let serverChannel = try server.bind(host: "localhost", port: 0).wait() defer { XCTAssertNoThrow(try serverChannel.close().wait()) } let port = serverChannel.localAddress!.port! - + let config = HTTPClient.Configuration().enableFastFailureModeForTesting() let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(port)").wait()) { error in - #if canImport(Network) +#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)") return } XCTAssertEqual(nwTLSError.status, errSSLBadCert, "unexpected tls error: \(nwTLSError)") - #else +#else guard let sslError = error as? NIOSSLError, case .handshakeFailed(.sslError) = sslError else { XCTFail("unexpected error \(error)") return } - #endif +#endif } } - + func testSelfSignedCertificateIsRejectedWithCorrectErrorIfRequestDeadlineIsExceeded() throws { /// key + cert was created with the follwing command: /// openssl req -x509 -newkey rsa:4096 -keyout self_signed_key.pem -out self_signed_cert.pem -sha256 -days 99999 -nodes -subj '/CN=localhost' @@ -1329,7 +1312,7 @@ class HTTPClientTests: XCTestCase { privateKey: .file(keyPath) ) let sslContext = try NIOSSLContext(configuration: configuration) - + let server = ServerBootstrap(group: serverGroup) .childChannelInitializer { channel in channel.pipeline.addHandler(NIOSSLServerHandler(context: sslContext)) @@ -1337,29 +1320,29 @@ class HTTPClientTests: XCTestCase { let serverChannel = try server.bind(host: "localhost", port: 0).wait() defer { XCTAssertNoThrow(try serverChannel.close().wait()) } let port = serverChannel.localAddress!.port! - + let config = HTTPClient.Configuration().enableFastFailureModeForTesting() let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(port)", deadline: .now() + .seconds(2)).wait()) { error in - #if canImport(Network) +#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)") return } XCTAssertEqual(nwTLSError.status, errSSLBadCert, "unexpected tls error: \(nwTLSError)") - #else +#else guard let sslError = error as? NIOSSLError, case .handshakeFailed(.sslError) = sslError else { XCTFail("unexpected error \(error)") return } - #endif +#endif } } - + func testFailingConnectionIsReleased() { let localHTTPBin = HTTPBin(.refuse) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) @@ -1377,26 +1360,7 @@ class HTTPClientTests: XCTestCase { } } } - - func testResponseDelayGet() throws { - let req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", - method: .GET, - headers: ["X-internal-delay": "2000"], - body: nil) - let start = NIODeadline.now() - let response = try self.defaultClient.execute(request: req).wait() - XCTAssertGreaterThanOrEqual(.now() - start, .milliseconds(1_900 /* 1.9 seconds */ )) - XCTAssertEqual(response.status, .ok) - } - - func testIdleTimeoutNoReuse() throws { - var req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .GET) - XCTAssertNoThrow(try self.defaultClient.execute(request: req, deadline: .now() + .seconds(2)).wait()) - req.headers.add(name: "X-internal-delay", value: "2500") - try self.defaultClient.eventLoopGroup.next().scheduleTask(in: .milliseconds(250)) {}.futureResult.wait() - XCTAssertNoThrow(try self.defaultClient.execute(request: req).timeout(after: .seconds(10)).wait()) - } - + func testStressGetClose() throws { let eventLoop = self.defaultClient.eventLoopGroup.next() let requestCount = 200 @@ -1410,24 +1374,24 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try EventLoopFuture.andAllComplete(futureResults, on: eventLoop) .timeout(after: .seconds(10)).wait()) } - + func testManyConcurrentRequestsWork() { let numberOfWorkers = 20 let numberOfRequestsPerWorkers = 20 let allWorkersReady = DispatchSemaphore(value: 0) let allWorkersGo = DispatchSemaphore(value: 0) let allDone = DispatchGroup() - + let url = self.defaultHTTPBinURLPrefix + "get" XCTAssertEqual(.ok, try self.defaultClient.get(url: url).wait().status) - + for w in 0..]() for i in 1...100 { let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .GET, headers: ["X-internal-delay": "10"]) @@ -1631,9 +1595,9 @@ class HTTPClientTests: XCTestCase { } futureResults.append(client.execute(request: request, eventLoop: preference)) } - + let results = try EventLoopFuture.whenAllComplete(futureResults, on: elg.next()).wait() - + for result in results { switch result { case .success: @@ -1643,7 +1607,7 @@ class HTTPClientTests: XCTestCase { } } } - + func testMakeSecondRequestDuringCancelledCallout() { let el = self.clientGroup.next() let web = NIOHTTP1TestServer(group: self.serverGroup.next()) @@ -1651,7 +1615,7 @@ class HTTPClientTests: XCTestCase { // This will throw as we've started the request but haven't fulfilled it. XCTAssertThrowsError(try web.stop()) } - + let url = "http://127.0.0.1:\(web.serverPort)" let localClient = HTTPClient(eventLoopGroupProvider: .shared(el)) defer { @@ -1659,7 +1623,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(.alreadyShutdown, error as? HTTPClientError) } } - + let seenError = DispatchGroup() seenError.enter() var maybeSecondRequest: EventLoopFuture? @@ -1672,23 +1636,23 @@ class HTTPClientTests: XCTestCase { } return secondRequest }.wait()) - + guard let secondRequest = maybeSecondRequest else { XCTFail("couldn't get request future") return } - + // Let's pull out the request .head so we know the request has started (but nothing else) XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) - + XCTAssertNoThrow(try localClient.syncShutdown()) - + seenError.wait() XCTAssertThrowsError(try secondRequest.wait()) { error in XCTAssertEqual(.alreadyShutdown, error as? HTTPClientError) } } - + func testMakeSecondRequestDuringSuccessCallout() { let el = self.clientGroup.next() let url = "http://127.0.0.1:\(self.defaultHTTPBin.port)/get" @@ -1696,50 +1660,50 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + XCTAssertEqual(.ok, try el.flatSubmit { () -> EventLoopFuture in - localClient.get(url: url).flatMap { firstResponse in - XCTAssertEqual(.ok, firstResponse.status) - return localClient.get(url: url) // <== interesting bit here - } - }.wait().status) + localClient.get(url: url).flatMap { firstResponse in + XCTAssertEqual(.ok, firstResponse.status) + return localClient.get(url: url) // <== interesting bit here + } + }.wait().status) } - + func testMakeSecondRequestWhilstFirstIsOngoing() { let web = NIOHTTP1TestServer(group: self.serverGroup) defer { XCTAssertNoThrow(try web.stop()) } - + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + let url = "http://127.0.0.1:\(web.serverPort)" let firstRequest = client.get(url: url) - + XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) // first request: .head - + // Now, the first request is ongoing but not complete, let's start a second one let secondRequest = client.get(url: url) XCTAssertEqual(.end(nil), try web.readInbound()) // first request: .end - + XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) - + XCTAssertEqual(.ok, try firstRequest.wait().status) - + // Okay, first request done successfully, let's do the second one too. XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) // first request: .head XCTAssertEqual(.end(nil), try web.readInbound()) // first request: .end - + XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .created)))) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) XCTAssertEqual(.created, try secondRequest.wait().status) } - + func testUDSBasic() { // This tests just connecting to a URL where the whole URL is the UNIX domain socket path like // unix:///this/is/my/socket.sock @@ -1754,7 +1718,7 @@ class HTTPClientTests: XCTestCase { try self.defaultClient.get(url: target).wait().headers[canonicalForm: "X-Is-This-Slash"]) }) } - + func testUDSSocketAndPath() { // Here, we're testing a URL that's encoding two different paths: // @@ -1774,7 +1738,7 @@ class HTTPClientTests: XCTestCase { try self.defaultClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"]) }) } - + func testHTTPPlusUNIX() { // Here, we're testing a URL where the UNIX domain socket is encoded as the host name XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in @@ -1791,7 +1755,7 @@ class HTTPClientTests: XCTestCase { try self.defaultClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"]) }) } - + func testHTTPSPlusUNIX() { // Here, we're testing a URL where the UNIX domain socket is encoded as the host name XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in @@ -1811,7 +1775,7 @@ class HTTPClientTests: XCTestCase { try localClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"]) }) } - + func testUseExistingConnectionOnDifferentEL() throws { let threadCount = 16 let elg = getDefaultEventLoopGroup(numberOfThreads: threadCount) @@ -1820,11 +1784,11 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try elg.syncShutdownGracefully()) } - + let eventLoops = (1...threadCount).map { _ in elg.next() } let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get") let closingRequest = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", headers: ["Connection": "close"]) - + for (index, el) in eventLoops.enumerated() { if index.isMultiple(of: 2) { XCTAssertNoThrow(try localClient.execute(request: request, eventLoop: .delegateAndChannel(on: el)).wait()) @@ -1834,27 +1798,27 @@ class HTTPClientTests: XCTestCase { } } } - + func testWeRecoverFromServerThatClosesTheConnectionOnUs() { final class ServerThatAcceptsThenRejects: ChannelInboundHandler { typealias InboundIn = HTTPServerRequestPart typealias OutboundOut = HTTPServerResponsePart - + let requestNumber: ManagedAtomic let connectionNumber: ManagedAtomic - + init(requestNumber: ManagedAtomic, connectionNumber: ManagedAtomic) { self.requestNumber = requestNumber self.connectionNumber = connectionNumber } - + func channelActive(context: ChannelHandlerContext) { _ = self.connectionNumber.loadThenWrappingIncrement(ordering: .relaxed) } - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { let req = self.unwrapInboundIn(data) - + switch req { case .head, .body: () @@ -1873,7 +1837,7 @@ class HTTPClientTests: XCTestCase { } } } - + let requestNumber = ManagedAtomic(0) let connectionNumber = ManagedAtomic(0) let sharedStateServerHandler = ServerThatAcceptsThenRejects(requestNumber: requestNumber, @@ -1897,13 +1861,13 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try server.close().wait()) } - + let url = "http://127.0.0.1:\(server.localAddress!.port!)" let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + XCTAssertEqual(0, sharedStateServerHandler.connectionNumber.load(ordering: .relaxed)) XCTAssertEqual(0, sharedStateServerHandler.requestNumber.load(ordering: .relaxed)) XCTAssertEqual(.ok, try client.get(url: url).wait().status) @@ -1918,7 +1882,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(2, sharedStateServerHandler.connectionNumber.load(ordering: .relaxed)) XCTAssertEqual(3, sharedStateServerHandler.requestNumber.load(ordering: .relaxed)) } - + func testPoolClosesIdleConnections() { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(connectionPool: .init(idleTimeout: .milliseconds(100)))) @@ -1929,19 +1893,7 @@ class HTTPClientTests: XCTestCase { Thread.sleep(forTimeInterval: 0.2) XCTAssertEqual(self.defaultHTTPBin.activeConnections, 0) } - - func testRacePoolIdleConnectionsAndGet() { - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(connectionPool: .init(idleTimeout: .milliseconds(10)))) - defer { - XCTAssertNoThrow(try localClient.syncShutdown()) - } - for _ in 1...500 { - XCTAssertNoThrow(try localClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait()) - Thread.sleep(forTimeInterval: 0.01 + .random(in: -0.05...0.05)) - } - } - + func testAvoidLeakingTLSHandshakeCompletionPromise() { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(timeout: .init(connect: .milliseconds(100)))) let localHTTPBin = HTTPBin() @@ -1950,21 +1902,21 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "http://localhost:\(port)").wait()) { error in if isTestingNIOTS() { - #if canImport(Network) +#if canImport(Network) // We can't be more specific than this. XCTAssertTrue(error is HTTPClient.NWTLSError || error is HTTPClient.NWPOSIXError) - #else +#else XCTFail("Impossible condition") - #endif +#endif } else { XCTAssert(error is NIOConnectionError, "Unexpected error: \(error)") } } } - + func testAsyncShutdown() throws { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) let promise = self.clientGroup.next().makePromise(of: Void.self) @@ -1976,7 +1928,7 @@ class HTTPClientTests: XCTestCase { } XCTAssertNoThrow(try promise.futureResult.wait()) } - + func testAsyncShutdownDefaultQueue() throws { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) let promise = self.clientGroup.next().makePromise(of: Void.self) @@ -1988,7 +1940,7 @@ class HTTPClientTests: XCTestCase { } XCTAssertNoThrow(try promise.futureResult.wait()) } - + func testValidationErrorsAreSurfaced() throws { let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .TRACE, body: .stream { _ in self.defaultClient.eventLoopGroup.next().makeSucceededFuture(()) @@ -1998,18 +1950,18 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(HTTPClientError.traceRequestWithBody, error as? HTTPClientError) } } - + func testUploadsReallyStream() { final class HTTPServer: ChannelInboundHandler { typealias InboundIn = HTTPServerRequestPart typealias OutboundOut = HTTPServerResponsePart - + private let headPromise: EventLoopPromise private let bodyPromises: [EventLoopPromise] private let endPromise: EventLoopPromise private var bodyPartsSeenSoFar = 0 private var atEnd = false - + init(headPromise: EventLoopPromise, bodyPromises: [EventLoopPromise], endPromise: EventLoopPromise) { @@ -2017,7 +1969,7 @@ class HTTPClientTests: XCTestCase { self.bodyPromises = bodyPromises self.endPromise = endPromise } - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { switch self.unwrapInboundIn(data) { case .head(let head): @@ -2034,13 +1986,13 @@ class HTTPClientTests: XCTestCase { self.atEnd = true } } - + func handlerRemoved(context: ChannelHandlerContext) { guard !self.atEnd else { return } struct NotFulfilledError: Error {} - + self.headPromise.fail(NotFulfilledError()) self.bodyPromises.forEach { $0.fail(NotFulfilledError()) @@ -2048,7 +2000,7 @@ class HTTPClientTests: XCTestCase { self.endPromise.fail(NotFulfilledError()) } } - + let group = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) @@ -2062,7 +2014,7 @@ class HTTPClientTests: XCTestCase { let endPromise = group.next().makePromise(of: Void.self) let sentOffAllBodyPartsPromise = group.next().makePromise(of: Void.self) let streamWriterPromise = group.next().makePromise(of: HTTPClient.Body.StreamWriter.self) - + func makeServer() -> Channel? { return try? ServerBootstrap(group: group) .childChannelInitializer { channel in @@ -2076,21 +2028,21 @@ class HTTPClientTests: XCTestCase { .bind(host: "127.0.0.1", port: 0) .wait() } - + func makeRequest(server: Channel) -> Request? { guard let localAddress = server.localAddress else { return nil } - + return try? HTTPClient.Request(url: "http://\(localAddress.ipAddress!):\(localAddress.port!)", method: .POST, headers: ["transfer-encoding": "chunked"], body: .stream { streamWriter in - streamWriterPromise.succeed(streamWriter) - return sentOffAllBodyPartsPromise.futureResult - }) + streamWriterPromise.succeed(streamWriter) + return sentOffAllBodyPartsPromise.futureResult + }) } - + guard let server = makeServer(), let request = makeRequest(server: server) else { XCTFail("couldn't make a server Channel and a matching Request...") return @@ -2098,14 +2050,14 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try server.close().wait()) } - + var buffer = ByteBufferAllocator().buffer(capacity: 1) let runningRequest = client.execute(request: request) guard let streamWriter = try? streamWriterPromise.futureResult.wait() else { XCTFail("didn't get StreamWriter") return } - + XCTAssertEqual(.POST, try headPromise.futureResult.wait().method) for bodyChunkNumber in 0..<16 { buffer.clear() @@ -2118,7 +2070,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try endPromise.futureResult.wait()) XCTAssertNoThrow(try runningRequest.wait()) } - + func testUploadStreamingCallinToleratedFromOtsideEL() throws { let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .POST, body: .stream(length: 4) { writer in let promise = self.defaultClient.eventLoopGroup.next().makePromise(of: Void.self) @@ -2132,13 +2084,13 @@ class HTTPClientTests: XCTestCase { }) XCTAssertNoThrow(try self.defaultClient.execute(request: request).wait()) } - + func testWeHandleUsSendingACloseHeaderCorrectly() { guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", method: .GET, headers: ["connection": "close"]), - let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { XCTFail("request 1 didn't work") return } @@ -2152,23 +2104,23 @@ class HTTPClientTests: XCTestCase { XCTFail("request 3 didn't work") return } - + // req 1 and 2 cannot share the same connection (close header) XCTAssertEqual(stats1.connectionNumber + 1, stats2.connectionNumber) XCTAssertEqual(stats1.requestNumber, 1) XCTAssertEqual(stats2.requestNumber, 1) - + // req 2 and 3 should share the same connection (keep-alive is default) XCTAssertEqual(stats2.requestNumber + 1, stats3.requestNumber) XCTAssertEqual(stats2.connectionNumber, stats3.connectionNumber) } - + func testWeHandleUsReceivingACloseHeaderCorrectly() { guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", method: .GET, headers: ["X-Send-Back-Header-Connection": "close"]), - let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { XCTFail("request 1 didn't work") return } @@ -2182,25 +2134,25 @@ class HTTPClientTests: XCTestCase { XCTFail("request 3 didn't work") return } - + // req 1 and 2 cannot share the same connection (close header) XCTAssertEqual(stats1.connectionNumber + 1, stats2.connectionNumber) XCTAssertEqual(stats1.requestNumber, 1) XCTAssertEqual(stats2.requestNumber, 1) - + // req 2 and 3 should share the same connection (keep-alive is default) XCTAssertEqual(stats2.requestNumber + 1, stats3.requestNumber) XCTAssertEqual(stats2.connectionNumber, stats3.connectionNumber) } - + func testWeHandleUsSendingACloseHeaderAmongstOtherConnectionHeadersCorrectly() { for closeHeader in [("connection", "close"), ("CoNneCTION", "ClOSe")] { guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", method: .GET, headers: ["X-Send-Back-Header-\(closeHeader.0)": - "foo,\(closeHeader.1),bar"]), - let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + "foo,\(closeHeader.1),bar"]), + let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { XCTFail("request 1 didn't work") return } @@ -2214,25 +2166,25 @@ class HTTPClientTests: XCTestCase { XCTFail("request 3 didn't work") return } - + // req 1 and 2 cannot share the same connection (close header) XCTAssertEqual(stats1.connectionNumber + 1, stats2.connectionNumber) XCTAssertEqual(stats2.requestNumber, 1) - + // req 2 and 3 should share the same connection (keep-alive is default) XCTAssertEqual(stats2.requestNumber + 1, stats3.requestNumber) XCTAssertEqual(stats2.connectionNumber, stats3.connectionNumber) } } - + func testWeHandleUsReceivingACloseHeaderAmongstOtherConnectionHeadersCorrectly() { for closeHeader in [("connection", "close"), ("CoNneCTION", "ClOSe")] { guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", method: .GET, headers: ["X-Send-Back-Header-\(closeHeader.0)": - "foo,\(closeHeader.1),bar"]), - let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + "foo,\(closeHeader.1),bar"]), + let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { XCTFail("request 1 didn't work") return } @@ -2246,26 +2198,26 @@ class HTTPClientTests: XCTestCase { XCTFail("request 3 didn't work") return } - + // req 1 and 2 cannot share the same connection (close header) XCTAssertEqual(stats1.connectionNumber + 1, stats2.connectionNumber) XCTAssertEqual(stats2.requestNumber, 1) - + // req 2 and 3 should share the same connection (keep-alive is default) XCTAssertEqual(stats2.requestNumber + 1, stats3.requestNumber) XCTAssertEqual(stats2.connectionNumber, stats3.connectionNumber) } } - + func testLoggingCorrectlyAttachesRequestInformationEvenAfterDuringRedirect() { let logStore = CollectEverythingLogHandler.LogStore() - + var logger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: logStore) }) logger.logLevel = .trace logger[metadataKey: "custom-request-id"] = "abcd" - + var maybeRequest: HTTPClient.Request? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( url: "http://localhost:\(self.defaultHTTPBin.port)/redirect/target", @@ -2275,7 +2227,7 @@ class HTTPClientTests: XCTestCase { ] )) guard let request = maybeRequest else { return } - + XCTAssertNoThrow(try self.defaultClient.execute( request: request, eventLoop: .indifferent, @@ -2283,29 +2235,29 @@ class HTTPClientTests: XCTestCase { logger: logger ).wait()) let logs = logStore.allEntries - + XCTAssertTrue(logs.allSatisfy { $0.metadata["custom-request-id"] == "abcd" }) - + guard let firstRequestID = logs.first?.metadata["ahc-request-id"] else { return XCTFail("could not get first request ID") } guard let lastRequestID = logs.last?.metadata["ahc-request-id"] else { return XCTFail("could not get second request ID") } - + let firstRequestLogs = logs.prefix(while: { $0.metadata["ahc-request-id"] == firstRequestID }) XCTAssertGreaterThan(firstRequestLogs.count, 0) - + let secondRequestLogs = logs.drop(while: { $0.metadata["ahc-request-id"] == firstRequestID }) XCTAssertGreaterThan(secondRequestLogs.count, 0) XCTAssertTrue(secondRequestLogs.allSatisfy { $0.metadata["ahc-request-id"] == lastRequestID }) - + logs.forEach { print($0) } } - + func testLoggingCorrectlyAttachesRequestInformation() { let logStore = CollectEverythingLogHandler.LogStore() - + var loggerYolo001 = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: logStore) }) @@ -2316,14 +2268,14 @@ class HTTPClientTests: XCTestCase { }) loggerACME002.logLevel = .trace loggerACME002[metadataKey: "acme-request-id"] = "acme-002" - + guard let request1 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get"), let request2 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "stats"), let request3 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "ok") else { XCTFail("bad stuff, can't even make request structures") return } - + // === Request 1 (Yolo001) XCTAssertNoThrow(try self.defaultClient.execute(request: request1, eventLoop: .indifferent, @@ -2331,7 +2283,7 @@ class HTTPClientTests: XCTestCase { logger: loggerYolo001).wait()) let logsAfterReq1 = logStore.allEntries logStore.allEntries = [] - + // === Request 2 (Yolo001) XCTAssertNoThrow(try self.defaultClient.execute(request: request2, eventLoop: .indifferent, @@ -2339,7 +2291,7 @@ class HTTPClientTests: XCTestCase { logger: loggerYolo001).wait()) let logsAfterReq2 = logStore.allEntries logStore.allEntries = [] - + // === Request 3 (ACME002) XCTAssertNoThrow(try self.defaultClient.execute(request: request3, eventLoop: .indifferent, @@ -2347,12 +2299,12 @@ class HTTPClientTests: XCTestCase { logger: loggerACME002).wait()) let logsAfterReq3 = logStore.allEntries logStore.allEntries = [] - + // === Assertions XCTAssertGreaterThan(logsAfterReq1.count, 0) XCTAssertGreaterThan(logsAfterReq2.count, 0) XCTAssertGreaterThan(logsAfterReq3.count, 0) - + XCTAssert(logsAfterReq1.allSatisfy { entry in if let httpRequestMetadata = entry.metadata["ahc-request-id"], let yoloRequestID = entry.metadata["yolo-request-id"] { @@ -2369,16 +2321,16 @@ class HTTPClientTests: XCTestCase { // Since a new connection must be created first we expect that the request is queued // and log message describing this is emitted. entry.message == "Request was queued (waiting for a connection to become available)" - && entry.level == .debug + && entry.level == .debug }) XCTAssert(logsAfterReq1.contains { entry in // After the new connection was created we expect a log message that describes that the // request was scheduled on a connection. The connection id must be set from here on. entry.message == "Request was scheduled on connection" - && entry.level == .debug - && entry.metadata["ahc-connection-id"] != nil + && entry.level == .debug + && entry.metadata["ahc-connection-id"] != nil }) - + XCTAssert(logsAfterReq2.allSatisfy { entry in if let httpRequestMetadata = entry.metadata["ahc-request-id"], let yoloRequestID = entry.metadata["yolo-request-id"] { @@ -2396,10 +2348,10 @@ class HTTPClientTests: XCTestCase { }) XCTAssert(logsAfterReq2.contains { entry in entry.message == "Request was scheduled on connection" - && entry.level == .debug - && entry.metadata["ahc-connection-id"] != nil + && entry.level == .debug + && entry.metadata["ahc-connection-id"] != nil }) - + XCTAssert(logsAfterReq3.allSatisfy { entry in if let httpRequestMetadata = entry.metadata["ahc-request-id"], let acmeRequestID = entry.metadata["acme-request-id"] { @@ -2417,39 +2369,39 @@ class HTTPClientTests: XCTestCase { }) XCTAssert(logsAfterReq3.contains { entry in entry.message == "Request was scheduled on connection" - && entry.level == .debug - && entry.metadata["ahc-connection-id"] != nil + && entry.level == .debug + && entry.metadata["ahc-connection-id"] != nil }) } - + func testNothingIsLoggedAtInfoOrHigher() { let logStore = CollectEverythingLogHandler.LogStore() - + var logger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: logStore) }) logger.logLevel = .info - + guard let request1 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get"), let request2 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "stats") else { XCTFail("bad stuff, can't even make request structures") return } - + // === Request 1 XCTAssertNoThrow(try self.defaultClient.execute(request: request1, eventLoop: .indifferent, deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + // === Request 2 XCTAssertNoThrow(try self.defaultClient.execute(request: request2, eventLoop: .indifferent, deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + // === Synthesized Request XCTAssertNoThrow(try self.defaultClient.execute(.GET, url: self.defaultHTTPBinURLPrefix + "get", @@ -2457,9 +2409,9 @@ class HTTPClientTests: XCTestCase { deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + XCTAssertEqual(0, self.backgroundLogStore.allEntries.filter { $0.level >= .info }.count) - + // === Synthesized Socket Path Request XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let backgroundLogStore = CollectEverythingLogHandler.LogStore() @@ -2467,7 +2419,7 @@ class HTTPClientTests: XCTestCase { CollectEverythingLogHandler(logStore: backgroundLogStore) }) backgroundLogger.logLevel = .trace - + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), backgroundActivityLogger: backgroundLogger) @@ -2475,7 +2427,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertNoThrow(try localClient.execute(.GET, socketPath: path, urlPath: "get", @@ -2483,10 +2435,10 @@ class HTTPClientTests: XCTestCase { deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .info }.count) }) - + // === Synthesized Secure Socket Path Request XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let backgroundLogStore = CollectEverythingLogHandler.LogStore() @@ -2494,7 +2446,7 @@ class HTTPClientTests: XCTestCase { CollectEverythingLogHandler(logStore: backgroundLogStore) }) backgroundLogger.logLevel = .trace - + let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none), @@ -2503,7 +2455,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertNoThrow(try localClient.execute(.GET, secureSocketPath: path, urlPath: "get", @@ -2511,24 +2463,24 @@ class HTTPClientTests: XCTestCase { deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .info }.count) }) } - + func testAllMethodsLog() { func checkExpectationsWithLogger(type: String, _ body: (Logger, String) throws -> T) throws -> T { let logStore = CollectEverythingLogHandler.LogStore() - + var logger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: logStore) }) logger.logLevel = .trace logger[metadataKey: "req"] = "yo-\(type)" - + let url = "not-found/request/\(type))" let result = try body(logger, url) - + XCTAssertGreaterThan(logStore.allEntries.count, 0) logStore.allEntries.forEach { entry in XCTAssertEqual("yo-\(type)", entry.metadata["req"] ?? "n/a") @@ -2536,41 +2488,41 @@ class HTTPClientTests: XCTestCase { } return result } - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "PUT") { logger, url in try self.defaultClient.put(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "POST") { logger, url in try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "DELETE") { logger, url in try self.defaultClient.delete(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "PATCH") { logger, url in try self.defaultClient.patch(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "CHECKOUT") { logger, url in try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + // No background activity expected here. XCTAssertEqual(0, self.backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) - + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let backgroundLogStore = CollectEverythingLogHandler.LogStore() var backgroundLogger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: backgroundLogStore) }) backgroundLogger.logLevel = .trace - + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), backgroundActivityLogger: backgroundLogger) @@ -2578,22 +2530,22 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in try localClient.execute(socketPath: path, urlPath: url, logger: logger).wait() }.status) - + // No background activity expected here. XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) }) - + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let backgroundLogStore = CollectEverythingLogHandler.LogStore() var backgroundLogger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: backgroundLogStore) }) backgroundLogger.logLevel = .trace - + let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none), @@ -2602,34 +2554,34 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in try localClient.execute(secureSocketPath: path, urlPath: url, logger: logger).wait() }.status) - + // No background activity expected here. XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) }) } - + func testClosingIdleConnectionsInPoolLogsInTheBackground() { XCTAssertNoThrow(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "/get").wait()) - + XCTAssertNoThrow(try self.defaultClient.syncShutdown()) - + XCTAssertGreaterThanOrEqual(self.backgroundLogStore.allEntries.count, 0) XCTAssert(self.backgroundLogStore.allEntries.contains { entry in entry.message == "Shutting down connection pool" }) XCTAssert(self.backgroundLogStore.allEntries.allSatisfy { entry in entry.metadata["ahc-request-id"] == nil && - entry.metadata["ahc-request"] == nil && - entry.metadata["ahc-pool-key"] != nil + entry.metadata["ahc-request"] == nil && + entry.metadata["ahc-pool-key"] != nil }) - + self.defaultClient = nil // so it doesn't get shut down again. } - + func testUploadStreamingNoLength() throws { let server = NIOHTTP1TestServer(group: self.serverGroup) let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) @@ -2637,30 +2589,30 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try server.stop()) } - + var request = try HTTPClient.Request(url: "http://localhost:\(server.serverPort)/") request.body = .stream { writer in writer.write(.byteBuffer(ByteBuffer(string: "1234"))) } - + let future = client.execute(request: request) - + switch try server.readInbound() { case .head(let head): XCTAssertEqual(head.headers["transfer-encoding"], ["chunked"]) default: XCTFail("Unexpected part") } - + XCTAssertNoThrow(try server.readInbound()) // .body XCTAssertNoThrow(try server.readInbound()) // .end - + XCTAssertNoThrow(try server.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) XCTAssertNoThrow(try server.writeOutbound(.end(nil))) - + XCTAssertNoThrow(try future.wait()) } - + func testConnectErrorPropagatedToDelegate() throws { class TestDelegate: HTTPClientResponseDelegate { typealias Response = Void @@ -2670,49 +2622,49 @@ class HTTPClientTests: XCTestCase { self.error = error } } - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(timeout: .init(connect: .milliseconds(10)))) - + defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + // This must throw as 198.51.100.254 is reserved for documentation only let request = try HTTPClient.Request(url: "http://198.51.100.254:65535/get") let delegate = TestDelegate() - + XCTAssertThrowsError(try httpClient.execute(request: request, delegate: delegate).wait()) { XCTAssertEqualTypeAndValue($0, HTTPClientError.connectTimeout) XCTAssertEqualTypeAndValue(delegate.error, HTTPClientError.connectTimeout) } } - + func testDelegateCallinsTolerateRandomEL() throws { class TestDelegate: HTTPClientResponseDelegate { typealias Response = Void let eventLoop: EventLoop - + init(eventLoop: EventLoop) { self.eventLoop = eventLoop } - + func didReceiveHead(task: HTTPClient.Task, _: HTTPResponseHead) -> EventLoopFuture { return self.eventLoop.makeSucceededFuture(()) } - + func didReceiveBodyPart(task: HTTPClient.Task, _: ByteBuffer) -> EventLoopFuture { return self.eventLoop.makeSucceededFuture(()) } - + func didFinishRequest(task: HTTPClient.Task) throws {} } - + let elg = getDefaultEventLoopGroup(numberOfThreads: 3) let first = elg.next() let second = elg.next() XCTAssertFalse(first === second) - + let httpServer = NIOHTTP1TestServer(group: self.serverGroup) let httpClient = HTTPClient(eventLoopGroupProvider: .shared(first)) defer { @@ -2720,35 +2672,35 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try httpServer.stop()) XCTAssertNoThrow(try elg.syncShutdownGracefully()) } - + let delegate = TestDelegate(eventLoop: second) let request = try HTTPClient.Request(url: "http://localhost:\(httpServer.serverPort)/") let future = httpClient.execute(request: request, delegate: delegate) - + XCTAssertNoThrow(try httpServer.readInbound()) // .head XCTAssertNoThrow(try httpServer.readInbound()) // .end - + XCTAssertNoThrow(try httpServer.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) XCTAssertNoThrow(try httpServer.writeOutbound(.body(.byteBuffer(ByteBuffer(string: "1234"))))) XCTAssertNoThrow(try httpServer.writeOutbound(.end(nil))) - + XCTAssertNoThrow(try future.wait()) } - + func testContentLengthTooLongFails() throws { let url = self.defaultHTTPBinURLPrefix + "post" XCTAssertThrowsError( try self.defaultClient.execute(request: - Request(url: url, - body: .stream(length: 10) { streamWriter in - let promise = self.defaultClient.eventLoopGroup.next().makePromise(of: Void.self) - DispatchQueue(label: "content-length-test").async { - streamWriter.write(.byteBuffer(ByteBuffer(string: "1"))).cascade(to: promise) - } - return promise.futureResult - })).wait()) { error in - XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) - } + Request(url: url, + body: .stream(length: 10) { streamWriter in + let promise = self.defaultClient.eventLoopGroup.next().makePromise(of: Void.self) + DispatchQueue(label: "content-length-test").async { + streamWriter.write(.byteBuffer(ByteBuffer(string: "1"))).cascade(to: promise) + } + return promise.futureResult + })).wait()) { error in + XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) + } // Quickly try another request and check that it works. let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait() guard var body = response.body else { @@ -2762,19 +2714,19 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(info.connectionNumber, 1) XCTAssertEqual(info.requestNumber, 1) } - + // currently gets stuck because of #250 the server just never replies func testContentLengthTooShortFails() throws { let url = self.defaultHTTPBinURLPrefix + "post" let tooLong = "XBAD BAD BAD NOT HTTP/1.1\r\n\r\n" XCTAssertThrowsError( try self.defaultClient.execute(request: - Request(url: url, - body: .stream(length: 1) { streamWriter in - streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong))) - })).wait()) { error in - XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) - } + Request(url: url, + body: .stream(length: 1) { streamWriter in + streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong))) + })).wait()) { error in + XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) + } // Quickly try another request and check that it works. If we by accident wrote some extra bytes into the // stream (and reuse the connection) that could cause problems. let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait() @@ -2789,10 +2741,10 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(info.connectionNumber, 1) XCTAssertEqual(info.requestNumber, 1) } - + func testBodyUploadAfterEndFails() { let url = self.defaultHTTPBinURLPrefix + "post" - + func uploader(_ streamWriter: HTTPClient.Body.StreamWriter) -> EventLoopFuture { let done = streamWriter.write(.byteBuffer(ByteBuffer(string: "X"))) done.recover { error -> Void in @@ -2812,111 +2764,63 @@ class HTTPClientTests: XCTestCase { } return done } - + var request: HTTPClient.Request? XCTAssertNoThrow(request = try Request(url: url, body: .stream(length: 1, uploader))) XCTAssertThrowsError(try self.defaultClient.execute(request: XCTUnwrap(request)).wait()) { XCTAssertEqual($0 as? HTTPClientError, .writeAfterRequestSent) } - + // Quickly try another request and check that it works. If we by accident wrote some extra bytes into the // stream (and reuse the connection) that could cause problems. XCTAssertNoThrow(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait()) } - - func testNoBytesSentOverBodyLimit() throws { - let server = NIOHTTP1TestServer(group: self.serverGroup) - defer { - XCTAssertNoThrow(try server.stop()) - } - - let tooLong = "XBAD BAD BAD NOT HTTP/1.1\r\n\r\n" - - let request = try Request( - url: "http://localhost:\(server.serverPort)", - body: .stream(length: 1) { streamWriter in - streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong))) - } - ) - - let future = self.defaultClient.execute(request: request) - - // Okay, what happens here needs an explanation: - // - // In the request state machine, we should start the request, which will lead to an - // invocation of `context.write(HTTPRequestHead)`. Since we will receive a streamed request - // body a `context.flush()` will be issued. Further the request stream will be started. - // Since the request stream immediately produces to much data, the request will be failed - // and the connection will be closed. - // - // Even though a flush was issued after the request head, there is no guarantee that the - // request head was written to the network. For this reason we must accept not receiving a - // request and receiving a request head. - - do { - _ = try server.receiveHead() - - // A request head was sent. We expect the request now to fail with a parsing error, - // since the client ended the connection to early (from the server's point of view.) - XCTAssertThrowsError(try server.readInbound()) { - XCTAssertEqual($0 as? HTTPParserError, HTTPParserError.invalidEOFState) - } - } catch { - // TBD: We sadly can't verify the error type, since it is private in `NIOTestUtils`: - // NIOTestUtils.BlockingQueue.TimeoutError - } - - // request must always be failed with this error - XCTAssertThrowsError(try future.wait()) { - XCTAssertEqual($0 as? HTTPClientError, .bodyLengthMismatch) - } - } - + func testDoubleError() throws { // This is needed to that connection pool will not get into closed state when we release // second connection. _ = self.defaultClient.get(url: "http://localhost:\(self.defaultHTTPBin.port)/events/10/1") - + var request = try HTTPClient.Request(url: "http://localhost:\(self.defaultHTTPBin.port)/wait", method: .POST) request.body = .stream { writer in // Start writing chunks so tha we will try to write after read timeout is thrown for _ in 1...10 { _ = writer.write(.byteBuffer(ByteBuffer(string: "1234"))) } - + let promise = self.clientGroup.next().makePromise(of: Void.self) self.clientGroup.next().scheduleTask(in: .milliseconds(3)) { writer.write(.byteBuffer(ByteBuffer(string: "1234"))).cascade(to: promise) } - + return promise.futureResult } - + // We specify a deadline of 2 ms co that request will be timed out before all chunks are writtent, // we need to verify that second error on write after timeout does not lead to double-release. XCTAssertThrowsError(try self.defaultClient.execute(request: request, deadline: .now() + .milliseconds(2)).wait()) } - + func testSSLHandshakeErrorPropagation() throws { class CloseHandler: ChannelInboundHandler { typealias InboundIn = Any - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { context.close(promise: nil) } } - + let server = try ServerBootstrap(group: self.serverGroup) .childChannelInitializer { channel in channel.pipeline.addHandler(CloseHandler()) } .bind(host: "127.0.0.1", port: 0) .wait() - + defer { XCTAssertNoThrow(try server.close().wait()) } - + var timeout = HTTPClient.Configuration.Timeout(connect: .seconds(10)) if isTestingNIOTS() { // If we are using Network.framework, we set the connect timeout down very low here @@ -2925,24 +2829,24 @@ class HTTPClientTests: XCTestCase { // DO NOT CHANGE THIS TO DISABLE WAITING FOR CONNECTIVITY. timeout.connect = .milliseconds(100) } - + let config = HTTPClient.Configuration(timeout: timeout) let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + let request = try Request(url: "https://127.0.0.1:\(server.localAddress!.port!)", method: .GET) let task = client.execute(request: request, delegate: TestHTTPDelegate()) - + XCTAssertThrowsError(try task.wait()) { error in if isTestingNIOTS() { - #if canImport(Network) +#if canImport(Network) // We can't be more specific than this. XCTAssertTrue(error is HTTPClient.NWTLSError) - #else +#else XCTFail("Impossible condition") - #endif +#endif } else { switch error as? NIOSSLError { case .some(.handshakeFailed(.sslError(_))): break @@ -2951,55 +2855,55 @@ class HTTPClientTests: XCTestCase { } } } - + func testSSLHandshakeErrorPropagationDelayedClose() throws { // This is as the test above, but the close handler delays its close action by a few hundred ms. // This will tend to catch the pipeline at different weird stages, and flush out different bugs. class CloseHandler: ChannelInboundHandler { typealias InboundIn = Any - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { context.eventLoop.scheduleTask(in: .milliseconds(100)) { context.close(promise: nil) } } } - + let server = try ServerBootstrap(group: self.serverGroup) .childChannelInitializer { channel in channel.pipeline.addHandler(CloseHandler()) } .bind(host: "127.0.0.1", port: 0) .wait() - + defer { XCTAssertNoThrow(try server.close().wait()) } - + var timeout = HTTPClient.Configuration.Timeout(connect: .seconds(10)) if isTestingNIOTS() { // If we are using Network.framework, we set the connect timeout down very low here // because on NIOTS a failing TLS handshake manifests as a connect timeout. timeout.connect = .milliseconds(300) } - + let config = HTTPClient.Configuration(timeout: timeout) let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + let request = try Request(url: "https://127.0.0.1:\(server.localAddress!.port!)", method: .GET) let task = client.execute(request: request, delegate: TestHTTPDelegate()) - + XCTAssertThrowsError(try task.wait()) { error in if isTestingNIOTS() { - #if canImport(Network) +#if canImport(Network) // We can't be more specific than this. XCTAssertTrue(error is HTTPClient.NWTLSError) - #else +#else XCTFail("Impossible condition") - #endif +#endif } else { switch error as? NIOSSLError { case .some(.handshakeFailed(.sslError(_))): break @@ -3008,11 +2912,11 @@ class HTTPClientTests: XCTestCase { } } } - + func testWeCloseConnectionsWhenConnectionCloseSetByServer() throws { let group = DispatchGroup() group.enter() - + let server = try ServerBootstrap(group: self.serverGroup) .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) .childChannelInitializer { channel in @@ -3022,19 +2926,19 @@ class HTTPClientTests: XCTestCase { } .bind(host: "localhost", port: 0) .wait() - + defer { server.close(promise: nil) } - + // Simple request, should go great. XCTAssertNoThrow(try self.defaultClient.get(url: "http://localhost:\(server.localAddress!.port!)/").wait()) - + // Shouldn't need more than 100ms of waiting to see the close. let result = group.wait(timeout: DispatchTime.now() + DispatchTimeInterval.milliseconds(100)) XCTAssertEqual(result, .success, "we never closed the connection!") } - + // In this test, we test that a request can continue to stream its body after the response head, // was received. The client sends a number to the server and waits for the server to echo the // number. Once the client receives the echoed number, it will continue with the next number. @@ -3042,28 +2946,28 @@ class HTTPClientTests: XCTestCase { func testBiDirectionalStreaming() { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let writeEL = eventLoopGroup.next() let delegateEL = eventLoopGroup.next() - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + let delegate = ResponseStreamDelegate(eventLoop: delegateEL) - + let body: HTTPClient.Body = .stream { writer in let finalPromise = writeEL.makePromise(of: Void.self) - + func writeLoop(_ writer: HTTPClient.Body.StreamWriter, index: Int) { // always invoke from the wrong el to test thread safety writeEL.preconditionInEventLoop() - + if index >= 30 { return finalPromise.succeed(()) } - + let sent = ByteBuffer(integer: index) writer.write(.byteBuffer(sent)).flatMap { () -> EventLoopFuture in // ensure, that the writer dispatches back to the expected delegate el. @@ -3073,37 +2977,37 @@ class HTTPClientTests: XCTestCase { switch result { case .success(let returned): XCTAssertEqual(returned, sent) - + writeEL.execute { writeLoop(writer, index: index + 1) } - + case .failure(let error): finalPromise.fail(error) } } } - + writeEL.execute { writeLoop(writer, index: 0) } - + return finalPromise.futureResult } - + let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)", body: body) let future = httpClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: delegateEL)) - + XCTAssertNoThrow(try future.wait()) XCTAssertNil(try delegate.next().wait()) } - + func testResponseAccumulatorMaxBodySizeLimitExceedingWithContentLength() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<11) - + var request = try Request(url: httpBin.baseURL) request.body = .byteBuffer(body) XCTAssertThrowsError(try self.defaultClient.execute( @@ -3113,45 +3017,45 @@ class HTTPClientTests: XCTestCase { XCTAssertTrue(error is ResponseAccumulator.ResponseTooBigError, "unexpected error \(error)") } } - + func testResponseAccumulatorMaxBodySizeLimitNotExceedingWithContentLength() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<10) - + var request = try Request(url: httpBin.baseURL) request.body = .byteBuffer(body) let response = try self.defaultClient.execute( request: request, delegate: ResponseAccumulator(request: request, maxBodySize: 10) ).wait() - + XCTAssertEqual(response.body, body) } - + func testResponseAccumulatorMaxBodySizeLimitExceedingWithContentLengthButMethodIsHead() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHeaders() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<11) - + var request = try Request(url: httpBin.baseURL, method: .HEAD) request.body = .byteBuffer(body) let response = try self.defaultClient.execute( request: request, delegate: ResponseAccumulator(request: request, maxBodySize: 10) ).wait() - + XCTAssertEqual(response.body ?? ByteBuffer(), ByteBuffer()) } - + func testResponseAccumulatorMaxBodySizeLimitExceedingWithTransferEncodingChuncked() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<11) - + var request = try Request(url: httpBin.baseURL) request.body = .stream { writer in writer.write(.byteBuffer(body)) @@ -3163,13 +3067,13 @@ class HTTPClientTests: XCTestCase { XCTAssertTrue(error is ResponseAccumulator.ResponseTooBigError, "unexpected error \(error)") } } - + func testResponseAccumulatorMaxBodySizeLimitNotExceedingWithTransferEncodingChuncked() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<10) - + var request = try Request(url: httpBin.baseURL) request.body = .stream { writer in writer.write(.byteBuffer(body)) @@ -3178,37 +3082,37 @@ class HTTPClientTests: XCTestCase { request: request, delegate: ResponseAccumulator(request: request, maxBodySize: 10) ).wait() - + XCTAssertEqual(response.body, body) } - + // In this test, we test that a request can continue to stream its body after the response head and end // was received where the end is a 200. func testBiDirectionalStreamingEarly200() { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTP200DelayedHandler(bodyPartsBeforeResponse: 1) } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let writeEL = eventLoopGroup.next() let delegateEL = eventLoopGroup.next() - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + let delegate = ResponseStreamDelegate(eventLoop: delegateEL) - + let body: HTTPClient.Body = .stream { writer in let finalPromise = writeEL.makePromise(of: Void.self) - + func writeLoop(_ writer: HTTPClient.Body.StreamWriter, index: Int) { // always invoke from the wrong el to test thread safety writeEL.preconditionInEventLoop() - + if index >= 30 { return finalPromise.succeed(()) } - + let sent = ByteBuffer(integer: index) writer.write(.byteBuffer(sent)).whenComplete { result in switch result { @@ -3216,50 +3120,50 @@ class HTTPClientTests: XCTestCase { writeEL.execute { writeLoop(writer, index: index + 1) } - + case .failure(let error): finalPromise.fail(error) } } } - + writeEL.execute { writeLoop(writer, index: 0) } - + return finalPromise.futureResult } - + let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)", body: body) let future = httpClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: delegateEL)) XCTAssertNoThrow(try future.wait()) XCTAssertNil(try delegate.next().wait()) } - + // This test is identical to the one above, except that we send another request immediately after. This is a regression // test for https://github.com/swift-server/async-http-client/issues/595. func testBiDirectionalStreamingEarly200DoesntPreventUsFromSendingMoreRequests() { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTP200DelayedHandler(bodyPartsBeforeResponse: 1) } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let writeEL = eventLoopGroup.next() - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + let body: HTTPClient.Body = .stream { writer in let finalPromise = writeEL.makePromise(of: Void.self) - + func writeLoop(_ writer: HTTPClient.Body.StreamWriter, index: Int) { // always invoke from the wrong el to test thread safety writeEL.preconditionInEventLoop() - + if index >= 30 { return finalPromise.succeed(()) } - + let sent = ByteBuffer(integer: index) writer.write(.byteBuffer(sent)).whenComplete { result in switch result { @@ -3267,55 +3171,55 @@ class HTTPClientTests: XCTestCase { writeEL.execute { writeLoop(writer, index: index + 1) } - + case .failure(let error): finalPromise.fail(error) } } } - + writeEL.execute { writeLoop(writer, index: 0) } - + return finalPromise.futureResult } - + let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)", body: body) let future = httpClient.execute(request: request) XCTAssertNoThrow(try future.wait()) - + // Try another request let future2 = httpClient.execute(request: request) XCTAssertNoThrow(try future2.wait()) } - + // This test validates that we correctly close the connection after our body completes when we've streamed a // body and received the 2XX response _before_ we finished our stream. func testCloseConnectionAfterEarly2XXWhenStreaming() { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - + let onClosePromise = eventLoopGroup.next().makePromise(of: Void.self) let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in ExpectClosureServerHandler(onClosePromise: onClosePromise) } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let writeEL = eventLoopGroup.next() - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + let body: HTTPClient.Body = .stream { writer in let finalPromise = writeEL.makePromise(of: Void.self) - + func writeLoop(_ writer: HTTPClient.Body.StreamWriter, index: Int) { // always invoke from the wrong el to test thread safety writeEL.preconditionInEventLoop() - + if index >= 30 { return finalPromise.succeed(()) } - + let sent = ByteBuffer(integer: index) writer.write(.byteBuffer(sent)).whenComplete { result in switch result { @@ -3323,31 +3227,31 @@ class HTTPClientTests: XCTestCase { writeEL.execute { writeLoop(writer, index: index + 1) } - + case .failure(let error): finalPromise.fail(error) } } } - + writeEL.execute { writeLoop(writer, index: 0) } - + return finalPromise.futureResult } - + let headers = HTTPHeaders([("Connection", "close")]) let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)", headers: headers, body: body) let future = httpClient.execute(request: request) XCTAssertNoThrow(try future.wait()) XCTAssertNoThrow(try onClosePromise.futureResult.wait()) } - + func testSynchronousHandshakeErrorReporting() throws { // This only affects cases where we use NIOSSL. guard !isTestingNIOTS() else { return } - + // We use a specially crafted client that has no cipher suites to offer. To do this we ask // only for cipher suites incompatible with our TLS version. var tlsConfig = TLSConfiguration.makeClientConfiguration() @@ -3361,7 +3265,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/").wait()) { error in guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else { XCTFail("Unexpected error: \(error)") @@ -3369,30 +3273,30 @@ class HTTPClientTests: XCTestCase { } } } - + func testFileDownloadChunked() throws { var request = try Request(url: self.defaultHTTPBinURLPrefix + "chunked") request.headers.add(name: "Accept", value: "text/event-stream") - + let progress = - try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in - let delegate = try FileDownloadDelegate(path: path) - - let progress = try self.defaultClient.execute( - request: request, - delegate: delegate - ) + try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in + let delegate = try FileDownloadDelegate(path: path) + + let progress = try self.defaultClient.execute( + request: request, + delegate: delegate + ) .wait() - - try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path)) - - return progress - } - + + try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path)) + + return progress + } + XCTAssertEqual(nil, progress.totalBytes) XCTAssertEqual(50, progress.receivedBytes) } - + func testCloseWhileBackpressureIsExertedIsFine() throws { let request = try Request(url: self.defaultHTTPBinURLPrefix + "close-on-response") let delegate = DelayOnHeadDelegate(eventLoop: self.clientGroup.next()) { _, promise in @@ -3400,9 +3304,9 @@ class HTTPClientTests: XCTestCase { promise.succeed(()) } } - + let resultFuture = self.defaultClient.execute(request: request, delegate: delegate) - + // The full response must be correctly delivered. var data = try resultFuture.wait() guard let info = try data.readJSONDecodable(RequestInfo.self, length: data.readableBytes) else { @@ -3411,25 +3315,25 @@ class HTTPClientTests: XCTestCase { } XCTAssertEqual(info.data, "some body content") } - + func testErrorAfterCloseWhileBackpressureExerted() throws { enum ExpectedError: Error { case expected } - + let request = try Request(url: self.defaultHTTPBinURLPrefix + "close-on-response") let delegate = DelayOnHeadDelegate(eventLoop: self.clientGroup.next()) { _, backpressurePromise in backpressurePromise.fail(ExpectedError.expected) } - + let resultFuture = self.defaultClient.execute(request: request, delegate: delegate) - + // The task must be failed. XCTAssertThrowsError(try resultFuture.wait()) { error in XCTAssertEqual(error as? ExpectedError, .expected) } } - + func testRequestSpecificTLS() throws { let configuration = HTTPClient.Configuration(tlsConfiguration: nil, timeout: .init(), @@ -3438,12 +3342,12 @@ class HTTPClientTests: XCTestCase { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: configuration) let decoder = JSONDecoder() - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + // First two requests use identical TLS configurations. var tlsConfig = TLSConfiguration.makeClientConfiguration() tlsConfig.certificateVerification = .none @@ -3454,7 +3358,7 @@ class HTTPClientTests: XCTestCase { return } let firstConnectionNumber = try decoder.decode(RequestInfo.self, from: firstBody).connectionNumber - + let secondRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: tlsConfig) let secondResponse = try localClient.execute(request: secondRequest).wait() guard let secondBody = secondResponse.body else { @@ -3462,7 +3366,7 @@ class HTTPClientTests: XCTestCase { return } let secondConnectionNumber = try decoder.decode(RequestInfo.self, from: secondBody).connectionNumber - + // Uses a differrent TLS config. var tlsConfig2 = TLSConfiguration.makeClientConfiguration() tlsConfig2.certificateVerification = .none @@ -3474,25 +3378,84 @@ class HTTPClientTests: XCTestCase { return } let thirdConnectionNumber = try decoder.decode(RequestInfo.self, from: thirdBody).connectionNumber - + XCTAssertEqual(firstResponse.status, .ok) XCTAssertEqual(secondResponse.status, .ok) XCTAssertEqual(thirdResponse.status, .ok) XCTAssertEqual(firstConnectionNumber, secondConnectionNumber, "Identical TLS configurations did not use the same connection") XCTAssertNotEqual(thirdConnectionNumber, firstConnectionNumber, "Different TLS configurations did not use different connections.") } + + func testRequestWithHeaderTransferEncodingIdentityDoesNotFail() { + let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } + + let client = HTTPClient(eventLoopGroupProvider: .shared(group)) + defer { XCTAssertNoThrow(try client.syncShutdown()) } + + let httpBin = HTTPBin() + defer { XCTAssertNoThrow(try httpBin.shutdown()) } + + guard var request = try? Request(url: "http://127.0.0.1:\(httpBin.port)/get") else { + return XCTFail("Expected to have a request here.") + } + request.headers.add(name: "X-Test-Header", value: "X-Test-Value") + request.headers.add(name: "Transfer-Encoding", value: "identity") + request.body = .string("1234") + + XCTAssertNoThrow(try client.execute(request: request).wait()) + } + + func testMassiveDownload() { + var response: HTTPClient.Response? + XCTAssertNoThrow(response = try self.defaultClient.get(url: "\(self.defaultHTTPBinURLPrefix)mega-chunked").wait()) + + XCTAssertEqual(.ok, response?.status) + XCTAssertEqual(response?.version, .http1_1) + XCTAssertEqual(response?.body?.readableBytes, 10_000) + } + + func testShutdownWithFutures() { + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) + XCTAssertNoThrow(try httpClient.shutdown().wait()) + } +} +final class TestResponseDelayGet: HTTPClientTestsBaseClass { + func testResponseDelayGet() throws { + let req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", + method: .GET, + headers: ["X-internal-delay": "2000"], + body: nil) + let start = NIODeadline.now() + let response = try self.defaultClient.execute(request: req).wait() + XCTAssertGreaterThanOrEqual(.now() - start, .milliseconds(1_900 /* 1.9 seconds */ )) + XCTAssertEqual(response.status, .ok) + } +} + +final class TestIdleTimeoutNoReuse: HTTPClientTestsBaseClass { + func testIdleTimeoutNoReuse() throws { + var req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .GET) + XCTAssertNoThrow(try self.defaultClient.execute(request: req, deadline: .now() + .seconds(2)).wait()) + req.headers.add(name: "X-internal-delay", value: "2500") + try self.defaultClient.eventLoopGroup.next().scheduleTask(in: .milliseconds(250)) {}.futureResult.wait() + XCTAssertNoThrow(try self.defaultClient.execute(request: req).timeout(after: .seconds(10)).wait()) + } +} + +final class TestConnectionPoolSizeConfigValueIsRespected: HTTPClientTestsBaseClass { func testConnectionPoolSizeConfigValueIsRespected() { let numberOfRequestsPerThread = 1000 let numberOfParallelWorkers = 16 let poolSize = 12 - + let httpBin = HTTPBin() defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let group = MultiThreadedEventLoopGroup(numberOfThreads: 4) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } - + let configuration = HTTPClient.Configuration( connectionPool: .init( idleTimeout: .seconds(30), @@ -3501,7 +3464,7 @@ class HTTPClientTests: XCTestCase { ) let client = HTTPClient(eventLoopGroupProvider: .shared(group), configuration: configuration) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + let g = DispatchGroup() for workerID in 0..]() + for _ in 1...requestCount { + let req = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, headers: ["X-internal-delay": "100"]) + futureResults.append(localClient.execute(request: req)) + } + XCTAssertNoThrow(try EventLoopFuture.andAllSucceed(futureResults, on: eventLoop).wait()) + } +} - let client = HTTPClient(eventLoopGroupProvider: .shared(group)) - defer { XCTAssertNoThrow(try client.syncShutdown()) } +final class TestNoBytesSentOverBodyLimit: HTTPClientTestsBaseClass { + func testNoBytesSentOverBodyLimit() throws { + let server = NIOHTTP1TestServer(group: self.serverGroup) + defer { + XCTAssertNoThrow(try server.stop()) + } - let httpBin = HTTPBin() - defer { XCTAssertNoThrow(try httpBin.shutdown()) } + let tooLong = "XBAD BAD BAD NOT HTTP/1.1\r\n\r\n" - guard var request = try? Request(url: "http://127.0.0.1:\(httpBin.port)/get") else { - return XCTFail("Expected to have a request here.") - } - request.headers.add(name: "X-Test-Header", value: "X-Test-Value") - request.headers.add(name: "Transfer-Encoding", value: "identity") - request.body = .string("1234") + let request = try Request( + url: "http://localhost:\(server.serverPort)", + body: .stream(length: 1) { streamWriter in + streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong))) + } + ) - XCTAssertNoThrow(try client.execute(request: request).wait()) - } + let future = self.defaultClient.execute(request: request) - func testMassiveDownload() { - var response: HTTPClient.Response? - XCTAssertNoThrow(response = try self.defaultClient.get(url: "\(self.defaultHTTPBinURLPrefix)mega-chunked").wait()) + // Okay, what happens here needs an explanation: + // + // In the request state machine, we should start the request, which will lead to an + // invocation of `context.write(HTTPRequestHead)`. Since we will receive a streamed request + // body a `context.flush()` will be issued. Further the request stream will be started. + // Since the request stream immediately produces to much data, the request will be failed + // and the connection will be closed. + // + // Even though a flush was issued after the request head, there is no guarantee that the + // request head was written to the network. For this reason we must accept not receiving a + // request and receiving a request head. - XCTAssertEqual(.ok, response?.status) - XCTAssertEqual(response?.version, .http1_1) - XCTAssertEqual(response?.body?.readableBytes, 10_000) + do { + _ = try server.receiveHead() + + // A request head was sent. We expect the request now to fail with a parsing error, + // since the client ended the connection to early (from the server's point of view.) + XCTAssertThrowsError(try server.readInbound()) { + XCTAssertEqual($0 as? HTTPParserError, HTTPParserError.invalidEOFState) + } + } catch { + // TBD: We sadly can't verify the error type, since it is private in `NIOTestUtils`: + // NIOTestUtils.BlockingQueue.TimeoutError + } + + // request must always be failed with this error + XCTAssertThrowsError(try future.wait()) { + XCTAssertEqual($0 as? HTTPClientError, .bodyLengthMismatch) + } } +} - func testShutdownWithFutures() { - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) - XCTAssertNoThrow(try httpClient.shutdown().wait()) + +final class TestRacePoolIdleConnectionsAndGet: HTTPClientTestsBaseClass { + func testRacePoolIdleConnectionsAndGet() { + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(connectionPool: .init(idleTimeout: .milliseconds(10)))) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + } + for _ in 1...200 { + XCTAssertNoThrow(try localClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait()) + Thread.sleep(forTimeInterval: 0.01 + .random(in: -0.01...0.01)) + } } } From f65f415f169086cf19c4c30e3e23e2b438276046 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Mon, 10 Oct 2022 18:41:22 +0100 Subject: [PATCH 3/4] Run SwiftFormat --- .../AsyncAwaitEndToEndTests.swift | 2 +- .../HTTPClient+SOCKSTests.swift | 2 +- .../HTTPClientInternalTests.swift | 1 - .../HTTPClientTests+XCTest.swift | 10 +- .../HTTPClientTests.swift | 1219 ++++++++--------- 5 files changed, 616 insertions(+), 618 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index 72e18d23e..8995acfb1 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -499,7 +499,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let config = HTTPClient.Configuration() .enableFastFailureModeForTesting() - + let localClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } let request = HTTPClientRequest(url: "https://localhost:\(port)") diff --git a/Tests/AsyncHTTPClientTests/HTTPClient+SOCKSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClient+SOCKSTests.swift index ca6f83c7f..e1427f5c0 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClient+SOCKSTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClient+SOCKSTests.swift @@ -77,7 +77,7 @@ class HTTPClientSOCKSTests: XCTestCase { 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) + proxy: .socksServer(host: "localhost", port: socksBin.port) ).enableFastFailureModeForTesting()) defer { diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index a67565d1e..6f412a30d 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -591,7 +591,6 @@ class HTTPClientInternalTests: XCTestCase { } } - extension HTTPClient.Configuration { func enableFastFailureModeForTesting() -> Self { var copy = self diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index 532f67147..9a54b36f1 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -77,12 +77,12 @@ extension HTTPClientTests { ("testWorksWhenServerClosesConnectionAfterReceivingRequest", testWorksWhenServerClosesConnectionAfterReceivingRequest), ("testSubsequentRequestsWorkWithServerSendingConnectionClose", testSubsequentRequestsWorkWithServerSendingConnectionClose), ("testSubsequentRequestsWorkWithServerAlternatingBetweenKeepAliveAndClose", testSubsequentRequestsWorkWithServerAlternatingBetweenKeepAliveAndClose), - + ("testStressGetHttpsSSLError", testStressGetHttpsSSLError), ("testSelfSignedCertificateIsRejectedWithCorrectError", testSelfSignedCertificateIsRejectedWithCorrectError), ("testSelfSignedCertificateIsRejectedWithCorrectErrorIfRequestDeadlineIsExceeded", testSelfSignedCertificateIsRejectedWithCorrectErrorIfRequestDeadlineIsExceeded), ("testFailingConnectionIsReleased", testFailingConnectionIsReleased), - + ("testStressGetClose", testStressGetClose), ("testManyConcurrentRequestsWork", testManyConcurrentRequestsWork), ("testRepeatedRequestsWorkWhenServerAlwaysCloses", testRepeatedRequestsWorkWhenServerAlwaysCloses), @@ -103,7 +103,7 @@ extension HTTPClientTests { ("testUseExistingConnectionOnDifferentEL", testUseExistingConnectionOnDifferentEL), ("testWeRecoverFromServerThatClosesTheConnectionOnUs", testWeRecoverFromServerThatClosesTheConnectionOnUs), ("testPoolClosesIdleConnections", testPoolClosesIdleConnections), - + ("testAvoidLeakingTLSHandshakeCompletionPromise", testAvoidLeakingTLSHandshakeCompletionPromise), ("testAsyncShutdown", testAsyncShutdown), ("testAsyncShutdownDefaultQueue", testAsyncShutdownDefaultQueue), @@ -125,7 +125,7 @@ extension HTTPClientTests { ("testContentLengthTooLongFails", testContentLengthTooLongFails), ("testContentLengthTooShortFails", testContentLengthTooShortFails), ("testBodyUploadAfterEndFails", testBodyUploadAfterEndFails), - + ("testDoubleError", testDoubleError), ("testSSLHandshakeErrorPropagation", testSSLHandshakeErrorPropagation), ("testSSLHandshakeErrorPropagationDelayedClose", testSSLHandshakeErrorPropagationDelayedClose), @@ -144,7 +144,7 @@ extension HTTPClientTests { ("testCloseWhileBackpressureIsExertedIsFine", testCloseWhileBackpressureIsExertedIsFine), ("testErrorAfterCloseWhileBackpressureExerted", testErrorAfterCloseWhileBackpressureExerted), ("testRequestSpecificTLS", testRequestSpecificTLS), - + ("testRequestWithHeaderTransferEncodingIdentityDoesNotFail", testRequestWithHeaderTransferEncodingIdentityDoesNotFail), ("testMassiveDownload", testMassiveDownload), ("testShutdownWithFutures", testShutdownWithFutures), diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index f1b773f65..031a6f56b 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -31,24 +31,24 @@ import XCTest class HTTPClientTestsBaseClass: XCTestCase { typealias Request = HTTPClient.Request - + var clientGroup: EventLoopGroup! var serverGroup: EventLoopGroup! var defaultHTTPBin: HTTPBin! var defaultClient: HTTPClient! var backgroundLogStore: CollectEverythingLogHandler.LogStore! - + var defaultHTTPBinURLPrefix: String { return "http://localhost:\(self.defaultHTTPBin.port)/" } - + override func setUp() { XCTAssertNil(self.clientGroup) XCTAssertNil(self.serverGroup) XCTAssertNil(self.defaultHTTPBin) XCTAssertNil(self.defaultClient) XCTAssertNil(self.backgroundLogStore) - + self.clientGroup = getDefaultEventLoopGroup(numberOfThreads: 1) self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) self.defaultHTTPBin = HTTPBin() @@ -61,25 +61,25 @@ class HTTPClientTestsBaseClass: XCTestCase { configuration: HTTPClient.Configuration().enableFastFailureModeForTesting(), backgroundActivityLogger: backgroundLogger) } - + override func tearDown() { if let defaultClient = self.defaultClient { XCTAssertNoThrow(try defaultClient.syncShutdown()) self.defaultClient = nil } - + XCTAssertNotNil(self.defaultHTTPBin) XCTAssertNoThrow(try self.defaultHTTPBin.shutdown()) self.defaultHTTPBin = nil - + XCTAssertNotNil(self.clientGroup) XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully()) self.clientGroup = nil - + XCTAssertNotNil(self.serverGroup) XCTAssertNoThrow(try self.serverGroup.syncShutdownGracefully()) self.serverGroup = nil - + XCTAssertNotNil(self.backgroundLogStore) self.backgroundLogStore = nil } @@ -93,30 +93,30 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(request1.url.query!, "foo=bar") XCTAssertEqual(request1.port, 8888) XCTAssertTrue(request1.useTLS) - + let request2 = try Request(url: "https://someserver.com") XCTAssertEqual(request2.url.path, "") - + let request3 = try Request(url: "unix:///tmp/file") XCTAssertNil(request3.url.host) XCTAssertEqual(request3.host, "") XCTAssertEqual(request3.url.path, "/tmp/file") XCTAssertEqual(request3.port, 80) XCTAssertFalse(request3.useTLS) - + let request4 = try Request(url: "http+unix://%2Ftmp%2Ffile/file/path") XCTAssertEqual(request4.host, "") XCTAssertEqual(request4.url.host, "/tmp/file") XCTAssertEqual(request4.url.path, "/file/path") XCTAssertFalse(request4.useTLS) - + let request5 = try Request(url: "https+unix://%2Ftmp%2Ffile/file/path") XCTAssertEqual(request5.host, "") XCTAssertEqual(request5.url.host, "/tmp/file") XCTAssertEqual(request5.url.path, "/file/path") XCTAssertTrue(request5.useTLS) } - + func testBadRequestURI() throws { XCTAssertThrowsError(try Request(url: "some/path"), "should throw") { error in XCTAssertEqual(error as! HTTPClientError, HTTPClientError.emptyScheme) @@ -131,14 +131,14 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(error as! HTTPClientError, HTTPClientError.missingSocketPath) } } - + func testSchemaCasing() throws { XCTAssertNoThrow(try Request(url: "hTTpS://someserver.com:8888/some/path?foo=bar")) XCTAssertNoThrow(try Request(url: "uNIx:///some/path")) XCTAssertNoThrow(try Request(url: "hTtP+uNIx://%2Fsome%2Fpath/")) XCTAssertNoThrow(try Request(url: "hTtPS+uNIx://%2Fsome%2Fpath/")) } - + func testURLSocketPathInitializers() throws { let url1 = URL(httpURLWithSocketPath: "/tmp/file") XCTAssertNotNil(url1) @@ -148,7 +148,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(url.path, "/") XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile/") } - + let url2 = URL(httpURLWithSocketPath: "/tmp/file", uri: "/file/path") XCTAssertNotNil(url2) if let url = url2 { @@ -157,7 +157,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile/file/path") } - + let url3 = URL(httpURLWithSocketPath: "/tmp/file", uri: "file/path") XCTAssertNotNil(url3) if let url = url3 { @@ -166,7 +166,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile/file/path") } - + let url4 = URL(httpURLWithSocketPath: "/tmp/file with spacesと漢字", uri: "file/path") XCTAssertNotNil(url4) if let url = url4 { @@ -175,7 +175,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "http+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path") } - + let url5 = URL(httpsURLWithSocketPath: "/tmp/file") XCTAssertNotNil(url5) if let url = url5 { @@ -184,7 +184,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(url.path, "/") XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile/") } - + let url6 = URL(httpsURLWithSocketPath: "/tmp/file", uri: "/file/path") XCTAssertNotNil(url6) if let url = url6 { @@ -193,7 +193,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile/file/path") } - + let url7 = URL(httpsURLWithSocketPath: "/tmp/file", uri: "file/path") XCTAssertNotNil(url7) if let url = url7 { @@ -202,7 +202,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile/file/path") } - + let url8 = URL(httpsURLWithSocketPath: "/tmp/file with spacesと漢字", uri: "file/path") XCTAssertNotNil(url8) if let url = url8 { @@ -211,14 +211,14 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(url.path, "/file/path") XCTAssertEqual(url.absoluteString, "https+unix://%2Ftmp%2Ffile%20with%20spaces%E3%81%A8%E6%BC%A2%E5%AD%97/file/path") } - + let url9 = URL(httpURLWithSocketPath: "/tmp/file", uri: " ") XCTAssertNil(url9) - + let url10 = URL(httpsURLWithSocketPath: "/tmp/file", uri: " ") XCTAssertNil(url10) } - + func testBadUnixWithBaseURL() { let badUnixBaseURL = URL(string: "/foo", relativeTo: URL(string: "unix:")!)! XCTAssertEqual(badUnixBaseURL.baseURL?.path, "") @@ -226,7 +226,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(error as! HTTPClientError, HTTPClientError.missingSocketPath) } } - + func testConvenienceExecuteMethods() throws { XCTAssertEqual(["GET"[...]], try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) @@ -243,14 +243,14 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(["CHECKOUT"[...]], try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) } - + func testConvenienceExecuteMethodsOverSocket() throws { XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) defer { XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertEqual(["GET"[...]], try self.defaultClient.execute(socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) XCTAssertEqual(["GET"[...]], @@ -259,7 +259,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { try self.defaultClient.execute(.POST, socketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) }) } - + func testConvenienceExecuteMethodsOverSecureSocket() throws { XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true, compress: false), bindTarget: .unixDomainSocket(path)) @@ -269,7 +269,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertEqual(["GET"[...]], try localClient.execute(secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) XCTAssertEqual(["GET"[...]], @@ -278,51 +278,51 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { try localClient.execute(.POST, secureSocketPath: path, urlPath: "echo-method").wait().headers[canonicalForm: "X-Method-Used"]) }) } - + func testGet() throws { let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait() XCTAssertEqual(.ok, response.status) } - + func testGetWithDifferentEventLoopBackpressure() throws { let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "events/10/1") let delegate = TestHTTPDelegate(backpressureEventLoop: self.serverGroup.next()) let task = self.defaultClient.execute(request: request, delegate: delegate) try task.wait() } - + func testPost() throws { let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .string("1234")).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("1234", data.data) } - + func testPostWithGenericBody() throws { let bodyData = Array("hello, world!").lazy.map { $0.uppercased().first!.asciiValue! } let erasedData = AnyRandomAccessCollection(bodyData) - + let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .bytes(erasedData)).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("HELLO, WORLD!", data.data) } - + func testPostWithFoundationDataBody() throws { let bodyData = Data("hello, world!".utf8) - + let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: .data(bodyData)).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("hello, world!", data.data) } - + func testGetHttps() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), @@ -331,11 +331,11 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + let response = try localClient.get(url: "https://localhost:\(localHTTPBin.port)/get").wait() XCTAssertEqual(.ok, response.status) } - + func testGetHttpsWithIP() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), @@ -344,11 +344,11 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + let response = try localClient.get(url: "https://127.0.0.1:\(localHTTPBin.port)/get").wait() XCTAssertEqual(.ok, response.status) } - + func testGetHTTPSWorksOnMTELGWithIP() throws { // Same test as above but this one will use NIO on Sockets even on Apple platforms, just to make sure // this works. @@ -363,11 +363,11 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + let response = try localClient.get(url: "https://127.0.0.1:\(localHTTPBin.port)/get").wait() XCTAssertEqual(.ok, response.status) } - + func testGetHttpsWithIPv6() throws { try XCTSkipUnless(canBindIPv6Loopback, "Requires IPv6") let localHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .localhostIPv6RandomPort) @@ -381,7 +381,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(response = try localClient.get(url: "https://[::1]:\(localHTTPBin.port)/get").wait()) XCTAssertEqual(.ok, response?.status) } - + func testGetHTTPSWorksOnMTELGWithIPv6() throws { try XCTSkipUnless(canBindIPv6Loopback, "Requires IPv6") // Same test as above but this one will use NIO on Sockets even on Apple platforms, just to make sure @@ -401,7 +401,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(response = try localClient.get(url: "https://[::1]:\(localHTTPBin.port)/get").wait()) XCTAssertEqual(.ok, response?.status) } - + func testPostHttps() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), @@ -410,33 +410,33 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + let request = try Request(url: "https://localhost:\(localHTTPBin.port)/post", method: .POST, body: .string("1234")) - + let response = try localClient.execute(request: request).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("1234", data.data) } - + func testHttpRedirect() throws { let httpsBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try httpsBin.shutdown()) } - + var response = try localClient.get(url: self.defaultHTTPBinURLPrefix + "redirect/302").wait() XCTAssertEqual(response.status, .ok) - + response = try localClient.get(url: self.defaultHTTPBinURLPrefix + "redirect/https?port=\(httpsBin.port)").wait() XCTAssertEqual(response.status, .ok) - + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { httpSocketPath in XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { httpsSocketPath in let socketHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(httpSocketPath)) @@ -445,96 +445,96 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try socketHTTPBin.shutdown()) XCTAssertNoThrow(try socketHTTPSBin.shutdown()) } - + // From HTTP or HTTPS to HTTP+UNIX should fail to redirect var targetURL = "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" var request = try Request(url: self.defaultHTTPBinURLPrefix + "redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + var response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .found) XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - + request = try Request(url: "https://localhost:\(httpsBin.port)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .found) XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - + // From HTTP or HTTPS to HTTPS+UNIX should also fail to redirect targetURL = "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: self.defaultHTTPBinURLPrefix + "redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .found) XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - + request = try Request(url: "https://localhost:\(httpsBin.port)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .found) XCTAssertEqual(response.headers.first(name: "Location"), targetURL) - + // ... while HTTP+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed targetURL = self.defaultHTTPBinURLPrefix + "ok" request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "https://localhost:\(httpsBin.port)/ok" request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + // ... and HTTPS+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed targetURL = self.defaultHTTPBinURLPrefix + "ok" request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "https://localhost:\(httpsBin.port)/ok" request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) - + targetURL = "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok" request = try Request(url: "https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/redirect/target", method: .GET, headers: ["X-Target-Redirect-URL": targetURL], body: nil) - + response = try localClient.execute(request: request).wait() XCTAssertEqual(response.status, .ok) }) }) } - + func testHttpHostRedirect() { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + let url = self.defaultHTTPBinURLPrefix + "redirect/loopback?port=\(self.defaultHTTPBin.port)" var maybeResponse: HTTPClient.Response? XCTAssertNoThrow(maybeResponse = try localClient.get(url: url).wait()) @@ -545,20 +545,20 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { let hostName = try? JSONDecoder().decode(RequestInfo.self, from: body).data XCTAssertEqual("127.0.0.1:\(self.defaultHTTPBin.port)", hostName) } - + func testPercentEncoded() throws { let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "percent%20encoded").wait() XCTAssertEqual(.ok, response.status) } - + func testPercentEncodedBackslash() throws { let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "percent%2Fencoded/hello").wait() XCTAssertEqual(.ok, response.status) } - + func testMultipleContentLengthHeaders() throws { let body = ByteBuffer(string: "hello world!") - + var headers = HTTPHeaders() headers.add(name: "Content-Length", value: "12") let request = try Request(url: self.defaultHTTPBinURLPrefix + "post", method: .POST, headers: headers, body: .byteBuffer(body)) @@ -566,95 +566,95 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { // if the library adds another content length header we'll get a bad request error. XCTAssertEqual(.ok, response.status) } - + func testStreaming() throws { var request = try Request(url: self.defaultHTTPBinURLPrefix + "events/10/1") request.headers.add(name: "Accept", value: "text/event-stream") - + let delegate = CountingDelegate() let count = try self.defaultClient.execute(request: request, delegate: delegate).wait() - + XCTAssertEqual(10, count) } - + func testFileDownload() throws { var request = try Request(url: self.defaultHTTPBinURLPrefix + "events/10/content-length") request.headers.add(name: "Accept", value: "text/event-stream") - + let progress = - try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in - let delegate = try FileDownloadDelegate(path: path) - - let progress = try self.defaultClient.execute( - request: request, - delegate: delegate - ) + try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in + let delegate = try FileDownloadDelegate(path: path) + + let progress = try self.defaultClient.execute( + request: request, + delegate: delegate + ) .wait() - - try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path)) - - return progress - } - + + try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path)) + + return progress + } + XCTAssertEqual(50, progress.totalBytes) XCTAssertEqual(50, progress.receivedBytes) } - + func testFileDownloadError() throws { var request = try Request(url: self.defaultHTTPBinURLPrefix + "not-found") request.headers.add(name: "Accept", value: "text/event-stream") - + let progress = - try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in - let delegate = try FileDownloadDelegate(path: path, reportHead: { - XCTAssertEqual($0.status, .notFound) - }) - - let progress = try self.defaultClient.execute( - request: request, - delegate: delegate - ) + try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in + let delegate = try FileDownloadDelegate(path: path, reportHead: { + XCTAssertEqual($0.status, .notFound) + }) + + let progress = try self.defaultClient.execute( + request: request, + delegate: delegate + ) .wait() - - XCTAssertFalse(TemporaryFileHelpers.fileExists(path: path)) - - return progress - } - + + XCTAssertFalse(TemporaryFileHelpers.fileExists(path: path)) + + return progress + } + XCTAssertEqual(nil, progress.totalBytes) XCTAssertEqual(0, progress.receivedBytes) } - + func testRemoteClose() { XCTAssertThrowsError(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "close").wait()) { XCTAssertEqual($0 as? HTTPClientError, .remoteConnectionClosed) } } - + func testReadTimeout() { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(read: .milliseconds(150)))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + XCTAssertThrowsError(try localClient.get(url: self.defaultHTTPBinURLPrefix + "wait").wait()) { XCTAssertEqual($0 as? HTTPClientError, .readTimeout) } } - + func testConnectTimeout() throws { -#if os(Linux) + #if os(Linux) // 198.51.100.254 is reserved for documentation only and therefore should not accept any TCP connection let url = "http://198.51.100.254/get" -#else + #else // on macOS we can use the TCP backlog behaviour when the queue is full to simulate a non reachable server. // this makes this test a bit more stable if `198.51.100.254` actually responds to connection attempt. // The backlog behaviour on Linux can not be used to simulate a non-reachable server. // Linux sends a `SYN/ACK` back even if the `backlog` queue is full as it has two queues. // The second queue is not limit by `ChannelOptions.backlog` but by `/proc/sys/net/ipv4/tcp_max_syn_backlog`. - + let serverChannel = try ServerBootstrap(group: self.serverGroup) .serverChannelOption(ChannelOptions.backlog, value: 1) .serverChannelOption(ChannelOptions.autoRead, value: false) @@ -671,42 +671,42 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try firstClientChannel.close().wait()) } let url = "http://localhost:\(port)/get" -#endif - + #endif + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(timeout: .init(connect: .milliseconds(100), read: .milliseconds(150)))) - + defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + XCTAssertThrowsError(try httpClient.get(url: url).wait()) { XCTAssertEqualTypeAndValue($0, HTTPClientError.connectTimeout) } } - + func testDeadline() { XCTAssertThrowsError(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "wait", deadline: .now() + .milliseconds(150)).wait()) { XCTAssertEqual($0 as? HTTPClientError, .deadlineExceeded) } } - + func testCancel() throws { let queue = DispatchQueue(label: "nio-test") let request = try Request(url: self.defaultHTTPBinURLPrefix + "wait") let task = self.defaultClient.execute(request: request, delegate: TestHTTPDelegate()) - + queue.asyncAfter(deadline: .now() + .milliseconds(100)) { task.cancel() } - + XCTAssertThrowsError(try task.wait(), "Should fail") { error in guard case let error = error as? HTTPClientError, error == .cancelled else { return XCTFail("Should fail with cancelled") } } } - + func testStressCancel() throws { let request = try Request(url: self.defaultHTTPBinURLPrefix + "wait", method: .GET) let tasks = (1...100).map { _ -> HTTPClient.Task in @@ -714,7 +714,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { task.cancel() return task } - + for task in tasks { switch (Result { try task.futureResult.timeout(after: .seconds(10)).wait() }) { case .success: @@ -728,15 +728,15 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + func testHTTPClientAuthorization() { var authorization = HTTPClient.Authorization.basic(username: "aladdin", password: "opensesame") XCTAssertEqual(authorization.headerValue, "Basic YWxhZGRpbjpvcGVuc2VzYW1l") - + authorization = HTTPClient.Authorization.bearer(tokens: "mF_9.B5f-4.1JqM") XCTAssertEqual(authorization.headerValue, "Bearer mF_9.B5f-4.1JqM") } - + func testProxyPlaintext() throws { let localHTTPBin = HTTPBin(proxy: .simulate(authorization: nil)) let localClient = HTTPClient( @@ -750,7 +750,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { let res = try localClient.get(url: "http://test/ok").wait() XCTAssertEqual(res.status, .ok) } - + func testProxyTLS() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true), proxy: .simulate(authorization: nil)) let localClient = HTTPClient( @@ -767,7 +767,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { let res = try localClient.get(url: "https://test/ok").wait() XCTAssertEqual(res.status, .ok) } - + func testProxyPlaintextWithCorrectlyAuthorization() throws { let localHTTPBin = HTTPBin(proxy: .simulate(authorization: "Basic YWxhZGRpbjpvcGVuc2VzYW1l")) let localClient = HTTPClient( @@ -781,7 +781,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { let res = try localClient.get(url: "http://test/ok").wait() XCTAssertEqual(res.status, .ok) } - + func testProxyPlaintextWithIncorrectlyAuthorization() throws { let localHTTPBin = HTTPBin(proxy: .simulate(authorization: "Basic YWxhZGRpbjpvcGVuc2VzYW1l")) let localClient = HTTPClient( @@ -792,7 +792,8 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { port: localHTTPBin.port, authorization: .basic( username: "aladdin", - password: "opensesamefoo") + password: "opensesamefoo" + ) ) ).enableFastFailureModeForTesting() ) @@ -806,7 +807,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + func testUploadStreaming() throws { let body: HTTPClient.Body = .stream(length: 8) { writer in let buffer = ByteBuffer(string: "1234") @@ -815,80 +816,80 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { return writer.write(.byteBuffer(buffer)) } } - + let response = try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + "post", body: body).wait() let bytes = response.body.flatMap { $0.getData(at: 0, length: $0.readableBytes) } let data = try JSONDecoder().decode(RequestInfo.self, from: bytes!) - + XCTAssertEqual(.ok, response.status) XCTAssertEqual("12344321", data.data) } - + func testEventLoopArgument() throws { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(redirectConfiguration: .follow(max: 10, allowCycles: true))) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + class EventLoopValidatingDelegate: HTTPClientResponseDelegate { typealias Response = Bool - + let eventLoop: EventLoop var result = false - + init(eventLoop: EventLoop) { self.eventLoop = eventLoop } - + func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) -> EventLoopFuture { self.result = task.eventLoop === self.eventLoop return task.eventLoop.makeSucceededFuture(()) } - + func didFinishRequest(task: HTTPClient.Task) throws -> Bool { return self.result } } - + let eventLoop = self.clientGroup.next() let delegate = EventLoopValidatingDelegate(eventLoop: eventLoop) var request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get") var response = try localClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: eventLoop)).wait() XCTAssertEqual(true, response) - + // redirect request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "redirect/302") response = try localClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: eventLoop)).wait() XCTAssertEqual(true, response) } - + func testDecompression() throws { let localHTTPBin = HTTPBin(.http1_1(compress: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(decompression: .enabled(limit: .none))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + var body = "" for _ in 1...1000 { body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." } - + for algorithm in [nil, "gzip", "deflate"] { var request = try HTTPClient.Request(url: "http://localhost:\(localHTTPBin.port)/post", method: .POST) request.body = .string(body) if let algorithm = algorithm { request.headers.add(name: "Accept-Encoding", value: algorithm) } - + let response = try localClient.execute(request: request).wait() let bytes = response.body!.getData(at: 0, length: response.body!.readableBytes)! let data = try JSONDecoder().decode(RequestInfo.self, from: bytes) - + XCTAssertEqual(.ok, response.status) XCTAssertGreaterThan(body.count, response.headers["Content-Length"].first.flatMap { Int($0) }!) if let algorithm = algorithm { @@ -899,7 +900,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(body, data.data) } } - + func testDecompressionHTTP2() throws { let localHTTPBin = HTTPBin(.http2(compress: true)) let localClient = HTTPClient( @@ -909,28 +910,28 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { decompression: .enabled(limit: .none) ) ) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + var body = "" for _ in 1...1000 { body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." } - + for algorithm: String? in [nil] { var request = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/post", method: .POST) request.body = .string(body) if let algorithm = algorithm { request.headers.add(name: "Accept-Encoding", value: algorithm) } - + let response = try localClient.execute(request: request).wait() var responseBody = try XCTUnwrap(response.body) let data = try responseBody.readJSONDecodable(RequestInfo.self, length: responseBody.readableBytes) - + XCTAssertEqual(.ok, response.status) let contentLength = try XCTUnwrap(response.headers["Content-Length"].first.flatMap { Int($0) }) XCTAssertGreaterThan(body.count, contentLength) @@ -942,55 +943,55 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(body, data?.data) } } - + func testDecompressionLimit() throws { let localHTTPBin = HTTPBin(.http1_1(compress: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(decompression: .enabled(limit: .ratio(1)))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + var request = try HTTPClient.Request(url: "http://localhost:\(localHTTPBin.port)/post", method: .POST) request.body = .byteBuffer(ByteBuffer(bytes: [120, 156, 75, 76, 28, 5, 200, 0, 0, 248, 66, 103, 17])) request.headers.add(name: "Accept-Encoding", value: "deflate") - + XCTAssertThrowsError(try localClient.execute(request: request).wait()) { XCTAssertEqual($0 as? NIOHTTPDecompression.DecompressionError, .limit) } } - + func testLoopDetectionRedirectLimit() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 5, allowCycles: false))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectCycleDetected) } } - + func testCountRedirectLimit() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/redirect/infinite1").timeout(after: .seconds(10)).wait()) { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectLimitReached) } } - + func testRedirectToTheInitialURLDoesThrowOnFirstRedirect() throws { let localHTTPBin = HTTPBin(.http1_1(ssl: true)) defer { XCTAssertNoThrow(try localHTTPBin.shutdown()) } @@ -1002,7 +1003,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { ) ) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + var maybeRequest: HTTPClient.Request? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( url: "https://localhost:\(localHTTPBin.port)/redirect/target", @@ -1012,22 +1013,22 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { ] )) guard let request = maybeRequest else { return } - + XCTAssertThrowsError( try localClient.execute(request: request).timeout(after: .seconds(10)).wait() ) { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectCycleDetected) } } - + func testMultipleConcurrentRequests() throws { let numberOfRequestsPerThread = 1000 let numberOfParallelWorkers = 5 - + final class HTTPServer: ChannelInboundHandler { typealias InboundIn = HTTPServerRequestPart typealias OutboundOut = HTTPServerResponsePart - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { if case .end = self.unwrapInboundIn(data) { let responseHead = HTTPServerResponsePart.head(.init(version: .init(major: 1, minor: 1), @@ -1037,12 +1038,12 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + let group = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } - + var server: Channel? XCTAssertNoThrow(server = try ServerBootstrap(group: group) .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1) @@ -1059,7 +1060,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { defer { XCTAssertNoThrow(try server?.close().wait()) } - + let url = "http://127.0.0.1:\(server?.localAddress?.port ?? -1)/hello" let g = DispatchGroup() for workerID in 0.. HTTPClient.Task in localClient.execute(request: request, delegate: TestHTTPDelegate()) } - + let results = try EventLoopFuture.whenAllComplete(tasks.map { $0.futureResult }, on: localClient.eventLoopGroup.next()).wait() - + for result in results { switch result { case .success: @@ -1240,7 +1240,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { continue case .failure(let error): if isTestingNIOTS() { -#if canImport(Network) + #if canImport(Network) guard let clientError = error as? HTTPClient.NWTLSError else { XCTFail("Unexpected error: \(error)") continue @@ -1249,9 +1249,9 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { // that the bytes "HTTP/1.1" aren't the start of a valid TLS packet, we can also get // errSSLPeerProtocolVersion because the first bytes contain the version. XCTAssert(clientError.status == errSSLHandshakeFail || - clientError.status == errSSLPeerProtocolVersion, - "unexpected NWTLSError with status \(clientError.status)") -#endif + clientError.status == errSSLPeerProtocolVersion, + "unexpected NWTLSError with status \(clientError.status)") + #endif } else { guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else { XCTFail("Unexpected error: \(error)") @@ -1261,7 +1261,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + func testSelfSignedCertificateIsRejectedWithCorrectError() throws { /// key + cert was created with the follwing command: /// openssl req -x509 -newkey rsa:4096 -keyout self_signed_key.pem -out self_signed_cert.pem -sha256 -days 99999 -nodes -subj '/CN=localhost' @@ -1272,7 +1272,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { privateKey: .file(keyPath) ) let sslContext = try NIOSSLContext(configuration: configuration) - + let server = ServerBootstrap(group: serverGroup) .childChannelInitializer { channel in channel.pipeline.addHandler(NIOSSLServerHandler(context: sslContext)) @@ -1280,28 +1280,28 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { let serverChannel = try server.bind(host: "localhost", port: 0).wait() defer { XCTAssertNoThrow(try serverChannel.close().wait()) } let port = serverChannel.localAddress!.port! - + let config = HTTPClient.Configuration().enableFastFailureModeForTesting() - + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(port)").wait()) { error in -#if canImport(Network) + #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)") return } XCTAssertEqual(nwTLSError.status, errSSLBadCert, "unexpected tls error: \(nwTLSError)") -#else + #else guard let sslError = error as? NIOSSLError, case .handshakeFailed(.sslError) = sslError else { XCTFail("unexpected error \(error)") return } -#endif + #endif } } - + func testSelfSignedCertificateIsRejectedWithCorrectErrorIfRequestDeadlineIsExceeded() throws { /// key + cert was created with the follwing command: /// openssl req -x509 -newkey rsa:4096 -keyout self_signed_key.pem -out self_signed_cert.pem -sha256 -days 99999 -nodes -subj '/CN=localhost' @@ -1312,7 +1312,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { privateKey: .file(keyPath) ) let sslContext = try NIOSSLContext(configuration: configuration) - + let server = ServerBootstrap(group: serverGroup) .childChannelInitializer { channel in channel.pipeline.addHandler(NIOSSLServerHandler(context: sslContext)) @@ -1320,29 +1320,29 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { let serverChannel = try server.bind(host: "localhost", port: 0).wait() defer { XCTAssertNoThrow(try serverChannel.close().wait()) } let port = serverChannel.localAddress!.port! - + let config = HTTPClient.Configuration().enableFastFailureModeForTesting() - + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(port)", deadline: .now() + .seconds(2)).wait()) { error in -#if canImport(Network) + #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)") return } XCTAssertEqual(nwTLSError.status, errSSLBadCert, "unexpected tls error: \(nwTLSError)") -#else + #else guard let sslError = error as? NIOSSLError, case .handshakeFailed(.sslError) = sslError else { XCTFail("unexpected error \(error)") return } -#endif + #endif } } - + func testFailingConnectionIsReleased() { let localHTTPBin = HTTPBin(.refuse) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) @@ -1360,7 +1360,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + func testStressGetClose() throws { let eventLoop = self.defaultClient.eventLoopGroup.next() let requestCount = 200 @@ -1374,24 +1374,24 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try EventLoopFuture.andAllComplete(futureResults, on: eventLoop) .timeout(after: .seconds(10)).wait()) } - + func testManyConcurrentRequestsWork() { let numberOfWorkers = 20 let numberOfRequestsPerWorkers = 20 let allWorkersReady = DispatchSemaphore(value: 0) let allWorkersGo = DispatchSemaphore(value: 0) let allDone = DispatchGroup() - + let url = self.defaultHTTPBinURLPrefix + "get" XCTAssertEqual(.ok, try self.defaultClient.get(url: url).wait().status) - + for w in 0..]() for i in 1...100 { let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .GET, headers: ["X-internal-delay": "10"]) @@ -1595,9 +1595,9 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } futureResults.append(client.execute(request: request, eventLoop: preference)) } - + let results = try EventLoopFuture.whenAllComplete(futureResults, on: elg.next()).wait() - + for result in results { switch result { case .success: @@ -1607,7 +1607,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + func testMakeSecondRequestDuringCancelledCallout() { let el = self.clientGroup.next() let web = NIOHTTP1TestServer(group: self.serverGroup.next()) @@ -1615,7 +1615,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { // This will throw as we've started the request but haven't fulfilled it. XCTAssertThrowsError(try web.stop()) } - + let url = "http://127.0.0.1:\(web.serverPort)" let localClient = HTTPClient(eventLoopGroupProvider: .shared(el)) defer { @@ -1623,7 +1623,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(.alreadyShutdown, error as? HTTPClientError) } } - + let seenError = DispatchGroup() seenError.enter() var maybeSecondRequest: EventLoopFuture? @@ -1636,23 +1636,23 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } return secondRequest }.wait()) - + guard let secondRequest = maybeSecondRequest else { XCTFail("couldn't get request future") return } - + // Let's pull out the request .head so we know the request has started (but nothing else) XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) - + XCTAssertNoThrow(try localClient.syncShutdown()) - + seenError.wait() XCTAssertThrowsError(try secondRequest.wait()) { error in XCTAssertEqual(.alreadyShutdown, error as? HTTPClientError) } } - + func testMakeSecondRequestDuringSuccessCallout() { let el = self.clientGroup.next() let url = "http://127.0.0.1:\(self.defaultHTTPBin.port)/get" @@ -1660,50 +1660,50 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + XCTAssertEqual(.ok, try el.flatSubmit { () -> EventLoopFuture in - localClient.get(url: url).flatMap { firstResponse in - XCTAssertEqual(.ok, firstResponse.status) - return localClient.get(url: url) // <== interesting bit here - } - }.wait().status) + localClient.get(url: url).flatMap { firstResponse in + XCTAssertEqual(.ok, firstResponse.status) + return localClient.get(url: url) // <== interesting bit here + } + }.wait().status) } - + func testMakeSecondRequestWhilstFirstIsOngoing() { let web = NIOHTTP1TestServer(group: self.serverGroup) defer { XCTAssertNoThrow(try web.stop()) } - + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + let url = "http://127.0.0.1:\(web.serverPort)" let firstRequest = client.get(url: url) - + XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) // first request: .head - + // Now, the first request is ongoing but not complete, let's start a second one let secondRequest = client.get(url: url) XCTAssertEqual(.end(nil), try web.readInbound()) // first request: .end - + XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) - + XCTAssertEqual(.ok, try firstRequest.wait().status) - + // Okay, first request done successfully, let's do the second one too. XCTAssertNoThrow(XCTAssertNotNil(try web.readInbound())) // first request: .head XCTAssertEqual(.end(nil), try web.readInbound()) // first request: .end - + XCTAssertNoThrow(try web.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .created)))) XCTAssertNoThrow(try web.writeOutbound(.end(nil))) XCTAssertEqual(.created, try secondRequest.wait().status) } - + func testUDSBasic() { // This tests just connecting to a URL where the whole URL is the UNIX domain socket path like // unix:///this/is/my/socket.sock @@ -1718,7 +1718,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { try self.defaultClient.get(url: target).wait().headers[canonicalForm: "X-Is-This-Slash"]) }) } - + func testUDSSocketAndPath() { // Here, we're testing a URL that's encoding two different paths: // @@ -1738,7 +1738,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { try self.defaultClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"]) }) } - + func testHTTPPlusUNIX() { // Here, we're testing a URL where the UNIX domain socket is encoded as the host name XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in @@ -1755,7 +1755,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { try self.defaultClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"]) }) } - + func testHTTPSPlusUNIX() { // Here, we're testing a URL where the UNIX domain socket is encoded as the host name XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in @@ -1775,7 +1775,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { try localClient.execute(request: request).wait().headers[canonicalForm: "X-Calling-URI"]) }) } - + func testUseExistingConnectionOnDifferentEL() throws { let threadCount = 16 let elg = getDefaultEventLoopGroup(numberOfThreads: threadCount) @@ -1784,11 +1784,11 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try elg.syncShutdownGracefully()) } - + let eventLoops = (1...threadCount).map { _ in elg.next() } let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get") let closingRequest = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", headers: ["Connection": "close"]) - + for (index, el) in eventLoops.enumerated() { if index.isMultiple(of: 2) { XCTAssertNoThrow(try localClient.execute(request: request, eventLoop: .delegateAndChannel(on: el)).wait()) @@ -1798,27 +1798,27 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + func testWeRecoverFromServerThatClosesTheConnectionOnUs() { final class ServerThatAcceptsThenRejects: ChannelInboundHandler { typealias InboundIn = HTTPServerRequestPart typealias OutboundOut = HTTPServerResponsePart - + let requestNumber: ManagedAtomic let connectionNumber: ManagedAtomic - + init(requestNumber: ManagedAtomic, connectionNumber: ManagedAtomic) { self.requestNumber = requestNumber self.connectionNumber = connectionNumber } - + func channelActive(context: ChannelHandlerContext) { _ = self.connectionNumber.loadThenWrappingIncrement(ordering: .relaxed) } - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { let req = self.unwrapInboundIn(data) - + switch req { case .head, .body: () @@ -1837,7 +1837,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + let requestNumber = ManagedAtomic(0) let connectionNumber = ManagedAtomic(0) let sharedStateServerHandler = ServerThatAcceptsThenRejects(requestNumber: requestNumber, @@ -1861,13 +1861,13 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { defer { XCTAssertNoThrow(try server.close().wait()) } - + let url = "http://127.0.0.1:\(server.localAddress!.port!)" let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + XCTAssertEqual(0, sharedStateServerHandler.connectionNumber.load(ordering: .relaxed)) XCTAssertEqual(0, sharedStateServerHandler.requestNumber.load(ordering: .relaxed)) XCTAssertEqual(.ok, try client.get(url: url).wait().status) @@ -1882,7 +1882,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(2, sharedStateServerHandler.connectionNumber.load(ordering: .relaxed)) XCTAssertEqual(3, sharedStateServerHandler.requestNumber.load(ordering: .relaxed)) } - + func testPoolClosesIdleConnections() { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(connectionPool: .init(idleTimeout: .milliseconds(100)))) @@ -1893,7 +1893,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { Thread.sleep(forTimeInterval: 0.2) XCTAssertEqual(self.defaultHTTPBin.activeConnections, 0) } - + func testAvoidLeakingTLSHandshakeCompletionPromise() { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(timeout: .init(connect: .milliseconds(100)))) let localHTTPBin = HTTPBin() @@ -1902,21 +1902,21 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { defer { XCTAssertNoThrow(try localClient.syncShutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "http://localhost:\(port)").wait()) { error in if isTestingNIOTS() { -#if canImport(Network) + #if canImport(Network) // We can't be more specific than this. XCTAssertTrue(error is HTTPClient.NWTLSError || error is HTTPClient.NWPOSIXError) -#else + #else XCTFail("Impossible condition") -#endif + #endif } else { XCTAssert(error is NIOConnectionError, "Unexpected error: \(error)") } } } - + func testAsyncShutdown() throws { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) let promise = self.clientGroup.next().makePromise(of: Void.self) @@ -1928,7 +1928,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } XCTAssertNoThrow(try promise.futureResult.wait()) } - + func testAsyncShutdownDefaultQueue() throws { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) let promise = self.clientGroup.next().makePromise(of: Void.self) @@ -1940,7 +1940,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } XCTAssertNoThrow(try promise.futureResult.wait()) } - + func testValidationErrorsAreSurfaced() throws { let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .TRACE, body: .stream { _ in self.defaultClient.eventLoopGroup.next().makeSucceededFuture(()) @@ -1950,18 +1950,18 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(HTTPClientError.traceRequestWithBody, error as? HTTPClientError) } } - + func testUploadsReallyStream() { final class HTTPServer: ChannelInboundHandler { typealias InboundIn = HTTPServerRequestPart typealias OutboundOut = HTTPServerResponsePart - + private let headPromise: EventLoopPromise private let bodyPromises: [EventLoopPromise] private let endPromise: EventLoopPromise private var bodyPartsSeenSoFar = 0 private var atEnd = false - + init(headPromise: EventLoopPromise, bodyPromises: [EventLoopPromise], endPromise: EventLoopPromise) { @@ -1969,7 +1969,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { self.bodyPromises = bodyPromises self.endPromise = endPromise } - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { switch self.unwrapInboundIn(data) { case .head(let head): @@ -1986,13 +1986,13 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { self.atEnd = true } } - + func handlerRemoved(context: ChannelHandlerContext) { guard !self.atEnd else { return } struct NotFulfilledError: Error {} - + self.headPromise.fail(NotFulfilledError()) self.bodyPromises.forEach { $0.fail(NotFulfilledError()) @@ -2000,7 +2000,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { self.endPromise.fail(NotFulfilledError()) } } - + let group = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) @@ -2014,7 +2014,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { let endPromise = group.next().makePromise(of: Void.self) let sentOffAllBodyPartsPromise = group.next().makePromise(of: Void.self) let streamWriterPromise = group.next().makePromise(of: HTTPClient.Body.StreamWriter.self) - + func makeServer() -> Channel? { return try? ServerBootstrap(group: group) .childChannelInitializer { channel in @@ -2028,21 +2028,21 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { .bind(host: "127.0.0.1", port: 0) .wait() } - + func makeRequest(server: Channel) -> Request? { guard let localAddress = server.localAddress else { return nil } - + return try? HTTPClient.Request(url: "http://\(localAddress.ipAddress!):\(localAddress.port!)", method: .POST, headers: ["transfer-encoding": "chunked"], body: .stream { streamWriter in - streamWriterPromise.succeed(streamWriter) - return sentOffAllBodyPartsPromise.futureResult - }) + streamWriterPromise.succeed(streamWriter) + return sentOffAllBodyPartsPromise.futureResult + }) } - + guard let server = makeServer(), let request = makeRequest(server: server) else { XCTFail("couldn't make a server Channel and a matching Request...") return @@ -2050,14 +2050,14 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { defer { XCTAssertNoThrow(try server.close().wait()) } - + var buffer = ByteBufferAllocator().buffer(capacity: 1) let runningRequest = client.execute(request: request) guard let streamWriter = try? streamWriterPromise.futureResult.wait() else { XCTFail("didn't get StreamWriter") return } - + XCTAssertEqual(.POST, try headPromise.futureResult.wait().method) for bodyChunkNumber in 0..<16 { buffer.clear() @@ -2070,7 +2070,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try endPromise.futureResult.wait()) XCTAssertNoThrow(try runningRequest.wait()) } - + func testUploadStreamingCallinToleratedFromOtsideEL() throws { let request = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .POST, body: .stream(length: 4) { writer in let promise = self.defaultClient.eventLoopGroup.next().makePromise(of: Void.self) @@ -2084,13 +2084,13 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { }) XCTAssertNoThrow(try self.defaultClient.execute(request: request).wait()) } - + func testWeHandleUsSendingACloseHeaderCorrectly() { guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", method: .GET, headers: ["connection": "close"]), - let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { XCTFail("request 1 didn't work") return } @@ -2104,23 +2104,23 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTFail("request 3 didn't work") return } - + // req 1 and 2 cannot share the same connection (close header) XCTAssertEqual(stats1.connectionNumber + 1, stats2.connectionNumber) XCTAssertEqual(stats1.requestNumber, 1) XCTAssertEqual(stats2.requestNumber, 1) - + // req 2 and 3 should share the same connection (keep-alive is default) XCTAssertEqual(stats2.requestNumber + 1, stats3.requestNumber) XCTAssertEqual(stats2.connectionNumber, stats3.connectionNumber) } - + func testWeHandleUsReceivingACloseHeaderCorrectly() { guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", method: .GET, headers: ["X-Send-Back-Header-Connection": "close"]), - let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { XCTFail("request 1 didn't work") return } @@ -2134,25 +2134,25 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTFail("request 3 didn't work") return } - + // req 1 and 2 cannot share the same connection (close header) XCTAssertEqual(stats1.connectionNumber + 1, stats2.connectionNumber) XCTAssertEqual(stats1.requestNumber, 1) XCTAssertEqual(stats2.requestNumber, 1) - + // req 2 and 3 should share the same connection (keep-alive is default) XCTAssertEqual(stats2.requestNumber + 1, stats3.requestNumber) XCTAssertEqual(stats2.connectionNumber, stats3.connectionNumber) } - + func testWeHandleUsSendingACloseHeaderAmongstOtherConnectionHeadersCorrectly() { for closeHeader in [("connection", "close"), ("CoNneCTION", "ClOSe")] { guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", method: .GET, headers: ["X-Send-Back-Header-\(closeHeader.0)": - "foo,\(closeHeader.1),bar"]), - let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + "foo,\(closeHeader.1),bar"]), + let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { XCTFail("request 1 didn't work") return } @@ -2166,25 +2166,25 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTFail("request 3 didn't work") return } - + // req 1 and 2 cannot share the same connection (close header) XCTAssertEqual(stats1.connectionNumber + 1, stats2.connectionNumber) XCTAssertEqual(stats2.requestNumber, 1) - + // req 2 and 3 should share the same connection (keep-alive is default) XCTAssertEqual(stats2.requestNumber + 1, stats3.requestNumber) XCTAssertEqual(stats2.connectionNumber, stats3.connectionNumber) } } - + func testWeHandleUsReceivingACloseHeaderAmongstOtherConnectionHeadersCorrectly() { for closeHeader in [("connection", "close"), ("CoNneCTION", "ClOSe")] { guard let req1 = try? Request(url: self.defaultHTTPBinURLPrefix + "stats", method: .GET, headers: ["X-Send-Back-Header-\(closeHeader.0)": - "foo,\(closeHeader.1),bar"]), - let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, - let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { + "foo,\(closeHeader.1),bar"]), + let statsBytes1 = try? self.defaultClient.execute(request: req1).wait().body, + let stats1 = try? JSONDecoder().decode(RequestInfo.self, from: statsBytes1) else { XCTFail("request 1 didn't work") return } @@ -2198,26 +2198,26 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTFail("request 3 didn't work") return } - + // req 1 and 2 cannot share the same connection (close header) XCTAssertEqual(stats1.connectionNumber + 1, stats2.connectionNumber) XCTAssertEqual(stats2.requestNumber, 1) - + // req 2 and 3 should share the same connection (keep-alive is default) XCTAssertEqual(stats2.requestNumber + 1, stats3.requestNumber) XCTAssertEqual(stats2.connectionNumber, stats3.connectionNumber) } } - + func testLoggingCorrectlyAttachesRequestInformationEvenAfterDuringRedirect() { let logStore = CollectEverythingLogHandler.LogStore() - + var logger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: logStore) }) logger.logLevel = .trace logger[metadataKey: "custom-request-id"] = "abcd" - + var maybeRequest: HTTPClient.Request? XCTAssertNoThrow(maybeRequest = try HTTPClient.Request( url: "http://localhost:\(self.defaultHTTPBin.port)/redirect/target", @@ -2227,7 +2227,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { ] )) guard let request = maybeRequest else { return } - + XCTAssertNoThrow(try self.defaultClient.execute( request: request, eventLoop: .indifferent, @@ -2235,29 +2235,29 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { logger: logger ).wait()) let logs = logStore.allEntries - + XCTAssertTrue(logs.allSatisfy { $0.metadata["custom-request-id"] == "abcd" }) - + guard let firstRequestID = logs.first?.metadata["ahc-request-id"] else { return XCTFail("could not get first request ID") } guard let lastRequestID = logs.last?.metadata["ahc-request-id"] else { return XCTFail("could not get second request ID") } - + let firstRequestLogs = logs.prefix(while: { $0.metadata["ahc-request-id"] == firstRequestID }) XCTAssertGreaterThan(firstRequestLogs.count, 0) - + let secondRequestLogs = logs.drop(while: { $0.metadata["ahc-request-id"] == firstRequestID }) XCTAssertGreaterThan(secondRequestLogs.count, 0) XCTAssertTrue(secondRequestLogs.allSatisfy { $0.metadata["ahc-request-id"] == lastRequestID }) - + logs.forEach { print($0) } } - + func testLoggingCorrectlyAttachesRequestInformation() { let logStore = CollectEverythingLogHandler.LogStore() - + var loggerYolo001 = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: logStore) }) @@ -2268,14 +2268,14 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { }) loggerACME002.logLevel = .trace loggerACME002[metadataKey: "acme-request-id"] = "acme-002" - + guard let request1 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get"), let request2 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "stats"), let request3 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "ok") else { XCTFail("bad stuff, can't even make request structures") return } - + // === Request 1 (Yolo001) XCTAssertNoThrow(try self.defaultClient.execute(request: request1, eventLoop: .indifferent, @@ -2283,7 +2283,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { logger: loggerYolo001).wait()) let logsAfterReq1 = logStore.allEntries logStore.allEntries = [] - + // === Request 2 (Yolo001) XCTAssertNoThrow(try self.defaultClient.execute(request: request2, eventLoop: .indifferent, @@ -2291,7 +2291,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { logger: loggerYolo001).wait()) let logsAfterReq2 = logStore.allEntries logStore.allEntries = [] - + // === Request 3 (ACME002) XCTAssertNoThrow(try self.defaultClient.execute(request: request3, eventLoop: .indifferent, @@ -2299,12 +2299,12 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { logger: loggerACME002).wait()) let logsAfterReq3 = logStore.allEntries logStore.allEntries = [] - + // === Assertions XCTAssertGreaterThan(logsAfterReq1.count, 0) XCTAssertGreaterThan(logsAfterReq2.count, 0) XCTAssertGreaterThan(logsAfterReq3.count, 0) - + XCTAssert(logsAfterReq1.allSatisfy { entry in if let httpRequestMetadata = entry.metadata["ahc-request-id"], let yoloRequestID = entry.metadata["yolo-request-id"] { @@ -2321,16 +2321,16 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { // Since a new connection must be created first we expect that the request is queued // and log message describing this is emitted. entry.message == "Request was queued (waiting for a connection to become available)" - && entry.level == .debug + && entry.level == .debug }) XCTAssert(logsAfterReq1.contains { entry in // After the new connection was created we expect a log message that describes that the // request was scheduled on a connection. The connection id must be set from here on. entry.message == "Request was scheduled on connection" - && entry.level == .debug - && entry.metadata["ahc-connection-id"] != nil + && entry.level == .debug + && entry.metadata["ahc-connection-id"] != nil }) - + XCTAssert(logsAfterReq2.allSatisfy { entry in if let httpRequestMetadata = entry.metadata["ahc-request-id"], let yoloRequestID = entry.metadata["yolo-request-id"] { @@ -2348,10 +2348,10 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { }) XCTAssert(logsAfterReq2.contains { entry in entry.message == "Request was scheduled on connection" - && entry.level == .debug - && entry.metadata["ahc-connection-id"] != nil + && entry.level == .debug + && entry.metadata["ahc-connection-id"] != nil }) - + XCTAssert(logsAfterReq3.allSatisfy { entry in if let httpRequestMetadata = entry.metadata["ahc-request-id"], let acmeRequestID = entry.metadata["acme-request-id"] { @@ -2369,39 +2369,39 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { }) XCTAssert(logsAfterReq3.contains { entry in entry.message == "Request was scheduled on connection" - && entry.level == .debug - && entry.metadata["ahc-connection-id"] != nil + && entry.level == .debug + && entry.metadata["ahc-connection-id"] != nil }) } - + func testNothingIsLoggedAtInfoOrHigher() { let logStore = CollectEverythingLogHandler.LogStore() - + var logger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: logStore) }) logger.logLevel = .info - + guard let request1 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get"), let request2 = try? HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "stats") else { XCTFail("bad stuff, can't even make request structures") return } - + // === Request 1 XCTAssertNoThrow(try self.defaultClient.execute(request: request1, eventLoop: .indifferent, deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + // === Request 2 XCTAssertNoThrow(try self.defaultClient.execute(request: request2, eventLoop: .indifferent, deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + // === Synthesized Request XCTAssertNoThrow(try self.defaultClient.execute(.GET, url: self.defaultHTTPBinURLPrefix + "get", @@ -2409,9 +2409,9 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + XCTAssertEqual(0, self.backgroundLogStore.allEntries.filter { $0.level >= .info }.count) - + // === Synthesized Socket Path Request XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let backgroundLogStore = CollectEverythingLogHandler.LogStore() @@ -2419,7 +2419,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { CollectEverythingLogHandler(logStore: backgroundLogStore) }) backgroundLogger.logLevel = .trace - + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), backgroundActivityLogger: backgroundLogger) @@ -2427,7 +2427,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertNoThrow(try localClient.execute(.GET, socketPath: path, urlPath: "get", @@ -2435,10 +2435,10 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .info }.count) }) - + // === Synthesized Secure Socket Path Request XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let backgroundLogStore = CollectEverythingLogHandler.LogStore() @@ -2446,7 +2446,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { CollectEverythingLogHandler(logStore: backgroundLogStore) }) backgroundLogger.logLevel = .trace - + let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none), @@ -2455,7 +2455,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertNoThrow(try localClient.execute(.GET, secureSocketPath: path, urlPath: "get", @@ -2463,24 +2463,24 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { deadline: nil, logger: logger).wait()) XCTAssertEqual(0, logStore.allEntries.count) - + XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .info }.count) }) } - + func testAllMethodsLog() { func checkExpectationsWithLogger(type: String, _ body: (Logger, String) throws -> T) throws -> T { let logStore = CollectEverythingLogHandler.LogStore() - + var logger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: logStore) }) logger.logLevel = .trace logger[metadataKey: "req"] = "yo-\(type)" - + let url = "not-found/request/\(type))" let result = try body(logger, url) - + XCTAssertGreaterThan(logStore.allEntries.count, 0) logStore.allEntries.forEach { entry in XCTAssertEqual("yo-\(type)", entry.metadata["req"] ?? "n/a") @@ -2488,41 +2488,41 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } return result } - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "PUT") { logger, url in try self.defaultClient.put(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "POST") { logger, url in try self.defaultClient.post(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "DELETE") { logger, url in try self.defaultClient.delete(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "PATCH") { logger, url in try self.defaultClient.patch(url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "CHECKOUT") { logger, url in try self.defaultClient.execute(.CHECKOUT, url: self.defaultHTTPBinURLPrefix + url, logger: logger).wait() }.status) - + // No background activity expected here. XCTAssertEqual(0, self.backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) - + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let backgroundLogStore = CollectEverythingLogHandler.LogStore() var backgroundLogger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: backgroundLogStore) }) backgroundLogger.logLevel = .trace - + let localSocketPathHTTPBin = HTTPBin(bindTarget: .unixDomainSocket(path)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), backgroundActivityLogger: backgroundLogger) @@ -2530,22 +2530,22 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in try localClient.execute(socketPath: path, urlPath: url, logger: logger).wait() }.status) - + // No background activity expected here. XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) }) - + XCTAssertNoThrow(try TemporaryFileHelpers.withTemporaryUnixDomainSocketPathName { path in let backgroundLogStore = CollectEverythingLogHandler.LogStore() var backgroundLogger = Logger(label: "\(#function)", factory: { _ in CollectEverythingLogHandler(logStore: backgroundLogStore) }) backgroundLogger.logLevel = .trace - + let localSocketPathHTTPBin = HTTPBin(.http1_1(ssl: true), bindTarget: .unixDomainSocket(path)) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none), @@ -2554,34 +2554,34 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localSocketPathHTTPBin.shutdown()) } - + XCTAssertEqual(.notFound, try checkExpectationsWithLogger(type: "GET") { logger, url in try localClient.execute(secureSocketPath: path, urlPath: url, logger: logger).wait() }.status) - + // No background activity expected here. XCTAssertEqual(0, backgroundLogStore.allEntries.filter { $0.level >= .debug }.count) }) } - + func testClosingIdleConnectionsInPoolLogsInTheBackground() { XCTAssertNoThrow(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "/get").wait()) - + XCTAssertNoThrow(try self.defaultClient.syncShutdown()) - + XCTAssertGreaterThanOrEqual(self.backgroundLogStore.allEntries.count, 0) XCTAssert(self.backgroundLogStore.allEntries.contains { entry in entry.message == "Shutting down connection pool" }) XCTAssert(self.backgroundLogStore.allEntries.allSatisfy { entry in entry.metadata["ahc-request-id"] == nil && - entry.metadata["ahc-request"] == nil && - entry.metadata["ahc-pool-key"] != nil + entry.metadata["ahc-request"] == nil && + entry.metadata["ahc-pool-key"] != nil }) - + self.defaultClient = nil // so it doesn't get shut down again. } - + func testUploadStreamingNoLength() throws { let server = NIOHTTP1TestServer(group: self.serverGroup) let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) @@ -2589,30 +2589,30 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try server.stop()) } - + var request = try HTTPClient.Request(url: "http://localhost:\(server.serverPort)/") request.body = .stream { writer in writer.write(.byteBuffer(ByteBuffer(string: "1234"))) } - + let future = client.execute(request: request) - + switch try server.readInbound() { case .head(let head): XCTAssertEqual(head.headers["transfer-encoding"], ["chunked"]) default: XCTFail("Unexpected part") } - + XCTAssertNoThrow(try server.readInbound()) // .body XCTAssertNoThrow(try server.readInbound()) // .end - + XCTAssertNoThrow(try server.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) XCTAssertNoThrow(try server.writeOutbound(.end(nil))) - + XCTAssertNoThrow(try future.wait()) } - + func testConnectErrorPropagatedToDelegate() throws { class TestDelegate: HTTPClientResponseDelegate { typealias Response = Void @@ -2622,49 +2622,49 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { self.error = error } } - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(timeout: .init(connect: .milliseconds(10)))) - + defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + // This must throw as 198.51.100.254 is reserved for documentation only let request = try HTTPClient.Request(url: "http://198.51.100.254:65535/get") let delegate = TestDelegate() - + XCTAssertThrowsError(try httpClient.execute(request: request, delegate: delegate).wait()) { XCTAssertEqualTypeAndValue($0, HTTPClientError.connectTimeout) XCTAssertEqualTypeAndValue(delegate.error, HTTPClientError.connectTimeout) } } - + func testDelegateCallinsTolerateRandomEL() throws { class TestDelegate: HTTPClientResponseDelegate { typealias Response = Void let eventLoop: EventLoop - + init(eventLoop: EventLoop) { self.eventLoop = eventLoop } - + func didReceiveHead(task: HTTPClient.Task, _: HTTPResponseHead) -> EventLoopFuture { return self.eventLoop.makeSucceededFuture(()) } - + func didReceiveBodyPart(task: HTTPClient.Task, _: ByteBuffer) -> EventLoopFuture { return self.eventLoop.makeSucceededFuture(()) } - + func didFinishRequest(task: HTTPClient.Task) throws {} } - + let elg = getDefaultEventLoopGroup(numberOfThreads: 3) let first = elg.next() let second = elg.next() XCTAssertFalse(first === second) - + let httpServer = NIOHTTP1TestServer(group: self.serverGroup) let httpClient = HTTPClient(eventLoopGroupProvider: .shared(first)) defer { @@ -2672,35 +2672,35 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try httpServer.stop()) XCTAssertNoThrow(try elg.syncShutdownGracefully()) } - + let delegate = TestDelegate(eventLoop: second) let request = try HTTPClient.Request(url: "http://localhost:\(httpServer.serverPort)/") let future = httpClient.execute(request: request, delegate: delegate) - + XCTAssertNoThrow(try httpServer.readInbound()) // .head XCTAssertNoThrow(try httpServer.readInbound()) // .end - + XCTAssertNoThrow(try httpServer.writeOutbound(.head(.init(version: .init(major: 1, minor: 1), status: .ok)))) XCTAssertNoThrow(try httpServer.writeOutbound(.body(.byteBuffer(ByteBuffer(string: "1234"))))) XCTAssertNoThrow(try httpServer.writeOutbound(.end(nil))) - + XCTAssertNoThrow(try future.wait()) } - + func testContentLengthTooLongFails() throws { let url = self.defaultHTTPBinURLPrefix + "post" XCTAssertThrowsError( try self.defaultClient.execute(request: - Request(url: url, - body: .stream(length: 10) { streamWriter in - let promise = self.defaultClient.eventLoopGroup.next().makePromise(of: Void.self) - DispatchQueue(label: "content-length-test").async { - streamWriter.write(.byteBuffer(ByteBuffer(string: "1"))).cascade(to: promise) - } - return promise.futureResult - })).wait()) { error in - XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) - } + Request(url: url, + body: .stream(length: 10) { streamWriter in + let promise = self.defaultClient.eventLoopGroup.next().makePromise(of: Void.self) + DispatchQueue(label: "content-length-test").async { + streamWriter.write(.byteBuffer(ByteBuffer(string: "1"))).cascade(to: promise) + } + return promise.futureResult + })).wait()) { error in + XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) + } // Quickly try another request and check that it works. let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait() guard var body = response.body else { @@ -2714,19 +2714,19 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(info.connectionNumber, 1) XCTAssertEqual(info.requestNumber, 1) } - + // currently gets stuck because of #250 the server just never replies func testContentLengthTooShortFails() throws { let url = self.defaultHTTPBinURLPrefix + "post" let tooLong = "XBAD BAD BAD NOT HTTP/1.1\r\n\r\n" XCTAssertThrowsError( try self.defaultClient.execute(request: - Request(url: url, - body: .stream(length: 1) { streamWriter in - streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong))) - })).wait()) { error in - XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) - } + Request(url: url, + body: .stream(length: 1) { streamWriter in + streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong))) + })).wait()) { error in + XCTAssertEqual(error as! HTTPClientError, HTTPClientError.bodyLengthMismatch) + } // Quickly try another request and check that it works. If we by accident wrote some extra bytes into the // stream (and reuse the connection) that could cause problems. let response = try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait() @@ -2741,10 +2741,10 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertEqual(info.connectionNumber, 1) XCTAssertEqual(info.requestNumber, 1) } - + func testBodyUploadAfterEndFails() { let url = self.defaultHTTPBinURLPrefix + "post" - + func uploader(_ streamWriter: HTTPClient.Body.StreamWriter) -> EventLoopFuture { let done = streamWriter.write(.byteBuffer(ByteBuffer(string: "X"))) done.recover { error -> Void in @@ -2764,63 +2764,63 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } return done } - + var request: HTTPClient.Request? XCTAssertNoThrow(request = try Request(url: url, body: .stream(length: 1, uploader))) XCTAssertThrowsError(try self.defaultClient.execute(request: XCTUnwrap(request)).wait()) { XCTAssertEqual($0 as? HTTPClientError, .writeAfterRequestSent) } - + // Quickly try another request and check that it works. If we by accident wrote some extra bytes into the // stream (and reuse the connection) that could cause problems. XCTAssertNoThrow(try self.defaultClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait()) } - + func testDoubleError() throws { // This is needed to that connection pool will not get into closed state when we release // second connection. _ = self.defaultClient.get(url: "http://localhost:\(self.defaultHTTPBin.port)/events/10/1") - + var request = try HTTPClient.Request(url: "http://localhost:\(self.defaultHTTPBin.port)/wait", method: .POST) request.body = .stream { writer in // Start writing chunks so tha we will try to write after read timeout is thrown for _ in 1...10 { _ = writer.write(.byteBuffer(ByteBuffer(string: "1234"))) } - + let promise = self.clientGroup.next().makePromise(of: Void.self) self.clientGroup.next().scheduleTask(in: .milliseconds(3)) { writer.write(.byteBuffer(ByteBuffer(string: "1234"))).cascade(to: promise) } - + return promise.futureResult } - + // We specify a deadline of 2 ms co that request will be timed out before all chunks are writtent, // we need to verify that second error on write after timeout does not lead to double-release. XCTAssertThrowsError(try self.defaultClient.execute(request: request, deadline: .now() + .milliseconds(2)).wait()) } - + func testSSLHandshakeErrorPropagation() throws { class CloseHandler: ChannelInboundHandler { typealias InboundIn = Any - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { context.close(promise: nil) } } - + let server = try ServerBootstrap(group: self.serverGroup) .childChannelInitializer { channel in channel.pipeline.addHandler(CloseHandler()) } .bind(host: "127.0.0.1", port: 0) .wait() - + defer { XCTAssertNoThrow(try server.close().wait()) } - + var timeout = HTTPClient.Configuration.Timeout(connect: .seconds(10)) if isTestingNIOTS() { // If we are using Network.framework, we set the connect timeout down very low here @@ -2829,24 +2829,24 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { // DO NOT CHANGE THIS TO DISABLE WAITING FOR CONNECTIVITY. timeout.connect = .milliseconds(100) } - + let config = HTTPClient.Configuration(timeout: timeout) let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + let request = try Request(url: "https://127.0.0.1:\(server.localAddress!.port!)", method: .GET) let task = client.execute(request: request, delegate: TestHTTPDelegate()) - + XCTAssertThrowsError(try task.wait()) { error in if isTestingNIOTS() { -#if canImport(Network) + #if canImport(Network) // We can't be more specific than this. XCTAssertTrue(error is HTTPClient.NWTLSError) -#else + #else XCTFail("Impossible condition") -#endif + #endif } else { switch error as? NIOSSLError { case .some(.handshakeFailed(.sslError(_))): break @@ -2855,55 +2855,55 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + func testSSLHandshakeErrorPropagationDelayedClose() throws { // This is as the test above, but the close handler delays its close action by a few hundred ms. // This will tend to catch the pipeline at different weird stages, and flush out different bugs. class CloseHandler: ChannelInboundHandler { typealias InboundIn = Any - + func channelRead(context: ChannelHandlerContext, data: NIOAny) { context.eventLoop.scheduleTask(in: .milliseconds(100)) { context.close(promise: nil) } } } - + let server = try ServerBootstrap(group: self.serverGroup) .childChannelInitializer { channel in channel.pipeline.addHandler(CloseHandler()) } .bind(host: "127.0.0.1", port: 0) .wait() - + defer { XCTAssertNoThrow(try server.close().wait()) } - + var timeout = HTTPClient.Configuration.Timeout(connect: .seconds(10)) if isTestingNIOTS() { // If we are using Network.framework, we set the connect timeout down very low here // because on NIOTS a failing TLS handshake manifests as a connect timeout. timeout.connect = .milliseconds(300) } - + let config = HTTPClient.Configuration(timeout: timeout) let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + let request = try Request(url: "https://127.0.0.1:\(server.localAddress!.port!)", method: .GET) let task = client.execute(request: request, delegate: TestHTTPDelegate()) - + XCTAssertThrowsError(try task.wait()) { error in if isTestingNIOTS() { -#if canImport(Network) + #if canImport(Network) // We can't be more specific than this. XCTAssertTrue(error is HTTPClient.NWTLSError) -#else + #else XCTFail("Impossible condition") -#endif + #endif } else { switch error as? NIOSSLError { case .some(.handshakeFailed(.sslError(_))): break @@ -2912,11 +2912,11 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + func testWeCloseConnectionsWhenConnectionCloseSetByServer() throws { let group = DispatchGroup() group.enter() - + let server = try ServerBootstrap(group: self.serverGroup) .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) .childChannelInitializer { channel in @@ -2926,19 +2926,19 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } .bind(host: "localhost", port: 0) .wait() - + defer { server.close(promise: nil) } - + // Simple request, should go great. XCTAssertNoThrow(try self.defaultClient.get(url: "http://localhost:\(server.localAddress!.port!)/").wait()) - + // Shouldn't need more than 100ms of waiting to see the close. let result = group.wait(timeout: DispatchTime.now() + DispatchTimeInterval.milliseconds(100)) XCTAssertEqual(result, .success, "we never closed the connection!") } - + // In this test, we test that a request can continue to stream its body after the response head, // was received. The client sends a number to the server and waits for the server to echo the // number. Once the client receives the echoed number, it will continue with the next number. @@ -2946,28 +2946,28 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { func testBiDirectionalStreaming() { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let writeEL = eventLoopGroup.next() let delegateEL = eventLoopGroup.next() - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + let delegate = ResponseStreamDelegate(eventLoop: delegateEL) - + let body: HTTPClient.Body = .stream { writer in let finalPromise = writeEL.makePromise(of: Void.self) - + func writeLoop(_ writer: HTTPClient.Body.StreamWriter, index: Int) { // always invoke from the wrong el to test thread safety writeEL.preconditionInEventLoop() - + if index >= 30 { return finalPromise.succeed(()) } - + let sent = ByteBuffer(integer: index) writer.write(.byteBuffer(sent)).flatMap { () -> EventLoopFuture in // ensure, that the writer dispatches back to the expected delegate el. @@ -2977,37 +2977,37 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { switch result { case .success(let returned): XCTAssertEqual(returned, sent) - + writeEL.execute { writeLoop(writer, index: index + 1) } - + case .failure(let error): finalPromise.fail(error) } } } - + writeEL.execute { writeLoop(writer, index: 0) } - + return finalPromise.futureResult } - + let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)", body: body) let future = httpClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: delegateEL)) - + XCTAssertNoThrow(try future.wait()) XCTAssertNil(try delegate.next().wait()) } - + func testResponseAccumulatorMaxBodySizeLimitExceedingWithContentLength() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<11) - + var request = try Request(url: httpBin.baseURL) request.body = .byteBuffer(body) XCTAssertThrowsError(try self.defaultClient.execute( @@ -3017,45 +3017,45 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertTrue(error is ResponseAccumulator.ResponseTooBigError, "unexpected error \(error)") } } - + func testResponseAccumulatorMaxBodySizeLimitNotExceedingWithContentLength() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<10) - + var request = try Request(url: httpBin.baseURL) request.body = .byteBuffer(body) let response = try self.defaultClient.execute( request: request, delegate: ResponseAccumulator(request: request, maxBodySize: 10) ).wait() - + XCTAssertEqual(response.body, body) } - + func testResponseAccumulatorMaxBodySizeLimitExceedingWithContentLengthButMethodIsHead() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHeaders() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<11) - + var request = try Request(url: httpBin.baseURL, method: .HEAD) request.body = .byteBuffer(body) let response = try self.defaultClient.execute( request: request, delegate: ResponseAccumulator(request: request, maxBodySize: 10) ).wait() - + XCTAssertEqual(response.body ?? ByteBuffer(), ByteBuffer()) } - + func testResponseAccumulatorMaxBodySizeLimitExceedingWithTransferEncodingChuncked() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<11) - + var request = try Request(url: httpBin.baseURL) request.body = .stream { writer in writer.write(.byteBuffer(body)) @@ -3067,13 +3067,13 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertTrue(error is ResponseAccumulator.ResponseTooBigError, "unexpected error \(error)") } } - + func testResponseAccumulatorMaxBodySizeLimitNotExceedingWithTransferEncodingChuncked() throws { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTPEchoHandler() } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let body = ByteBuffer(bytes: 0..<10) - + var request = try Request(url: httpBin.baseURL) request.body = .stream { writer in writer.write(.byteBuffer(body)) @@ -3082,37 +3082,37 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { request: request, delegate: ResponseAccumulator(request: request, maxBodySize: 10) ).wait() - + XCTAssertEqual(response.body, body) } - + // In this test, we test that a request can continue to stream its body after the response head and end // was received where the end is a 200. func testBiDirectionalStreamingEarly200() { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTP200DelayedHandler(bodyPartsBeforeResponse: 1) } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let writeEL = eventLoopGroup.next() let delegateEL = eventLoopGroup.next() - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + let delegate = ResponseStreamDelegate(eventLoop: delegateEL) - + let body: HTTPClient.Body = .stream { writer in let finalPromise = writeEL.makePromise(of: Void.self) - + func writeLoop(_ writer: HTTPClient.Body.StreamWriter, index: Int) { // always invoke from the wrong el to test thread safety writeEL.preconditionInEventLoop() - + if index >= 30 { return finalPromise.succeed(()) } - + let sent = ByteBuffer(integer: index) writer.write(.byteBuffer(sent)).whenComplete { result in switch result { @@ -3120,50 +3120,50 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { writeEL.execute { writeLoop(writer, index: index + 1) } - + case .failure(let error): finalPromise.fail(error) } } } - + writeEL.execute { writeLoop(writer, index: 0) } - + return finalPromise.futureResult } - + let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)", body: body) let future = httpClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: delegateEL)) XCTAssertNoThrow(try future.wait()) XCTAssertNil(try delegate.next().wait()) } - + // This test is identical to the one above, except that we send another request immediately after. This is a regression // test for https://github.com/swift-server/async-http-client/issues/595. func testBiDirectionalStreamingEarly200DoesntPreventUsFromSendingMoreRequests() { let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in HTTP200DelayedHandler(bodyPartsBeforeResponse: 1) } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let writeEL = eventLoopGroup.next() - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + let body: HTTPClient.Body = .stream { writer in let finalPromise = writeEL.makePromise(of: Void.self) - + func writeLoop(_ writer: HTTPClient.Body.StreamWriter, index: Int) { // always invoke from the wrong el to test thread safety writeEL.preconditionInEventLoop() - + if index >= 30 { return finalPromise.succeed(()) } - + let sent = ByteBuffer(integer: index) writer.write(.byteBuffer(sent)).whenComplete { result in switch result { @@ -3171,55 +3171,55 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { writeEL.execute { writeLoop(writer, index: index + 1) } - + case .failure(let error): finalPromise.fail(error) } } } - + writeEL.execute { writeLoop(writer, index: 0) } - + return finalPromise.futureResult } - + let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)", body: body) let future = httpClient.execute(request: request) XCTAssertNoThrow(try future.wait()) - + // Try another request let future2 = httpClient.execute(request: request) XCTAssertNoThrow(try future2.wait()) } - + // This test validates that we correctly close the connection after our body completes when we've streamed a // body and received the 2XX response _before_ we finished our stream. func testCloseConnectionAfterEarly2XXWhenStreaming() { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - + let onClosePromise = eventLoopGroup.next().makePromise(of: Void.self) let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in ExpectClosureServerHandler(onClosePromise: onClosePromise) } defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let writeEL = eventLoopGroup.next() - + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } - + let body: HTTPClient.Body = .stream { writer in let finalPromise = writeEL.makePromise(of: Void.self) - + func writeLoop(_ writer: HTTPClient.Body.StreamWriter, index: Int) { // always invoke from the wrong el to test thread safety writeEL.preconditionInEventLoop() - + if index >= 30 { return finalPromise.succeed(()) } - + let sent = ByteBuffer(integer: index) writer.write(.byteBuffer(sent)).whenComplete { result in switch result { @@ -3227,31 +3227,31 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { writeEL.execute { writeLoop(writer, index: index + 1) } - + case .failure(let error): finalPromise.fail(error) } } } - + writeEL.execute { writeLoop(writer, index: 0) } - + return finalPromise.futureResult } - + let headers = HTTPHeaders([("Connection", "close")]) let request = try! HTTPClient.Request(url: "http://localhost:\(httpBin.port)", headers: headers, body: body) let future = httpClient.execute(request: request) XCTAssertNoThrow(try future.wait()) XCTAssertNoThrow(try onClosePromise.futureResult.wait()) } - + func testSynchronousHandshakeErrorReporting() throws { // This only affects cases where we use NIOSSL. guard !isTestingNIOTS() else { return } - + // We use a specially crafted client that has no cipher suites to offer. To do this we ask // only for cipher suites incompatible with our TLS version. var tlsConfig = TLSConfiguration.makeClientConfiguration() @@ -3265,7 +3265,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + XCTAssertThrowsError(try localClient.get(url: "https://localhost:\(localHTTPBin.port)/").wait()) { error in guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else { XCTFail("Unexpected error: \(error)") @@ -3273,30 +3273,30 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } } } - + func testFileDownloadChunked() throws { var request = try Request(url: self.defaultHTTPBinURLPrefix + "chunked") request.headers.add(name: "Accept", value: "text/event-stream") - + let progress = - try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in - let delegate = try FileDownloadDelegate(path: path) - - let progress = try self.defaultClient.execute( - request: request, - delegate: delegate - ) + try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in + let delegate = try FileDownloadDelegate(path: path) + + let progress = try self.defaultClient.execute( + request: request, + delegate: delegate + ) .wait() - - try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path)) - - return progress - } - + + try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path)) + + return progress + } + XCTAssertEqual(nil, progress.totalBytes) XCTAssertEqual(50, progress.receivedBytes) } - + func testCloseWhileBackpressureIsExertedIsFine() throws { let request = try Request(url: self.defaultHTTPBinURLPrefix + "close-on-response") let delegate = DelayOnHeadDelegate(eventLoop: self.clientGroup.next()) { _, promise in @@ -3304,9 +3304,9 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { promise.succeed(()) } } - + let resultFuture = self.defaultClient.execute(request: request, delegate: delegate) - + // The full response must be correctly delivered. var data = try resultFuture.wait() guard let info = try data.readJSONDecodable(RequestInfo.self, length: data.readableBytes) else { @@ -3315,25 +3315,25 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { } XCTAssertEqual(info.data, "some body content") } - + func testErrorAfterCloseWhileBackpressureExerted() throws { enum ExpectedError: Error { case expected } - + let request = try Request(url: self.defaultHTTPBinURLPrefix + "close-on-response") let delegate = DelayOnHeadDelegate(eventLoop: self.clientGroup.next()) { _, backpressurePromise in backpressurePromise.fail(ExpectedError.expected) } - + let resultFuture = self.defaultClient.execute(request: request, delegate: delegate) - + // The task must be failed. XCTAssertThrowsError(try resultFuture.wait()) { error in XCTAssertEqual(error as? ExpectedError, .expected) } } - + func testRequestSpecificTLS() throws { let configuration = HTTPClient.Configuration(tlsConfiguration: nil, timeout: .init(), @@ -3342,12 +3342,12 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: configuration) let decoder = JSONDecoder() - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + // First two requests use identical TLS configurations. var tlsConfig = TLSConfiguration.makeClientConfiguration() tlsConfig.certificateVerification = .none @@ -3358,7 +3358,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { return } let firstConnectionNumber = try decoder.decode(RequestInfo.self, from: firstBody).connectionNumber - + let secondRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: tlsConfig) let secondResponse = try localClient.execute(request: secondRequest).wait() guard let secondBody = secondResponse.body else { @@ -3366,7 +3366,7 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { return } let secondConnectionNumber = try decoder.decode(RequestInfo.self, from: secondBody).connectionNumber - + // Uses a differrent TLS config. var tlsConfig2 = TLSConfiguration.makeClientConfiguration() tlsConfig2.certificateVerification = .none @@ -3378,43 +3378,43 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { return } let thirdConnectionNumber = try decoder.decode(RequestInfo.self, from: thirdBody).connectionNumber - + XCTAssertEqual(firstResponse.status, .ok) XCTAssertEqual(secondResponse.status, .ok) XCTAssertEqual(thirdResponse.status, .ok) XCTAssertEqual(firstConnectionNumber, secondConnectionNumber, "Identical TLS configurations did not use the same connection") XCTAssertNotEqual(thirdConnectionNumber, firstConnectionNumber, "Different TLS configurations did not use different connections.") } - + func testRequestWithHeaderTransferEncodingIdentityDoesNotFail() { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } - + let client = HTTPClient(eventLoopGroupProvider: .shared(group)) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + let httpBin = HTTPBin() defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + guard var request = try? Request(url: "http://127.0.0.1:\(httpBin.port)/get") else { return XCTFail("Expected to have a request here.") } request.headers.add(name: "X-Test-Header", value: "X-Test-Value") request.headers.add(name: "Transfer-Encoding", value: "identity") request.body = .string("1234") - + XCTAssertNoThrow(try client.execute(request: request).wait()) } - + func testMassiveDownload() { var response: HTTPClient.Response? XCTAssertNoThrow(response = try self.defaultClient.get(url: "\(self.defaultHTTPBinURLPrefix)mega-chunked").wait()) - + XCTAssertEqual(.ok, response?.status) XCTAssertEqual(response?.version, .http1_1) XCTAssertEqual(response?.body?.readableBytes, 10_000) } - + func testShutdownWithFutures() { let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) XCTAssertNoThrow(try httpClient.shutdown().wait()) @@ -3433,7 +3433,7 @@ final class TestResponseDelayGet: HTTPClientTestsBaseClass { XCTAssertEqual(response.status, .ok) } } - + final class TestIdleTimeoutNoReuse: HTTPClientTestsBaseClass { func testIdleTimeoutNoReuse() throws { var req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .GET) @@ -3449,13 +3449,13 @@ final class TestConnectionPoolSizeConfigValueIsRespected: HTTPClientTestsBaseCla let numberOfRequestsPerThread = 1000 let numberOfParallelWorkers = 16 let poolSize = 12 - + let httpBin = HTTPBin() defer { XCTAssertNoThrow(try httpBin.shutdown()) } - + let group = MultiThreadedEventLoopGroup(numberOfThreads: 4) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } - + let configuration = HTTPClient.Configuration( connectionPool: .init( idleTimeout: .seconds(30), @@ -3464,7 +3464,7 @@ final class TestConnectionPoolSizeConfigValueIsRespected: HTTPClientTestsBaseCla ) let client = HTTPClient(eventLoopGroupProvider: .shared(group), configuration: configuration) defer { XCTAssertNoThrow(try client.syncShutdown()) } - + let g = DispatchGroup() for workerID in 0..]() @@ -3560,7 +3560,6 @@ final class TestNoBytesSentOverBodyLimit: HTTPClientTestsBaseClass { } } - final class TestRacePoolIdleConnectionsAndGet: HTTPClientTestsBaseClass { func testRacePoolIdleConnectionsAndGet() { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), From 839f3b4a649a7be5ee828f64a8f0eafe1ab72f9b Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Tue, 11 Oct 2022 11:26:27 +0100 Subject: [PATCH 4/4] Move test subclasses into separate files --- ...zeConfigValueIsRespectedTests+XCTest.swift | 31 +++ ...nPoolSizeConfigValueIsRespectedTests.swift | 75 +++++++ .../AsyncHTTPClientTests/HTTPClientBase.swift | 86 +++++++ .../HTTPClientTests+XCTest.swift | 5 - .../HTTPClientTests.swift | 211 +----------------- .../IdleTimeoutNoReuseTests+XCTest.swift | 31 +++ .../IdleTimeoutNoReuseTests.swift | 40 ++++ ...NoBytesSentOverBodyLimitTests+XCTest.swift | 31 +++ .../NoBytesSentOverBodyLimitTests.swift | 80 +++++++ ...oolIdleConnectionsAndGetTests+XCTest.swift | 31 +++ .../RacePoolIdleConnectionsAndGetTests.swift | 44 ++++ .../ResponseDelayGetTests+XCTest.swift | 31 +++ .../ResponseDelayGetTests.swift | 43 ++++ .../StressGetHttpsTests+XCTest.swift | 31 +++ .../StressGetHttpsTests.swift | 51 +++++ Tests/LinuxMain.swift | 6 + 16 files changed, 612 insertions(+), 215 deletions(-) create mode 100644 Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests+XCTest.swift create mode 100644 Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests.swift create mode 100644 Tests/AsyncHTTPClientTests/HTTPClientBase.swift create mode 100644 Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests+XCTest.swift create mode 100644 Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests.swift create mode 100644 Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests+XCTest.swift create mode 100644 Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests.swift create mode 100644 Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests+XCTest.swift create mode 100644 Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests.swift create mode 100644 Tests/AsyncHTTPClientTests/ResponseDelayGetTests+XCTest.swift create mode 100644 Tests/AsyncHTTPClientTests/ResponseDelayGetTests.swift create mode 100644 Tests/AsyncHTTPClientTests/StressGetHttpsTests+XCTest.swift create mode 100644 Tests/AsyncHTTPClientTests/StressGetHttpsTests.swift diff --git a/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests+XCTest.swift b/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests+XCTest.swift new file mode 100644 index 000000000..f76fea3c4 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests+XCTest.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +// +// ConnectionPoolSizeConfigValueIsRespectedTests+XCTest.swift +// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension ConnectionPoolSizeConfigValueIsRespectedTests { + static var allTests: [(String, (ConnectionPoolSizeConfigValueIsRespectedTests) -> () throws -> Void)] { + return [ + ("testConnectionPoolSizeConfigValueIsRespected", testConnectionPoolSizeConfigValueIsRespected), + ] + } +} diff --git a/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests.swift b/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests.swift new file mode 100644 index 000000000..46cddeead --- /dev/null +++ b/Tests/AsyncHTTPClientTests/ConnectionPoolSizeConfigValueIsRespectedTests.swift @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AsyncHTTPClient +import Atomics +#if canImport(Network) +import Network +#endif +import Logging +import NIOConcurrencyHelpers +import NIOCore +import NIOFoundationCompat +import NIOHTTP1 +import NIOHTTPCompression +import NIOPosix +import NIOSSL +import NIOTestUtils +import NIOTransportServices +import XCTest + +final class ConnectionPoolSizeConfigValueIsRespectedTests: XCTestCaseHTTPClientTestsBaseClass { + func testConnectionPoolSizeConfigValueIsRespected() { + let numberOfRequestsPerThread = 1000 + let numberOfParallelWorkers = 16 + let poolSize = 12 + + let httpBin = HTTPBin() + defer { XCTAssertNoThrow(try httpBin.shutdown()) } + + let group = MultiThreadedEventLoopGroup(numberOfThreads: 4) + defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } + + let configuration = HTTPClient.Configuration( + connectionPool: .init( + idleTimeout: .seconds(30), + concurrentHTTP1ConnectionsPerHostSoftLimit: poolSize + ) + ) + let client = HTTPClient(eventLoopGroupProvider: .shared(group), configuration: configuration) + defer { XCTAssertNoThrow(try client.syncShutdown()) } + + let g = DispatchGroup() + for workerID in 0..! + var defaultClient: HTTPClient! + var backgroundLogStore: CollectEverythingLogHandler.LogStore! + + var defaultHTTPBinURLPrefix: String { + return "http://localhost:\(self.defaultHTTPBin.port)/" + } + + override func setUp() { + XCTAssertNil(self.clientGroup) + XCTAssertNil(self.serverGroup) + XCTAssertNil(self.defaultHTTPBin) + XCTAssertNil(self.defaultClient) + XCTAssertNil(self.backgroundLogStore) + + self.clientGroup = getDefaultEventLoopGroup(numberOfThreads: 1) + self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + self.defaultHTTPBin = HTTPBin() + self.backgroundLogStore = CollectEverythingLogHandler.LogStore() + 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) + } + + override func tearDown() { + if let defaultClient = self.defaultClient { + XCTAssertNoThrow(try defaultClient.syncShutdown()) + self.defaultClient = nil + } + + XCTAssertNotNil(self.defaultHTTPBin) + XCTAssertNoThrow(try self.defaultHTTPBin.shutdown()) + self.defaultHTTPBin = nil + + XCTAssertNotNil(self.clientGroup) + XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully()) + self.clientGroup = nil + + XCTAssertNotNil(self.serverGroup) + XCTAssertNoThrow(try self.serverGroup.syncShutdownGracefully()) + self.serverGroup = nil + + XCTAssertNotNil(self.backgroundLogStore) + self.backgroundLogStore = nil + } +} diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index 9a54b36f1..ef81b1dde 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -77,12 +77,10 @@ extension HTTPClientTests { ("testWorksWhenServerClosesConnectionAfterReceivingRequest", testWorksWhenServerClosesConnectionAfterReceivingRequest), ("testSubsequentRequestsWorkWithServerSendingConnectionClose", testSubsequentRequestsWorkWithServerSendingConnectionClose), ("testSubsequentRequestsWorkWithServerAlternatingBetweenKeepAliveAndClose", testSubsequentRequestsWorkWithServerAlternatingBetweenKeepAliveAndClose), - ("testStressGetHttpsSSLError", testStressGetHttpsSSLError), ("testSelfSignedCertificateIsRejectedWithCorrectError", testSelfSignedCertificateIsRejectedWithCorrectError), ("testSelfSignedCertificateIsRejectedWithCorrectErrorIfRequestDeadlineIsExceeded", testSelfSignedCertificateIsRejectedWithCorrectErrorIfRequestDeadlineIsExceeded), ("testFailingConnectionIsReleased", testFailingConnectionIsReleased), - ("testStressGetClose", testStressGetClose), ("testManyConcurrentRequestsWork", testManyConcurrentRequestsWork), ("testRepeatedRequestsWorkWhenServerAlwaysCloses", testRepeatedRequestsWorkWhenServerAlwaysCloses), @@ -103,7 +101,6 @@ extension HTTPClientTests { ("testUseExistingConnectionOnDifferentEL", testUseExistingConnectionOnDifferentEL), ("testWeRecoverFromServerThatClosesTheConnectionOnUs", testWeRecoverFromServerThatClosesTheConnectionOnUs), ("testPoolClosesIdleConnections", testPoolClosesIdleConnections), - ("testAvoidLeakingTLSHandshakeCompletionPromise", testAvoidLeakingTLSHandshakeCompletionPromise), ("testAsyncShutdown", testAsyncShutdown), ("testAsyncShutdownDefaultQueue", testAsyncShutdownDefaultQueue), @@ -125,7 +122,6 @@ extension HTTPClientTests { ("testContentLengthTooLongFails", testContentLengthTooLongFails), ("testContentLengthTooShortFails", testContentLengthTooShortFails), ("testBodyUploadAfterEndFails", testBodyUploadAfterEndFails), - ("testDoubleError", testDoubleError), ("testSSLHandshakeErrorPropagation", testSSLHandshakeErrorPropagation), ("testSSLHandshakeErrorPropagationDelayedClose", testSSLHandshakeErrorPropagationDelayedClose), @@ -144,7 +140,6 @@ extension HTTPClientTests { ("testCloseWhileBackpressureIsExertedIsFine", testCloseWhileBackpressureIsExertedIsFine), ("testErrorAfterCloseWhileBackpressureExerted", testErrorAfterCloseWhileBackpressureExerted), ("testRequestSpecificTLS", testRequestSpecificTLS), - ("testRequestWithHeaderTransferEncodingIdentityDoesNotFail", testRequestWithHeaderTransferEncodingIdentityDoesNotFail), ("testMassiveDownload", testMassiveDownload), ("testShutdownWithFutures", testShutdownWithFutures), diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 031a6f56b..a8036ab81 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -29,63 +29,7 @@ import NIOTestUtils import NIOTransportServices import XCTest -class HTTPClientTestsBaseClass: XCTestCase { - typealias Request = HTTPClient.Request - - var clientGroup: EventLoopGroup! - var serverGroup: EventLoopGroup! - var defaultHTTPBin: HTTPBin! - var defaultClient: HTTPClient! - var backgroundLogStore: CollectEverythingLogHandler.LogStore! - - var defaultHTTPBinURLPrefix: String { - return "http://localhost:\(self.defaultHTTPBin.port)/" - } - - override func setUp() { - XCTAssertNil(self.clientGroup) - XCTAssertNil(self.serverGroup) - XCTAssertNil(self.defaultHTTPBin) - XCTAssertNil(self.defaultClient) - XCTAssertNil(self.backgroundLogStore) - - self.clientGroup = getDefaultEventLoopGroup(numberOfThreads: 1) - self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - self.defaultHTTPBin = HTTPBin() - self.backgroundLogStore = CollectEverythingLogHandler.LogStore() - 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) - } - - override func tearDown() { - if let defaultClient = self.defaultClient { - XCTAssertNoThrow(try defaultClient.syncShutdown()) - self.defaultClient = nil - } - - XCTAssertNotNil(self.defaultHTTPBin) - XCTAssertNoThrow(try self.defaultHTTPBin.shutdown()) - self.defaultHTTPBin = nil - - XCTAssertNotNil(self.clientGroup) - XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully()) - self.clientGroup = nil - - XCTAssertNotNil(self.serverGroup) - XCTAssertNoThrow(try self.serverGroup.syncShutdownGracefully()) - self.serverGroup = nil - - XCTAssertNotNil(self.backgroundLogStore) - self.backgroundLogStore = nil - } -} - -final class HTTPClientTests: HTTPClientTestsBaseClass { +final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { func testRequestURI() throws { let request1 = try Request(url: "https://someserver.com:8888/some/path?foo=bar") XCTAssertEqual(request1.url.host, "someserver.com") @@ -3420,156 +3364,3 @@ final class HTTPClientTests: HTTPClientTestsBaseClass { XCTAssertNoThrow(try httpClient.shutdown().wait()) } } - -final class TestResponseDelayGet: HTTPClientTestsBaseClass { - func testResponseDelayGet() throws { - let req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", - method: .GET, - headers: ["X-internal-delay": "2000"], - body: nil) - let start = NIODeadline.now() - let response = try self.defaultClient.execute(request: req).wait() - XCTAssertGreaterThanOrEqual(.now() - start, .milliseconds(1_900 /* 1.9 seconds */ )) - XCTAssertEqual(response.status, .ok) - } -} - -final class TestIdleTimeoutNoReuse: HTTPClientTestsBaseClass { - func testIdleTimeoutNoReuse() throws { - var req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .GET) - XCTAssertNoThrow(try self.defaultClient.execute(request: req, deadline: .now() + .seconds(2)).wait()) - req.headers.add(name: "X-internal-delay", value: "2500") - try self.defaultClient.eventLoopGroup.next().scheduleTask(in: .milliseconds(250)) {}.futureResult.wait() - XCTAssertNoThrow(try self.defaultClient.execute(request: req).timeout(after: .seconds(10)).wait()) - } -} - -final class TestConnectionPoolSizeConfigValueIsRespected: HTTPClientTestsBaseClass { - func testConnectionPoolSizeConfigValueIsRespected() { - let numberOfRequestsPerThread = 1000 - let numberOfParallelWorkers = 16 - let poolSize = 12 - - let httpBin = HTTPBin() - defer { XCTAssertNoThrow(try httpBin.shutdown()) } - - let group = MultiThreadedEventLoopGroup(numberOfThreads: 4) - defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } - - let configuration = HTTPClient.Configuration( - connectionPool: .init( - idleTimeout: .seconds(30), - concurrentHTTP1ConnectionsPerHostSoftLimit: poolSize - ) - ) - let client = HTTPClient(eventLoopGroupProvider: .shared(group), configuration: configuration) - defer { XCTAssertNoThrow(try client.syncShutdown()) } - - let g = DispatchGroup() - for workerID in 0..]() - for _ in 1...requestCount { - let req = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, headers: ["X-internal-delay": "100"]) - futureResults.append(localClient.execute(request: req)) - } - XCTAssertNoThrow(try EventLoopFuture.andAllSucceed(futureResults, on: eventLoop).wait()) - } -} - -final class TestNoBytesSentOverBodyLimit: HTTPClientTestsBaseClass { - func testNoBytesSentOverBodyLimit() throws { - let server = NIOHTTP1TestServer(group: self.serverGroup) - defer { - XCTAssertNoThrow(try server.stop()) - } - - let tooLong = "XBAD BAD BAD NOT HTTP/1.1\r\n\r\n" - - let request = try Request( - url: "http://localhost:\(server.serverPort)", - body: .stream(length: 1) { streamWriter in - streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong))) - } - ) - - let future = self.defaultClient.execute(request: request) - - // Okay, what happens here needs an explanation: - // - // In the request state machine, we should start the request, which will lead to an - // invocation of `context.write(HTTPRequestHead)`. Since we will receive a streamed request - // body a `context.flush()` will be issued. Further the request stream will be started. - // Since the request stream immediately produces to much data, the request will be failed - // and the connection will be closed. - // - // Even though a flush was issued after the request head, there is no guarantee that the - // request head was written to the network. For this reason we must accept not receiving a - // request and receiving a request head. - - do { - _ = try server.receiveHead() - - // A request head was sent. We expect the request now to fail with a parsing error, - // since the client ended the connection to early (from the server's point of view.) - XCTAssertThrowsError(try server.readInbound()) { - XCTAssertEqual($0 as? HTTPParserError, HTTPParserError.invalidEOFState) - } - } catch { - // TBD: We sadly can't verify the error type, since it is private in `NIOTestUtils`: - // NIOTestUtils.BlockingQueue.TimeoutError - } - - // request must always be failed with this error - XCTAssertThrowsError(try future.wait()) { - XCTAssertEqual($0 as? HTTPClientError, .bodyLengthMismatch) - } - } -} - -final class TestRacePoolIdleConnectionsAndGet: HTTPClientTestsBaseClass { - func testRacePoolIdleConnectionsAndGet() { - let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(connectionPool: .init(idleTimeout: .milliseconds(10)))) - defer { - XCTAssertNoThrow(try localClient.syncShutdown()) - } - for _ in 1...200 { - XCTAssertNoThrow(try localClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait()) - Thread.sleep(forTimeInterval: 0.01 + .random(in: -0.01...0.01)) - } - } -} diff --git a/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests+XCTest.swift b/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests+XCTest.swift new file mode 100644 index 000000000..b496d527c --- /dev/null +++ b/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests+XCTest.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +// +// IdleTimeoutNoReuseTests+XCTest.swift +// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension TestIdleTimeoutNoReuse { + static var allTests: [(String, (TestIdleTimeoutNoReuse) -> () throws -> Void)] { + return [ + ("testIdleTimeoutNoReuse", testIdleTimeoutNoReuse), + ] + } +} diff --git a/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests.swift b/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests.swift new file mode 100644 index 000000000..e7cfed4d0 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/IdleTimeoutNoReuseTests.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AsyncHTTPClient +import Atomics +#if canImport(Network) +import Network +#endif +import Logging +import NIOConcurrencyHelpers +import NIOCore +import NIOFoundationCompat +import NIOHTTP1 +import NIOHTTPCompression +import NIOPosix +import NIOSSL +import NIOTestUtils +import NIOTransportServices +import XCTest + +final class TestIdleTimeoutNoReuse: XCTestCaseHTTPClientTestsBaseClass { + func testIdleTimeoutNoReuse() throws { + var req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", method: .GET) + XCTAssertNoThrow(try self.defaultClient.execute(request: req, deadline: .now() + .seconds(2)).wait()) + req.headers.add(name: "X-internal-delay", value: "2500") + try self.defaultClient.eventLoopGroup.next().scheduleTask(in: .milliseconds(250)) {}.futureResult.wait() + XCTAssertNoThrow(try self.defaultClient.execute(request: req).timeout(after: .seconds(10)).wait()) + } +} diff --git a/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests+XCTest.swift b/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests+XCTest.swift new file mode 100644 index 000000000..9ed1ca2a8 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests+XCTest.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +// +// NoBytesSentOverBodyLimitTests+XCTest.swift +// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension NoBytesSentOverBodyLimitTests { + static var allTests: [(String, (NoBytesSentOverBodyLimitTests) -> () throws -> Void)] { + return [ + ("testNoBytesSentOverBodyLimit", testNoBytesSentOverBodyLimit), + ] + } +} diff --git a/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests.swift b/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests.swift new file mode 100644 index 000000000..41285d5c5 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/NoBytesSentOverBodyLimitTests.swift @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AsyncHTTPClient +import Atomics +#if canImport(Network) +import Network +#endif +import Logging +import NIOConcurrencyHelpers +import NIOCore +import NIOFoundationCompat +import NIOHTTP1 +import NIOHTTPCompression +import NIOPosix +import NIOSSL +import NIOTestUtils +import NIOTransportServices +import XCTest + +final class NoBytesSentOverBodyLimitTests: XCTestCaseHTTPClientTestsBaseClass { + func testNoBytesSentOverBodyLimit() throws { + let server = NIOHTTP1TestServer(group: self.serverGroup) + defer { + XCTAssertNoThrow(try server.stop()) + } + + let tooLong = "XBAD BAD BAD NOT HTTP/1.1\r\n\r\n" + + let request = try Request( + url: "http://localhost:\(server.serverPort)", + body: .stream(length: 1) { streamWriter in + streamWriter.write(.byteBuffer(ByteBuffer(string: tooLong))) + } + ) + + let future = self.defaultClient.execute(request: request) + + // Okay, what happens here needs an explanation: + // + // In the request state machine, we should start the request, which will lead to an + // invocation of `context.write(HTTPRequestHead)`. Since we will receive a streamed request + // body a `context.flush()` will be issued. Further the request stream will be started. + // Since the request stream immediately produces to much data, the request will be failed + // and the connection will be closed. + // + // Even though a flush was issued after the request head, there is no guarantee that the + // request head was written to the network. For this reason we must accept not receiving a + // request and receiving a request head. + + do { + _ = try server.receiveHead() + + // A request head was sent. We expect the request now to fail with a parsing error, + // since the client ended the connection to early (from the server's point of view.) + XCTAssertThrowsError(try server.readInbound()) { + XCTAssertEqual($0 as? HTTPParserError, HTTPParserError.invalidEOFState) + } + } catch { + // TBD: We sadly can't verify the error type, since it is private in `NIOTestUtils`: + // NIOTestUtils.BlockingQueue.TimeoutError + } + + // request must always be failed with this error + XCTAssertThrowsError(try future.wait()) { + XCTAssertEqual($0 as? HTTPClientError, .bodyLengthMismatch) + } + } +} diff --git a/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests+XCTest.swift b/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests+XCTest.swift new file mode 100644 index 000000000..b4aa20dad --- /dev/null +++ b/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests+XCTest.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +// +// RacePoolIdleConnectionsAndGetTests+XCTest.swift +// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension RacePoolIdleConnectionsAndGetTests { + static var allTests: [(String, (RacePoolIdleConnectionsAndGetTests) -> () throws -> Void)] { + return [ + ("testRacePoolIdleConnectionsAndGet", testRacePoolIdleConnectionsAndGet), + ] + } +} diff --git a/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests.swift b/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests.swift new file mode 100644 index 000000000..fd8e45273 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/RacePoolIdleConnectionsAndGetTests.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AsyncHTTPClient +import Atomics +#if canImport(Network) +import Network +#endif +import Logging +import NIOConcurrencyHelpers +import NIOCore +import NIOFoundationCompat +import NIOHTTP1 +import NIOHTTPCompression +import NIOPosix +import NIOSSL +import NIOTestUtils +import NIOTransportServices +import XCTest + +final class RacePoolIdleConnectionsAndGetTests: XCTestCaseHTTPClientTestsBaseClass { + func testRacePoolIdleConnectionsAndGet() { + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(connectionPool: .init(idleTimeout: .milliseconds(10)))) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + } + for _ in 1...200 { + XCTAssertNoThrow(try localClient.get(url: self.defaultHTTPBinURLPrefix + "get").wait()) + Thread.sleep(forTimeInterval: 0.01 + .random(in: -0.01...0.01)) + } + } +} diff --git a/Tests/AsyncHTTPClientTests/ResponseDelayGetTests+XCTest.swift b/Tests/AsyncHTTPClientTests/ResponseDelayGetTests+XCTest.swift new file mode 100644 index 000000000..5d3c8bfb1 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/ResponseDelayGetTests+XCTest.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +// +// ResponseDelayGetTests+XCTest.swift +// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension ResponseDelayGetTests { + static var allTests: [(String, (ResponseDelayGetTests) -> () throws -> Void)] { + return [ + ("testResponseDelayGet", testResponseDelayGet), + ] + } +} diff --git a/Tests/AsyncHTTPClientTests/ResponseDelayGetTests.swift b/Tests/AsyncHTTPClientTests/ResponseDelayGetTests.swift new file mode 100644 index 000000000..0af5c7243 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/ResponseDelayGetTests.swift @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AsyncHTTPClient +import Atomics +#if canImport(Network) +import Network +#endif +import Logging +import NIOConcurrencyHelpers +import NIOCore +import NIOFoundationCompat +import NIOHTTP1 +import NIOHTTPCompression +import NIOPosix +import NIOSSL +import NIOTestUtils +import NIOTransportServices +import XCTest + +final class ResponseDelayGetTests: XCTestCaseHTTPClientTestsBaseClass { + func testResponseDelayGet() throws { + let req = try HTTPClient.Request(url: self.defaultHTTPBinURLPrefix + "get", + method: .GET, + headers: ["X-internal-delay": "2000"], + body: nil) + let start = NIODeadline.now() + let response = try self.defaultClient.execute(request: req).wait() + XCTAssertGreaterThanOrEqual(.now() - start, .milliseconds(1_900 /* 1.9 seconds */ )) + XCTAssertEqual(response.status, .ok) + } +} diff --git a/Tests/AsyncHTTPClientTests/StressGetHttpsTests+XCTest.swift b/Tests/AsyncHTTPClientTests/StressGetHttpsTests+XCTest.swift new file mode 100644 index 000000000..faf64cb19 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/StressGetHttpsTests+XCTest.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +// +// StressGetHttpsTests+XCTest.swift +// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension StressGetHttpsTests { + static var allTests: [(String, (StressGetHttpsTests) -> () throws -> Void)] { + return [ + ("testStressGetHttps", testStressGetHttps), + ] + } +} diff --git a/Tests/AsyncHTTPClientTests/StressGetHttpsTests.swift b/Tests/AsyncHTTPClientTests/StressGetHttpsTests.swift new file mode 100644 index 000000000..4c5cd1816 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/StressGetHttpsTests.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AsyncHTTPClient +import Atomics +#if canImport(Network) +import Network +#endif +import Logging +import NIOConcurrencyHelpers +import NIOCore +import NIOFoundationCompat +import NIOHTTP1 +import NIOHTTPCompression +import NIOPosix +import NIOSSL +import NIOTestUtils +import NIOTransportServices +import XCTest + +final class StressGetHttpsTests: XCTestCaseHTTPClientTestsBaseClass { + func testStressGetHttps() throws { + let localHTTPBin = HTTPBin(.http1_1(ssl: true)) + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + configuration: HTTPClient.Configuration(certificateVerification: .none)) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localHTTPBin.shutdown()) + } + + let eventLoop = localClient.eventLoopGroup.next() + let requestCount = 200 + var futureResults = [EventLoopFuture]() + for _ in 1...requestCount { + let req = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, headers: ["X-internal-delay": "100"]) + futureResults.append(localClient.execute(request: req)) + } + XCTAssertNoThrow(try EventLoopFuture.andAllSucceed(futureResults, on: eventLoop).wait()) + } +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 2d7e744af..ca8478326 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -30,6 +30,7 @@ struct LinuxMain { static func main() { XCTMain([ testCase(AsyncAwaitEndToEndTests.allTests), + testCase(ConnectionPoolSizeConfigValueIsRespectedTests.allTests), testCase(HTTP1ClientChannelHandlerTests.allTests), testCase(HTTP1ConnectionStateMachineTests.allTests), testCase(HTTP1ConnectionTests.allTests), @@ -56,11 +57,16 @@ struct LinuxMain { testCase(HTTPConnectionPool_RequestQueueTests.allTests), testCase(HTTPRequestStateMachineTests.allTests), testCase(LRUCacheTests.allTests), + testCase(NoBytesSentOverBodyLimitTests.allTests), + testCase(RacePoolIdleConnectionsAndGetTests.allTests), testCase(RequestBagTests.allTests), testCase(RequestValidationTests.allTests), + testCase(ResponseDelayGetTests.allTests), testCase(SOCKSEventsHandlerTests.allTests), testCase(SSLContextCacheTests.allTests), + testCase(StressGetHttpsTests.allTests), testCase(TLSEventsHandlerTests.allTests), + testCase(TestIdleTimeoutNoReuse.allTests), testCase(TransactionTests.allTests), testCase(Transaction_StateMachineTests.allTests), ])