diff --git a/Tests/AsyncHTTPClientTests/HTTP2ClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTP2ClientTests+XCTest.swift new file mode 100644 index 000000000..ffe9c14a1 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/HTTP2ClientTests+XCTest.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// HTTP2ClientTests+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 HTTP2ClientTests { + static var allTests: [(String, (HTTP2ClientTests) -> () throws -> Void)] { + return [ + ("testSimpleGet", testSimpleGet), + ("testConcurrentRequests", testConcurrentRequests), + ("testConcurrentRequestsFromDifferentThreads", testConcurrentRequestsFromDifferentThreads), + ("testConcurrentRequestsWorkWithRequiredEventLoop", testConcurrentRequestsWorkWithRequiredEventLoop), + ] + } +} diff --git a/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift new file mode 100644 index 000000000..bef964577 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift @@ -0,0 +1,190 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// TODO: remove @testable once we officially support HTTP/2 +@testable import AsyncHTTPClient // Tests that really need @testable go into HTTP2ClientInternalTests.swift +#if canImport(Network) + import Network +#endif +import Logging +import NIOCore +import NIOPosix +import NIOSSL +import XCTest + +class HTTP2ClientTests: XCTestCase { + func makeDefaultHTTPClient() -> HTTPClient { + var tlsConfig = TLSConfiguration.makeClientConfiguration() + tlsConfig.certificateVerification = .none + return HTTPClient( + eventLoopGroupProvider: .createNew, + configuration: HTTPClient.Configuration( + tlsConfiguration: tlsConfig, + httpVersion: .automatic + ), + backgroundActivityLogger: Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) + ) + } + + func testSimpleGet() { + let bin = HTTPBin(.http2(compress: false)) + defer { XCTAssertNoThrow(try bin.shutdown()) } + let client = self.makeDefaultHTTPClient() + defer { XCTAssertNoThrow(try client.syncShutdown()) } + var response: HTTPClient.Response? + XCTAssertNoThrow(response = try client.get(url: "https://localhost:\(bin.port)/get").wait()) + + XCTAssertEqual(.ok, response?.status) + XCTAssertEqual(response?.version, .http2) + } + + func testConcurrentRequests() { + let bin = HTTPBin(.http2(compress: false)) + defer { XCTAssertNoThrow(try bin.shutdown()) } + let client = self.makeDefaultHTTPClient() + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let el = client.eventLoopGroup.next() + let requestPromises = (0..<1000).map { _ in + client.get(url: "https://localhost:\(bin.port)/get") + .map { result -> Void in + XCTAssertEqual(result.version, .http2) + } + } + XCTAssertNoThrow(try EventLoopFuture.whenAllComplete(requestPromises, on: el).wait()) + } + + func testConcurrentRequestsFromDifferentThreads() { + let bin = HTTPBin(.http2(compress: false)) + defer { XCTAssertNoThrow(try bin.shutdown()) } + let client = self.makeDefaultHTTPClient() + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let numberOfWorkers = 20 + let numberOfRequestsPerWorkers = 20 + let allWorkersReady = DispatchSemaphore(value: 0) + let allWorkersGo = DispatchSemaphore(value: 0) + let allDone = DispatchGroup() + + let url = "https://localhost:\(bin.port)/get" + + var response: HTTPClient.Response? + XCTAssertNoThrow(response = try client.get(url: url).wait()) + + XCTAssertEqual(.ok, response?.status) + XCTAssertEqual(response?.version, .http2) + + for w in 0..<numberOfWorkers { + let q = DispatchQueue(label: "worker \(w)") + q.async(group: allDone) { + func go() { + allWorkersReady.signal() // tell the driver we're ready + allWorkersGo.wait() // wait for the driver to let us go + + for _ in 0..<numberOfRequestsPerWorkers { + var response: HTTPClient.Response? + XCTAssertNoThrow(response = try client.get(url: url).wait()) + + XCTAssertEqual(.ok, response?.status) + XCTAssertEqual(response?.version, .http2) + } + } + go() + } + } + + for _ in 0..<numberOfWorkers { + allWorkersReady.wait() + } + // now all workers should be waiting for the go signal + + for _ in 0..<numberOfWorkers { + allWorkersGo.signal() + } + // all workers should be running, let's wait for them to finish + allDone.wait() + } + + func testConcurrentRequestsWorkWithRequiredEventLoop() { + let numberOfWorkers = 20 + let numberOfRequestsPerWorkers = 20 + let allWorkersReady = DispatchSemaphore(value: 0) + let allWorkersGo = DispatchSemaphore(value: 0) + let allDone = DispatchGroup() + + let localHTTPBin = HTTPBin(.http2(compress: false)) + let elg = MultiThreadedEventLoopGroup(numberOfThreads: numberOfWorkers) + var tlsConfig = TLSConfiguration.makeClientConfiguration() + tlsConfig.certificateVerification = .none + let localClient = HTTPClient( + eventLoopGroupProvider: .shared(elg), + configuration: HTTPClient.Configuration( + tlsConfiguration: tlsConfig, + httpVersion: .automatic + ), + backgroundActivityLogger: Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) + ) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localHTTPBin.shutdown()) + } + + let url = "https://localhost:\(localHTTPBin.port)/get" + + var response: HTTPClient.Response? + XCTAssertNoThrow(response = try localClient.get(url: url).wait()) + + XCTAssertEqual(.ok, response?.status) + XCTAssertEqual(response?.version, .http2) + + for w in 0..<numberOfWorkers { + let q = DispatchQueue(label: "worker \(w)") + let el = elg.next() + q.async(group: allDone) { + func go() { + allWorkersReady.signal() // tell the driver we're ready + allWorkersGo.wait() // wait for the driver to let us go + + for _ in 0..<numberOfRequestsPerWorkers { + var response: HTTPClient.Response? + let request = try! HTTPClient.Request(url: url) + let requestPromise = localClient + .execute( + request: request, + eventLoop: .delegateAndChannel(on: el) + ) + .map { response -> HTTPClient.Response in + XCTAssertTrue(el.inEventLoop) + return response + } + XCTAssertNoThrow(response = try requestPromise.wait()) + + XCTAssertEqual(.ok, response?.status) + XCTAssertEqual(response?.version, .http2) + } + } + go() + } + } + + for _ in 0..<numberOfWorkers { + allWorkersReady.wait() + } + // now all workers should be waiting for the go signal + + for _ in 0..<numberOfWorkers { + allWorkersGo.signal() + } + // all workers should be running, let's wait for them to finish + allDone.wait() + } +} diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index d54c640bc..15baf0cc1 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -17,6 +17,7 @@ import Foundation import Logging import NIOConcurrencyHelpers import NIOCore +import NIOHPACK import NIOHTTP1 import NIOHTTP2 import NIOHTTPCompression @@ -484,7 +485,11 @@ internal final class HTTPBin<RequestHandler: ChannelInboundHandler> where // Successful upgrade to HTTP/2. Let the user configure the pipeline. let http2Handler = NIOHTTP2Handler( mode: .server, - initialSettings: NIOHTTP2.nioDefaultSettings + initialSettings: [ + // TODO: make max concurrent streams configurable + HTTP2Setting(parameter: .maxConcurrentStreams, value: 10), + HTTP2Setting(parameter: .maxHeaderListSize, value: HPACKDecoder.defaultMaxHeaderListSize), + ] ) let multiplexer = HTTP2StreamMultiplexer( mode: .server, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 1adb04801..ddc4dad39 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -31,6 +31,7 @@ import XCTest testCase(HTTP1ConnectionTests.allTests), testCase(HTTP1ProxyConnectHandlerTests.allTests), testCase(HTTP2ClientRequestHandlerTests.allTests), + testCase(HTTP2ClientTests.allTests), testCase(HTTP2ConnectionTests.allTests), testCase(HTTP2IdleHandlerTests.allTests), testCase(HTTPClientCookieTests.allTests),