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>
153 lines
6.2 KiB
Swift
153 lines
6.2 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/* NOT @testable */ import AsyncHTTPClient // Tests that need @testable go into HTTPClientInternalTests.swift
|
|
import Logging
|
|
import NIOCore
|
|
import NIOPosix
|
|
import NIOSOCKS
|
|
import XCTest
|
|
|
|
class HTTPClientSOCKSTests: XCTestCase {
|
|
typealias Request = HTTPClient.Request
|
|
|
|
var clientGroup: EventLoopGroup!
|
|
var serverGroup: EventLoopGroup!
|
|
var defaultHTTPBin: HTTPBin<HTTPBinHandler>!
|
|
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),
|
|
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
|
|
}
|
|
|
|
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)))
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try localClient.syncShutdown())
|
|
XCTAssertNoThrow(try socksBin.shutdown())
|
|
}
|
|
|
|
var response: HTTPClient.Response?
|
|
XCTAssertNoThrow(response = try localClient.get(url: "http://localhost/socks/test").wait())
|
|
XCTAssertEqual(.ok, response?.status)
|
|
XCTAssertEqual(ByteBuffer(string: "it works!"), response?.body)
|
|
}
|
|
|
|
func testProxySOCKSBogusAddress() throws {
|
|
var config = HTTPClient.Configuration(proxy: .socksServer(host: "127.0.."))
|
|
config.networkFrameworkWaitForConnectivity = false
|
|
let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
|
configuration: config)
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try localClient.syncShutdown())
|
|
}
|
|
XCTAssertThrowsError(try localClient.get(url: "http://localhost/socks/test").wait())
|
|
}
|
|
|
|
// 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 localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
|
configuration: config)
|
|
defer {
|
|
XCTAssertNoThrow(try localClient.syncShutdown())
|
|
XCTAssertNoThrow(try localHTTPBin.shutdown())
|
|
}
|
|
XCTAssertThrowsError(try localClient.get(url: "http://localhost/socks/test").wait())
|
|
}
|
|
|
|
// speak to a server that doesn't speak SOCKS
|
|
func testProxySOCKSFailureInvalidServer() throws {
|
|
var config = HTTPClient.Configuration(proxy: .socksServer(host: "localhost"))
|
|
config.networkFrameworkWaitForConnectivity = false
|
|
|
|
let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
|
configuration: config)
|
|
defer {
|
|
XCTAssertNoThrow(try localClient.syncShutdown())
|
|
}
|
|
XCTAssertThrowsError(try localClient.get(url: "http://localhost/socks/test").wait())
|
|
}
|
|
|
|
// 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 localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
|
|
configuration: config)
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try localClient.syncShutdown())
|
|
XCTAssertNoThrow(try socksBin.shutdown())
|
|
}
|
|
|
|
// the server will send a bogus message in response to the clients greeting
|
|
// this will be first picked up as an invalid protocol
|
|
XCTAssertThrowsError(try localClient.get(url: "http://localhost/socks/test").wait()) { e in
|
|
XCTAssertTrue(e is SOCKSError.InvalidProtocolVersion)
|
|
}
|
|
}
|
|
}
|