Add an HTTP/1.1 connection pool (#105)

motivation: Better performance thanks to connection reuse

changes:
- Added a connection pool for HTTP/1.1
- All requests automatically use the connection pool
- Up to 8 parallel connections per (scheme, host, port)
- Multiple additional unit tests
This commit is contained in:
Trevör
2020-02-25 16:43:16 +01:00
committed by GitHub
parent de7421906c
commit 19e2ea727e
10 changed files with 1726 additions and 208 deletions
+51
View File
@@ -14,6 +14,7 @@
import NIO
import NIOHTTP1
import NIOHTTPCompression
internal extension String {
var isIPAddress: Bool {
@@ -44,3 +45,53 @@ public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate {
return ()
}
}
extension ClientBootstrap {
static func makeHTTPClientBootstrapBase(group: EventLoopGroup, host: String, port: Int, configuration: HTTPClient.Configuration, channelInitializer: ((Channel) -> EventLoopFuture<Void>)? = nil) -> ClientBootstrap {
return ClientBootstrap(group: group)
.channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1)
.channelInitializer { channel in
let channelAddedFuture: EventLoopFuture<Void>
switch configuration.proxy {
case .none:
channelAddedFuture = group.next().makeSucceededFuture(())
case .some:
channelAddedFuture = channel.pipeline.addProxyHandler(host: host, port: port, authorization: configuration.proxy?.authorization)
}
return channelAddedFuture.flatMap { (_: Void) -> EventLoopFuture<Void> in
channelInitializer?(channel) ?? group.next().makeSucceededFuture(())
}
}
}
}
extension CircularBuffer {
@discardableResult
mutating func swapWithFirstAndRemove(at index: Index) -> Element? {
precondition(index >= self.startIndex && index < self.endIndex)
if !self.isEmpty {
self.swapAt(self.startIndex, index)
return self.removeFirst()
} else {
return nil
}
}
@discardableResult
mutating func swapWithFirstAndRemove(where predicate: (Element) throws -> Bool) rethrows -> Element? {
if let existingIndex = try self.firstIndex(where: predicate) {
return self.swapWithFirstAndRemove(at: existingIndex)
} else {
return nil
}
}
}
extension ConnectionPool.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 }
}
}