mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-05-03 07:32:29 +00:00
221 lines
7.9 KiB
Swift
221 lines
7.9 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
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))"
|
|
}
|
|
}
|
|
}
|