mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-05-03 07:32:29 +00:00
e401a2801c
allowed only using exposed API. Motivation: Having a setter for internal state of ConnectionsState led to a subset of test testing invalid invariants, for example when we have at the same time an available connecion and a waiter, which is an invalid state of the system. Modifications: * test of ConnectionsState * ConnectionsState * ConnectionPool are modified so the state under tests is achieved only by a sequence of modifications invoked by state API. During modification some tests are eliminated as they were testing artificial state, which can not be achieved by exposed APIs. ConnectionsState is pruned from "replace" as in no valid state we can have a situation when we can "replace" a connection. Invalid invariants and tests are removed. Result: We do not have a way to modify state of the ConnectionsState by direct interaction with private state of the object. Getter on the state is considered harmless and used for tests only.
209 lines
8.9 KiB
Swift
209 lines
8.9 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the AsyncHTTPClient open source project
|
|
//
|
|
// Copyright (c) 2018-2020 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 Foundation
|
|
#if canImport(Network)
|
|
import Network
|
|
#endif
|
|
import NIO
|
|
import NIOHTTP1
|
|
import NIOHTTPCompression
|
|
import NIOSSL
|
|
import NIOTransportServices
|
|
|
|
internal extension String {
|
|
var isIPAddress: Bool {
|
|
var ipv4Addr = in_addr()
|
|
var ipv6Addr = in6_addr()
|
|
|
|
return self.withCString { ptr in
|
|
inet_pton(AF_INET, ptr, &ipv4Addr) == 1 ||
|
|
inet_pton(AF_INET6, ptr, &ipv6Addr) == 1
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate {
|
|
public typealias Response = Void
|
|
|
|
let chunkHandler: (ByteBuffer) -> EventLoopFuture<Void>
|
|
|
|
public init(chunkHandler: @escaping (ByteBuffer) -> EventLoopFuture<Void>) {
|
|
self.chunkHandler = chunkHandler
|
|
}
|
|
|
|
public func didReceiveBodyPart(task: HTTPClient.Task<Void>, _ buffer: ByteBuffer) -> EventLoopFuture<Void> {
|
|
return self.chunkHandler(buffer)
|
|
}
|
|
|
|
public func didFinishRequest(task: HTTPClient.Task<Void>) throws {
|
|
return ()
|
|
}
|
|
}
|
|
|
|
extension ClientBootstrap {
|
|
fileprivate func makeClientTCPBootstrap(
|
|
host: String,
|
|
requiresTLS: Bool,
|
|
configuration: HTTPClient.Configuration
|
|
) throws -> NIOClientTCPBootstrap {
|
|
// if there is a proxy don't create TLS provider as it will be added at a later point
|
|
if configuration.proxy != nil {
|
|
return NIOClientTCPBootstrap(self, tls: NIOInsecureNoTLS())
|
|
} else {
|
|
let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient()
|
|
let sslContext = try NIOSSLContext(configuration: tlsConfiguration)
|
|
let hostname = (!requiresTLS || host.isIPAddress || host.isEmpty) ? nil : host
|
|
let tlsProvider = try NIOSSLClientTLSProvider<ClientBootstrap>(context: sslContext, serverHostname: hostname)
|
|
return NIOClientTCPBootstrap(self, tls: tlsProvider)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension NIOClientTCPBootstrap {
|
|
/// create a TCP Bootstrap based off what type of `EventLoop` has been passed to the function.
|
|
fileprivate static func makeBootstrap(
|
|
on eventLoop: EventLoop,
|
|
host: String,
|
|
requiresTLS: Bool,
|
|
configuration: HTTPClient.Configuration
|
|
) throws -> NIOClientTCPBootstrap {
|
|
var bootstrap: NIOClientTCPBootstrap
|
|
#if canImport(Network)
|
|
// if eventLoop is compatible with NIOTransportServices create a NIOTSConnectionBootstrap
|
|
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) {
|
|
// if there is a proxy don't create TLS provider as it will be added at a later point
|
|
if configuration.proxy != nil {
|
|
bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: NIOInsecureNoTLS())
|
|
} else {
|
|
// create NIOClientTCPBootstrap with NIOTS TLS provider
|
|
let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient()
|
|
let parameters = tlsConfiguration.getNWProtocolTLSOptions()
|
|
let tlsProvider = NIOTSClientTLSProvider(tlsOptions: parameters)
|
|
bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: tlsProvider)
|
|
}
|
|
} else if let clientBootstrap = ClientBootstrap(validatingGroup: eventLoop) {
|
|
bootstrap = try clientBootstrap.makeClientTCPBootstrap(host: host, requiresTLS: requiresTLS, configuration: configuration)
|
|
} else {
|
|
preconditionFailure("Cannot create bootstrap for the supplied EventLoop")
|
|
}
|
|
#else
|
|
if let clientBootstrap = ClientBootstrap(validatingGroup: eventLoop) {
|
|
bootstrap = try clientBootstrap.makeClientTCPBootstrap(host: host, requiresTLS: requiresTLS, configuration: configuration)
|
|
} else {
|
|
preconditionFailure("Cannot create bootstrap for the supplied EventLoop")
|
|
}
|
|
#endif
|
|
|
|
if let timeout = configuration.timeout.connect {
|
|
bootstrap = bootstrap.connectTimeout(timeout)
|
|
}
|
|
|
|
// don't enable TLS if we have a proxy, this will be enabled later on
|
|
if requiresTLS, configuration.proxy == nil {
|
|
return bootstrap.enableTLS()
|
|
}
|
|
|
|
return bootstrap
|
|
}
|
|
|
|
static func makeHTTPClientBootstrapBase(
|
|
on eventLoop: EventLoop,
|
|
host: String,
|
|
port: Int,
|
|
requiresTLS: Bool,
|
|
configuration: HTTPClient.Configuration
|
|
) throws -> NIOClientTCPBootstrap {
|
|
return try self.makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration)
|
|
.channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1)
|
|
.channelInitializer { channel in
|
|
let channelAddedFuture: EventLoopFuture<Void>
|
|
switch configuration.proxy {
|
|
case .none:
|
|
channelAddedFuture = eventLoop.makeSucceededFuture(())
|
|
case .some:
|
|
channelAddedFuture = channel.pipeline.addProxyHandler(host: host, port: port, authorization: configuration.proxy?.authorization)
|
|
}
|
|
return channelAddedFuture
|
|
}
|
|
}
|
|
|
|
static func makeHTTP1Channel(destination: ConnectionPool.Key, eventLoop: EventLoop, configuration: HTTPClient.Configuration, preference: HTTPClient.EventLoopPreference) -> EventLoopFuture<Channel> {
|
|
let channelEventLoop = preference.bestEventLoop ?? eventLoop
|
|
|
|
let key = destination
|
|
|
|
let requiresTLS = key.scheme.requiresTLS
|
|
let bootstrap: NIOClientTCPBootstrap
|
|
do {
|
|
bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: channelEventLoop, host: key.host, port: key.port, requiresTLS: requiresTLS, configuration: configuration)
|
|
} catch {
|
|
return channelEventLoop.makeFailedFuture(error)
|
|
}
|
|
|
|
let channel: EventLoopFuture<Channel>
|
|
switch key.scheme {
|
|
case .http, .https:
|
|
let address = HTTPClient.resolveAddress(host: key.host, port: key.port, proxy: configuration.proxy)
|
|
channel = bootstrap.connect(host: address.host, port: address.port)
|
|
case .unix, .http_unix, .https_unix:
|
|
channel = bootstrap.connect(unixDomainSocketPath: key.unixPath)
|
|
}
|
|
|
|
return channel.flatMap { channel in
|
|
let requiresSSLHandler = configuration.proxy != nil && key.scheme.requiresTLS
|
|
let handshakePromise = channel.eventLoop.makePromise(of: Void.self)
|
|
|
|
channel.pipeline.addSSLHandlerIfNeeded(for: key, tlsConfiguration: configuration.tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise)
|
|
|
|
return handshakePromise.futureResult.flatMap {
|
|
channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes)
|
|
}.flatMap {
|
|
#if canImport(Network)
|
|
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap {
|
|
return channel.pipeline.addHandler(HTTPClient.NWErrorHandler(), position: .first)
|
|
}
|
|
#endif
|
|
return channel.eventLoop.makeSucceededFuture(())
|
|
}.flatMap {
|
|
switch configuration.decompression {
|
|
case .disabled:
|
|
return channel.eventLoop.makeSucceededFuture(())
|
|
case .enabled(let limit):
|
|
let decompressHandler = NIOHTTPResponseDecompressor(limit: limit)
|
|
return channel.pipeline.addHandler(decompressHandler)
|
|
}
|
|
}.map {
|
|
channel
|
|
}
|
|
}.flatMapError { error in
|
|
#if canImport(Network)
|
|
var error = error
|
|
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap {
|
|
error = HTTPClient.NWErrorHandler.translateError(error)
|
|
}
|
|
#endif
|
|
return channelEventLoop.makeFailedFuture(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Connection {
|
|
func removeHandler<Handler: RemovableChannelHandler>(_ type: Handler.Type) -> EventLoopFuture<Void> {
|
|
return self.channel.pipeline.handler(type: type).flatMap { handler in
|
|
self.channel.pipeline.removeHandler(handler)
|
|
}.recover { _ in }
|
|
}
|
|
}
|