Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,42 +137,19 @@ public struct NIOHTTPServerConfiguration: Sendable {
/// The number of concurrent streams on the HTTP/2 connection.
public var maxConcurrentStreams: Int

/// The graceful shutdown configuration.
public var gracefulShutdown: GracefulShutdownConfiguration

/// Configuration options for HTTP/2 graceful shutdown behavior.
public struct GracefulShutdownConfiguration: Sendable, Hashable {
/// The maximum amount of time that the connection has to close gracefully.
/// If set to `nil`, no time limit is enforced on the graceful shutdown process.
public var maximumGracefulShutdownDuration: Duration?

/// Creates a graceful shutdown configuration with the specified timeout value.
///
/// - Parameters:
/// - maximumGracefulShutdownDuration: The maximum amount of time that the connection has to close
/// gracefully. When `nil`, no time limit is enforced for active streams to finish during graceful
/// shutdown.
public init(maximumGracefulShutdownDuration: Duration? = nil) {
self.maximumGracefulShutdownDuration = maximumGracefulShutdownDuration
}
}

/// - Parameters:
/// - maxFrameSize: The maximum frame size to be used in connections.
/// - targetWindowSize: The target window size for connections. This will also be set as the initial window
/// size.
/// - maxConcurrentStreams: The maximum number of concurrent streams permitted on connections.
/// - gracefulShutdown: The graceful shutdown configuration.
public init(
maxFrameSize: Int = Self.defaultMaxFrameSize,
targetWindowSize: Int = Self.defaultTargetWindowSize,
maxConcurrentStreams: Int = Self.defaultMaxConcurrentStreams,
gracefulShutdown: GracefulShutdownConfiguration = .init()
maxConcurrentStreams: Int = Self.defaultMaxConcurrentStreams
) {
self.maxFrameSize = maxFrameSize
self.targetWindowSize = targetWindowSize
self.maxConcurrentStreams = maxConcurrentStreams
self.gracefulShutdown = gracefulShutdown
}

@inlinable
Expand All @@ -190,8 +167,7 @@ public struct NIOHTTPServerConfiguration: Sendable {
Self(
maxFrameSize: Self.defaultMaxFrameSize,
targetWindowSize: Self.defaultTargetWindowSize,
maxConcurrentStreams: Self.defaultMaxConcurrentStreams,
gracefulShutdown: GracefulShutdownConfiguration()
maxConcurrentStreams: Self.defaultMaxConcurrentStreams
)
}
}
Expand Down Expand Up @@ -288,6 +264,30 @@ public struct NIOHTTPServerConfiguration: Sendable {
public static var defaults: Self { .init() }
}

/// Configuration options for graceful shutdown behavior.
public struct GracefulShutdownConfiguration: Sendable, Hashable {
/// The maximum amount of time that the connection has to close gracefully before being forcefully closed.
/// If set to `nil`, no time limit is enforced for the connection to close gracefully.
public var maximumGracefulShutdownDuration: Duration?

/// Creates a graceful shutdown configuration with the specified timeout value.
///
/// - Parameters:
/// - maximumGracefulShutdownDuration: The maximum amount of time that the connection has to close gracefully.
/// If set to `nil`, no time limit is enforced for the connection to close gracefully.
public init(maximumGracefulShutdownDuration: Duration? = nil) {
self.maximumGracefulShutdownDuration = maximumGracefulShutdownDuration
}

/// The default graceful shutdown configuration.
///
/// Uses the following default values:
/// - ``maximumGracefulShutdownDuration``: `nil` (no time limit enforced for the connection to close gracefully).
public static var defaults: Self {
Self(maximumGracefulShutdownDuration: nil)
}
}

/// Network binding configuration specifying all addresses where the server should listen.
///
/// - Precondition: Must not be empty.
Expand Down Expand Up @@ -361,6 +361,9 @@ public struct NIOHTTPServerConfiguration: Sendable {
/// Configuration for connection timeouts.
public var connectionTimeouts: ConnectionTimeouts

/// The graceful shutdown configuration.
public var gracefulShutdown: GracefulShutdownConfiguration

/// The `NIOSSLContext` used by the secure upgrade channel(s), derived when the configuration is validated.
///
/// `nil` when the configuration doesn't call for a secure upgrade channel, i.e. plaintext HTTP/1.1 or HTTP/3 only.
Expand All @@ -381,9 +384,9 @@ public struct NIOHTTPServerConfiguration: Sendable {

/// Create a new configuration with multiple bind targets.
///
/// Other configuration properties (``backpressureStrategy``, ``maxConnections``,
/// ``connectionTimeouts``) are initialized to their defaults and can be set on the resulting
/// value before passing it to ``NIOHTTPServer``.
/// Other configuration properties (``backpressureStrategy``, ``maxConnections``, ``connectionTimeouts``,
/// ``gracefulShutdown``) are initialized to their defaults and can be set on the resulting value before passing it
/// to ``NIOHTTPServer``.
///
/// - Parameters:
/// - bindTargets: An array of ``BindTarget`` values specifying where the server should listen.
Expand All @@ -408,16 +411,17 @@ public struct NIOHTTPServerConfiguration: Sendable {
self.backpressureStrategy = .defaults
self.maxConnections = nil
self.connectionTimeouts = .defaults
self.gracefulShutdown = .defaults

// Validate the compatibility of `supportedHTTPVersions` and `transportSecurity`.
try self.validateTransportConfiguration()
}

/// Create a new configuration with a single bind target.
///
/// Other configuration properties (``backpressureStrategy``, ``maxConnections``,
/// ``connectionTimeouts``) are initialized to their defaults and can be set on the resulting
/// value before passing it to ``NIOHTTPServer``.
/// Other configuration properties (``backpressureStrategy``, ``maxConnections``, ``connectionTimeouts``,
/// ``gracefulShutdown``) are initialized to their defaults and can be set on the resulting value before passing it
/// to ``NIOHTTPServer``.
///
/// - Parameters:
/// - bindTarget: A ``BindTarget``.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ extension NIOHTTPServerConfiguration.HTTP2 {
/// connection.
/// - `maxConcurrentStreams` (int, optional, default: 100): The maximum number of concurrent streams in an HTTP/2
/// connection.
/// - `gracefulShutdown.maximumDuration` (int, optional, default: nil): The maximum amount of time (in seconds) that
/// the connection has to close gracefully.
///
/// - Parameter config: The configuration reader.
public init(config: ConfigSnapshotReader) {
Expand All @@ -39,24 +37,7 @@ extension NIOHTTPServerConfiguration.HTTP2 {
forKey: "targetWindowSize",
default: NIOHTTPServerConfiguration.HTTP2.defaultTargetWindowSize
),
maxConcurrentStreams: config.int(forKey: "maxConcurrentStreams", default: 100),
gracefulShutdown: .init(config: config.scoped(to: "gracefulShutdown"))
)
}
}

@available(anyAppleOS 26.0, *)
extension NIOHTTPServerConfiguration.HTTP2.GracefulShutdownConfiguration {
/// Initialize a HTTP/2 graceful shutdown configuration from a config reader.
///
/// ## Configuration keys:
/// - `maximumDuration` (int, optional, default: nil): The maximum amount of time (in seconds) that the connection
/// has to close gracefully.
///
/// - Parameter config: The configuration reader.
public init(config: ConfigSnapshotReader) {
self.init(
maximumGracefulShutdownDuration: config.int(forKey: "maximumDuration").map { .seconds($0) }
maxConcurrentStreams: config.int(forKey: "maxConcurrentStreams", default: 100)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ extension NIOHTTPServerConfiguration {
///
/// - **`"backpressureStrategy"`**: The backpressure strategy (see ``BackPressureStrategy/init(config:)``).
///
/// - **`"maxConnections"`** (int, optional, default: nil): The maximum number of concurrent connections the server
/// will accept. When omitted, no limit is imposed.
///
/// - **`"connectionTimeouts"`**: The idle, header read, and body read timeouts applied to connections (see
/// ``ConnectionTimeouts/init(config:)``).
///
/// - **`"gracefulShutdown"`**: The graceful shutdown behavior of connections (see
/// ``GracefulShutdownConfiguration/init(config:)``).
///
/// - Parameters:
/// - config: The configuration reader to read configuration values from.
/// - customCertificateVerificationCallback: A custom client certificate verification callback. This must be
Expand Down Expand Up @@ -80,6 +89,7 @@ extension NIOHTTPServerConfiguration {
self.backpressureStrategy = .init(config: snapshot.scoped(to: "backpressureStrategy"))
self.maxConnections = snapshot.int(forKey: "maxConnections")
self.connectionTimeouts = .init(config: snapshot.scoped(to: "connectionTimeouts"))
self.gracefulShutdown = .init(config: snapshot.scoped(to: "gracefulShutdown"))
}

/// Reads bind targets from either the singular `bindTarget` scope or the plural `bindTargets` scope.
Expand Down Expand Up @@ -493,4 +503,19 @@ extension NIOHTTPServerConfiguration.ConnectionTimeouts {
}
}

@available(anyAppleOS 26.0, *)
extension NIOHTTPServerConfiguration.GracefulShutdownConfiguration {
/// Initialize a graceful shutdown configuration from a config reader.
///
/// ## Configuration keys:
/// - `maximumDuration` (int, optional, default: nil): The maximum amount of time (in seconds) that the connection
/// has to close gracefully.
///
/// - Parameter config: The configuration reader.
public init(config: ConfigSnapshotReader) {
self.init(
maximumGracefulShutdownDuration: config.int(forKey: "maximumDuration").map { .seconds($0) }
)
}
}
#endif // Configuration
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ its respective key prefix.
| `http.http2` | `maxFrameSize` | `int` (bytes) | Optional | 2^14 |
| | `targetWindowSize` | `int` (bytes) | Optional | 2^16-1 |
| | `maxConcurrentStreams` | `int` | Optional | 100 |
| `http.http2.gracefulShutdown` | `maximumDuration` | `int` (seconds) | Optional | nil |
| `http.http3` | `preferHuffmanEncoding` | `bool` | Optional | true |
| `http.http3.connectionSettings` | `qpackMaximumTableCapacity` | `int` | Optional | 0 |
| | `qpackBlockedStreams` | `int` | Optional | 0 |
Expand Down Expand Up @@ -92,6 +91,7 @@ its respective key prefix.
| `connectionTimeouts` | `idle` | `int` | Optional | nil |
| | `readHeader` | `int` | Optional | nil |
| | `readBody` | `int` | Optional | nil |
| `gracefulShutdown` | `maximumDuration` | `int` (seconds) | Optional | nil |

#### (m)TLS credentials

Expand Down Expand Up @@ -139,10 +139,7 @@ key were omitted.
"http2": {
"maxFrameSize": 16384, // default: 2^14 (16384)
"targetWindowSize": 65535, // default: 2^16 - 1 (65535)
"maxConcurrentStreams": 100, // default: 100
"gracefulShutdown": {
"maximumDuration": 30 // default: nil (no time limit)
}
"maxConcurrentStreams": 100 // default: 100
}
},
"transportSecurity": {
Expand All @@ -163,6 +160,9 @@ key were omitted.
"idle": 60, // default: nil (no timeout)
"readHeader": 30, // default: nil (no timeout)
"readBody": 60 // default: nil (no timeout)
},
"gracefulShutdown": {
"maximumDuration": 30 // default: nil (no time limit)
}
}
```
Expand Down
2 changes: 1 addition & 1 deletion Sources/NIOHTTPServer/NIOHTTPServer+SecureUpgrade.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ extension NIOHTTPServer {
connectionManagerConfiguration: .init(
maxIdleTime: self.configuration.connectionTimeouts.idle.map { TimeAmount($0) },
maxAge: nil,
maxGraceTime: configuration.gracefulShutdown.maximumGracefulShutdownDuration
maxGraceTime: self.configuration.gracefulShutdown.maximumGracefulShutdownDuration
.map { TimeAmount($0) },
keepalive: nil
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,19 +284,15 @@ struct NIOHTTPServiceLifecycleTests {
func testActiveHTTP2ConnectionIsShutDownAfterGraceTimeout() async throws {
let (leafPath, caPath, keyPath) = try TestCA.makeSelfSignedChain().writeToDisk()

let server = NIOHTTPServer(
logger: self.serverLogger,
configuration: try .init(
bindTarget: .hostAndPort(host: "127.0.0.1", port: 0),
supportedHTTPVersions: [
.http1_1,
.http2(config: .init(gracefulShutdown: .init(maximumGracefulShutdownDuration: .milliseconds(500)))),
],
transportSecurity: .tls(
credentials: .x509(.pemFile(certificateChainPath: leafPath, privateKeyPath: keyPath))
)
var configuration = try NIOHTTPServerConfiguration(
bindTarget: .hostAndPort(host: "127.0.0.1", port: 0),
supportedHTTPVersions: [.http1_1, .http2],
transportSecurity: .tls(
credentials: .x509(.pemFile(certificateChainPath: leafPath, privateKeyPath: keyPath))
)
)
configuration.gracefulShutdown = .init(maximumGracefulShutdownDuration: .milliseconds(500))
let server = NIOHTTPServer(logger: self.serverLogger, configuration: configuration)

// This promise will be fulfilled when the server receives the first part of the request body. Once this
// happens, we can initiate the graceful shutdown.
Expand Down
Loading
Loading