Skip to content

[HTTP2] Basic Integration Tests #467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Tests/AsyncHTTPClientTests/HTTP2ClientTests+XCTest.swift
Original file line number Diff line number Diff line change
@@ -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),
]
}
}
190 changes: 190 additions & 0 deletions Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
7 changes: 6 additions & 1 deletion Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Foundation
import Logging
import NIOConcurrencyHelpers
import NIOCore
import NIOHPACK
import NIOHTTP1
import NIOHTTP2
import NIOHTTPCompression
Expand Down Expand Up @@ -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),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maxConcurrentStreams will be made configurable and dynamically changeable in a follow up PR.

HTTP2Setting(parameter: .maxHeaderListSize, value: HPACKDecoder.defaultMaxHeaderListSize),
]
)
let multiplexer = HTTP2StreamMultiplexer(
mode: .server,
Expand Down
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down