mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-06-02 07:37:34 +00:00
Motivation Currently error reporting with NIO Transport Services is often sub-par. This occurs because the Network.framework connections may enter the waiting state until the network connectivity state changes. We were not watching for the user event that contains the error in that state, so if we timed out in that state we'd just give a generic timeout error, instead of telling the user anything more detailed. Additionally, several of our tests assume that failure will be fast, but in NIO Transport Services we will enter that .waiting state. This is reasonable, as changed network connections may make a connection that was not succeeding suddenly viable. However, it's inconvenient for testing, where we're mostly interested in confirming that the error path works as expected. Modifications - Add an observer of the WaitingForConnectivity event that records it into our state machine for later reporting. - Add support for disabling waiting for connectivity for testing purposes. - Add annotations to several tests to stop them waiting for connectivity. Results Faster tests, better coverage, better errors for our users. Co-authored-by: David Nadoba <dnadoba@gmail.com>
171 lines
6.0 KiB
Swift
171 lines
6.0 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
@testable import AsyncHTTPClient
|
|
#if canImport(Network)
|
|
import Network
|
|
#endif
|
|
import NIOConcurrencyHelpers
|
|
import NIOCore
|
|
import NIOPosix
|
|
import NIOSSL
|
|
import NIOTransportServices
|
|
import XCTest
|
|
|
|
class HTTPClientNIOTSTests: XCTestCase {
|
|
var clientGroup: EventLoopGroup!
|
|
|
|
override func setUp() {
|
|
XCTAssertNil(self.clientGroup)
|
|
self.clientGroup = getDefaultEventLoopGroup(numberOfThreads: 3)
|
|
}
|
|
|
|
override func tearDown() {
|
|
XCTAssertNotNil(self.clientGroup)
|
|
XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully())
|
|
self.clientGroup = nil
|
|
}
|
|
|
|
func testCorrectEventLoopGroup() {
|
|
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
|
|
defer {
|
|
XCTAssertNoThrow(try httpClient.syncShutdown())
|
|
}
|
|
#if canImport(Network)
|
|
if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
|
|
XCTAssertTrue(httpClient.eventLoopGroup is NIOTSEventLoopGroup)
|
|
return
|
|
}
|
|
#endif
|
|
XCTAssertTrue(httpClient.eventLoopGroup is MultiThreadedEventLoopGroup)
|
|
}
|
|
|
|
func testTLSFailError() {
|
|
guard isTestingNIOTS() else { return }
|
|
|
|
let httpBin = HTTPBin(.http1_1(ssl: true))
|
|
var config = HTTPClient.Configuration()
|
|
config.networkFrameworkWaitForConnectivity = false
|
|
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
|
configuration: config)
|
|
defer {
|
|
XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true))
|
|
XCTAssertNoThrow(try httpBin.shutdown())
|
|
}
|
|
|
|
#if canImport(Network)
|
|
do {
|
|
_ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait()
|
|
XCTFail("This should have failed")
|
|
} catch let error as HTTPClient.NWTLSError {
|
|
XCTAssert(error.status == errSSLHandshakeFail || error.status == errSSLBadCert,
|
|
"unexpected NWTLSError with status \(error.status)")
|
|
} catch {
|
|
XCTFail("Error should have been NWTLSError not \(type(of: error))")
|
|
}
|
|
#else
|
|
XCTFail("wrong OS")
|
|
#endif
|
|
}
|
|
|
|
func testConnectionFailsFastError() {
|
|
guard isTestingNIOTS() else { return }
|
|
#if canImport(Network)
|
|
let httpBin = HTTPBin(.http1_1(ssl: false))
|
|
var config = HTTPClient.Configuration()
|
|
config.networkFrameworkWaitForConnectivity = false
|
|
|
|
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
|
configuration: config)
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true))
|
|
}
|
|
|
|
let port = httpBin.port
|
|
XCTAssertNoThrow(try httpBin.shutdown())
|
|
|
|
XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(port)/get").wait()) {
|
|
XCTAssertTrue($0 is NWError)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
func testConnectionFailError() {
|
|
guard isTestingNIOTS() else { return }
|
|
let httpBin = HTTPBin(.http1_1(ssl: false))
|
|
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
|
configuration: .init(timeout: .init(connect: .milliseconds(100),
|
|
read: .milliseconds(100))))
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true))
|
|
}
|
|
|
|
let port = httpBin.port
|
|
XCTAssertNoThrow(try httpBin.shutdown())
|
|
|
|
XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(port)/get").wait()) {
|
|
XCTAssertEqual($0 as? HTTPClientError, .connectTimeout)
|
|
}
|
|
}
|
|
|
|
func testTLSVersionError() {
|
|
guard isTestingNIOTS() else { return }
|
|
#if canImport(Network)
|
|
let httpBin = HTTPBin(.http1_1(ssl: true))
|
|
var tlsConfig = TLSConfiguration.makeClientConfiguration()
|
|
tlsConfig.certificateVerification = .none
|
|
tlsConfig.minimumTLSVersion = .tlsv11
|
|
tlsConfig.maximumTLSVersion = .tlsv1
|
|
|
|
var clientConfig = HTTPClient.Configuration(tlsConfiguration: tlsConfig)
|
|
clientConfig.networkFrameworkWaitForConnectivity = false
|
|
let httpClient = HTTPClient(
|
|
eventLoopGroupProvider: .shared(self.clientGroup),
|
|
configuration: clientConfig
|
|
)
|
|
defer {
|
|
XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true))
|
|
XCTAssertNoThrow(try httpBin.shutdown())
|
|
}
|
|
|
|
XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait()) { error in
|
|
XCTAssertEqual((error as? HTTPClient.NWTLSError)?.status, errSSLHandshakeFail)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
func testTrustRootCertificateLoadFail() {
|
|
guard isTestingNIOTS() else { return }
|
|
#if canImport(Network)
|
|
if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
|
|
var tlsConfig = TLSConfiguration.makeClientConfiguration()
|
|
tlsConfig.trustRoots = .file("not/a/certificate")
|
|
|
|
XCTAssertThrowsError(try tlsConfig.getNWProtocolTLSOptions()) { error in
|
|
switch error {
|
|
case let error as NIOSSL.NIOSSLError where error == .failedToLoadCertificate:
|
|
break
|
|
default:
|
|
XCTFail("\(error)")
|
|
}
|
|
}
|
|
} else {
|
|
XCTFail("should be impossible")
|
|
}
|
|
#endif
|
|
}
|
|
}
|