mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-06-02 07:37:34 +00:00
Add ConnectionPool HTTP1StateMachine (#416)
This commit is contained in:
+220
@@ -0,0 +1,220 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import NIO
|
||||
import NIOHTTP1
|
||||
|
||||
extension HTTPConnectionPool {
|
||||
struct StateMachine {
|
||||
struct Action {
|
||||
let request: RequestAction
|
||||
let connection: ConnectionAction
|
||||
|
||||
init(request: RequestAction, connection: ConnectionAction) {
|
||||
self.request = request
|
||||
self.connection = connection
|
||||
}
|
||||
|
||||
static let none: Action = Action(request: .none, connection: .none)
|
||||
}
|
||||
|
||||
enum ConnectionAction {
|
||||
enum IsShutdown: Equatable {
|
||||
case yes(unclean: Bool)
|
||||
case no
|
||||
}
|
||||
|
||||
case createConnection(Connection.ID, on: EventLoop)
|
||||
case scheduleBackoffTimer(Connection.ID, backoff: TimeAmount, on: EventLoop)
|
||||
|
||||
case scheduleTimeoutTimer(Connection.ID, on: EventLoop)
|
||||
case cancelTimeoutTimer(Connection.ID)
|
||||
|
||||
case closeConnection(Connection, isShutdown: IsShutdown)
|
||||
case cleanupConnections(CleanupContext, isShutdown: IsShutdown)
|
||||
|
||||
case none
|
||||
}
|
||||
|
||||
enum RequestAction {
|
||||
case executeRequest(Request, Connection, cancelTimeout: Bool)
|
||||
case executeRequestsAndCancelTimeouts([Request], Connection)
|
||||
|
||||
case failRequest(Request, Error, cancelTimeout: Bool)
|
||||
case failRequestsAndCancelTimeouts([Request], Error)
|
||||
|
||||
case scheduleRequestTimeout(for: Request, on: EventLoop)
|
||||
case cancelRequestTimeout(Request.ID)
|
||||
|
||||
case none
|
||||
}
|
||||
|
||||
enum HTTPVersionState {
|
||||
case http1(HTTP1StateMachine)
|
||||
}
|
||||
|
||||
var state: HTTPVersionState
|
||||
var isShuttingDown: Bool = false
|
||||
|
||||
let eventLoopGroup: EventLoopGroup
|
||||
let maximumConcurrentHTTP1Connections: Int
|
||||
|
||||
init(eventLoopGroup: EventLoopGroup, idGenerator: Connection.ID.Generator, maximumConcurrentHTTP1Connections: Int) {
|
||||
self.maximumConcurrentHTTP1Connections = maximumConcurrentHTTP1Connections
|
||||
let http1State = HTTP1StateMachine(
|
||||
idGenerator: idGenerator,
|
||||
maximumConcurrentConnections: maximumConcurrentHTTP1Connections
|
||||
)
|
||||
self.state = .http1(http1State)
|
||||
self.eventLoopGroup = eventLoopGroup
|
||||
}
|
||||
|
||||
mutating func executeRequest(_ request: Request) -> Action {
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.executeRequest(request)
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
mutating func newHTTP1ConnectionCreated(_ connection: Connection) -> Action {
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.newHTTP1ConnectionEstablished(connection)
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
mutating func failedToCreateNewConnection(_ error: Error, connectionID: Connection.ID) -> Action {
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.failedToCreateNewConnection(
|
||||
error,
|
||||
connectionID: connectionID
|
||||
)
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
mutating func connectionCreationBackoffDone(_ connectionID: Connection.ID) -> Action {
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.connectionCreationBackoffDone(connectionID)
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
/// A request has timed out.
|
||||
///
|
||||
/// This is different to a request being cancelled. If a request times out, we need to fail the
|
||||
/// request, but don't need to cancel the timer (it already triggered). If a request is cancelled
|
||||
/// we don't need to fail it but we need to cancel its timeout timer.
|
||||
mutating func timeoutRequest(_ requestID: Request.ID) -> Action {
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.timeoutRequest(requestID)
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
/// A request was cancelled.
|
||||
///
|
||||
/// This is different to a request timing out. If a request is cancelled we don't need to fail it but we
|
||||
/// need to cancel its timeout timer. If a request times out, we need to fail the request, but don't
|
||||
/// need to cancel the timer (it already triggered).
|
||||
mutating func cancelRequest(_ requestID: Request.ID) -> Action {
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.cancelRequest(requestID)
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
mutating func connectionIdleTimeout(_ connectionID: Connection.ID) -> Action {
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.connectionIdleTimeout(connectionID)
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
/// A connection has been closed
|
||||
mutating func connectionClosed(_ connectionID: Connection.ID) -> Action {
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.connectionClosed(connectionID)
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
mutating func http1ConnectionReleased(_ connectionID: Connection.ID) -> Action {
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.http1ConnectionReleased(connectionID)
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
||||
mutating func shutdown() -> Action {
|
||||
precondition(!self.isShuttingDown, "Shutdown must only be called once")
|
||||
|
||||
self.isShuttingDown = true
|
||||
|
||||
switch self.state {
|
||||
case .http1(var http1StateMachine):
|
||||
let action = http1StateMachine.shutdown()
|
||||
self.state = .http1(http1StateMachine)
|
||||
return action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension HTTPConnectionPool {
|
||||
/// The pool cleanup todo list.
|
||||
struct CleanupContext: Equatable {
|
||||
/// the connections to close right away. These are idle.
|
||||
var close: [Connection]
|
||||
|
||||
/// the connections that currently run a request that needs to be cancelled to close the connections
|
||||
var cancel: [Connection]
|
||||
|
||||
/// the connections that are backing off from connection creation
|
||||
var connectBackoff: [Connection.ID]
|
||||
|
||||
init(close: [Connection] = [], cancel: [Connection] = [], connectBackoff: [Connection.ID] = []) {
|
||||
self.close = close
|
||||
self.cancel = cancel
|
||||
self.connectBackoff = connectBackoff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension HTTPConnectionPool.StateMachine: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self.state {
|
||||
case .http1(let http1):
|
||||
return ".http1(\(http1))"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user