From b1d39a3d407422eac4b0d1d5285a1538ba437a35 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Sun, 7 Sep 2025 20:51:23 +0100 Subject: [PATCH 1/2] Add backpressure configuration --- Sources/HTTPServer/HTTPServer.swift | 22 ++++++++-- .../HTTPServer/HTTPServerConfiguration.swift | 43 +++++++++++++++++-- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/Sources/HTTPServer/HTTPServer.swift b/Sources/HTTPServer/HTTPServer.swift index fe1ebd9..2a71fd3 100644 --- a/Sources/HTTPServer/HTTPServer.swift +++ b/Sources/HTTPServer/HTTPServer.swift @@ -155,11 +155,24 @@ public final class Server { configuration: HTTPServerConfiguration, handler: RequestHandler ) async throws { + let asyncChannelConfiguration: NIOAsyncChannel.Configuration + switch configuration.backpressureStrategy.backing { + case .none: + asyncChannelConfiguration = .init(isOutboundHalfClosureEnabled: true) + + case .watermark(let low, let high): + asyncChannelConfiguration = .init( + backPressureStrategy: .init(lowWatermark: low, highWatermark: high), + isOutboundHalfClosureEnabled: true + ) + } + switch configuration.tlSConfiguration.backing { case .insecure: try await Self.serveInsecureHTTP1_1( bindTarget: configuration.bindTarget, handler: handler, + asyncChannelConfiguration: asyncChannelConfiguration, logger: logger ) @@ -169,6 +182,7 @@ public final class Server { certificateChain: certificateChain, privateKey: privateKey, handler: handler, + asyncChannelConfiguration: asyncChannelConfiguration, logger: logger ) } @@ -177,6 +191,7 @@ public final class Server { private static func serveInsecureHTTP1_1( bindTarget: HTTPServerConfiguration.BindTarget, handler: RequestHandler, + asyncChannelConfiguration: NIOAsyncChannel.Configuration, logger: Logger ) async throws { switch bindTarget.backing { @@ -188,7 +203,7 @@ public final class Server { try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: false)) return try NIOAsyncChannel( wrappingChannelSynchronously: channel, - configuration: .init(isOutboundHalfClosureEnabled: true) + configuration: asyncChannelConfiguration ) } } @@ -214,6 +229,7 @@ public final class Server { certificateChain: [Certificate], privateKey: Certificate.PrivateKey, handler: RequestHandler, + asyncChannelConfiguration: NIOAsyncChannel.Configuration, logger: Logger ) async throws { switch bindTarget.backing { @@ -256,7 +272,7 @@ public final class Server { return try NIOAsyncChannel( wrappingChannelSynchronously: channel, - configuration: .init(isOutboundHalfClosureEnabled: true) + configuration: asyncChannelConfiguration ) } } http2ConnectionInitializer: { channel in @@ -270,7 +286,7 @@ public final class Server { return try NIOAsyncChannel( wrappingChannelSynchronously: channel, - configuration: .init(isOutboundHalfClosureEnabled: true) + configuration: asyncChannelConfiguration ) } } diff --git a/Sources/HTTPServer/HTTPServerConfiguration.swift b/Sources/HTTPServer/HTTPServerConfiguration.swift index 4b98e90..1659458 100644 --- a/Sources/HTTPServer/HTTPServerConfiguration.swift +++ b/Sources/HTTPServer/HTTPServerConfiguration.swift @@ -48,9 +48,7 @@ public struct HTTPServerConfiguration: Sendable { let backing: Backing - public static func insecure() -> Self { - Self(backing: .insecure) - } + public static let insecure: Self = Self(backing: .insecure) public static func certificateChainAndPrivateKey( certificateChain: [Certificate], @@ -65,16 +63,53 @@ public struct HTTPServerConfiguration: Sendable { } } + /// Configuration for the backpressure strategy to use when reading requests and writing back responses. + public struct BackPressureStrategy: Sendable { + enum Backing { + case none + case watermark(low: Int, high: Int) + } + + internal let backing: Backing + + private init(backing: Backing) { + self.backing = backing + } + + /// No backpressure will be applied to reading requests or writing responses. + public static let none: Self = .init(backing: .none) + + /// A low/high watermark will be applied when reading requests and writing responses. + /// - Parameters: + /// - low: The threshold below which the consumer will ask the producer to produce more elements. + /// - high: The threshold above which the producer will stop producing elements. + /// - Returns: A low/high watermark strategy with the configured thresholds. + public static func watermark(low: Int, high: Int) -> Self { + .init(backing: .watermark(low: low, high: high)) + } + } + /// Network binding configuration public var bindTarget: BindTarget + /// TLS configuration for the server. public var tlSConfiguration: TLSConfiguration + /// Backpressure strategy to use in the server. + public var backpressureStrategy: BackPressureStrategy + + /// Create a new configuration. + /// - Parameters: + /// - bindTarget: A ``BindTarget``. + /// - tlsConfiguration: A ``TLSConfiguration``. Defaults to ``TLSConfiguration/insecure``. + /// - backpressureStrategy: A ``BackPressureStrategy``. Defaults to ``BackPressureStrategy/none``. public init( bindTarget: BindTarget, - tlsConfiguration: TLSConfiguration = .insecure() + tlsConfiguration: TLSConfiguration = .insecure, + backpressureStrategy: BackPressureStrategy = .none ) { self.bindTarget = bindTarget self.tlSConfiguration = tlsConfiguration + self.backpressureStrategy = backpressureStrategy } } From 5ee4044399e70de98a2f8d563cd29e42fcdaca84 Mon Sep 17 00:00:00 2001 From: Gus Cairo Date: Mon, 15 Sep 2025 16:16:01 +0100 Subject: [PATCH 2/2] Remove `none` backpressure strategy --- Sources/HTTPServer/HTTPServer.swift | 3 --- Sources/HTTPServer/HTTPServerConfiguration.swift | 9 +++------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Sources/HTTPServer/HTTPServer.swift b/Sources/HTTPServer/HTTPServer.swift index 2a71fd3..70d4938 100644 --- a/Sources/HTTPServer/HTTPServer.swift +++ b/Sources/HTTPServer/HTTPServer.swift @@ -157,9 +157,6 @@ public final class Server { ) async throws { let asyncChannelConfiguration: NIOAsyncChannel.Configuration switch configuration.backpressureStrategy.backing { - case .none: - asyncChannelConfiguration = .init(isOutboundHalfClosureEnabled: true) - case .watermark(let low, let high): asyncChannelConfiguration = .init( backPressureStrategy: .init(lowWatermark: low, highWatermark: high), diff --git a/Sources/HTTPServer/HTTPServerConfiguration.swift b/Sources/HTTPServer/HTTPServerConfiguration.swift index 1659458..ae42e79 100644 --- a/Sources/HTTPServer/HTTPServerConfiguration.swift +++ b/Sources/HTTPServer/HTTPServerConfiguration.swift @@ -66,7 +66,6 @@ public struct HTTPServerConfiguration: Sendable { /// Configuration for the backpressure strategy to use when reading requests and writing back responses. public struct BackPressureStrategy: Sendable { enum Backing { - case none case watermark(low: Int, high: Int) } @@ -76,9 +75,6 @@ public struct HTTPServerConfiguration: Sendable { self.backing = backing } - /// No backpressure will be applied to reading requests or writing responses. - public static let none: Self = .init(backing: .none) - /// A low/high watermark will be applied when reading requests and writing responses. /// - Parameters: /// - low: The threshold below which the consumer will ask the producer to produce more elements. @@ -102,11 +98,12 @@ public struct HTTPServerConfiguration: Sendable { /// - Parameters: /// - bindTarget: A ``BindTarget``. /// - tlsConfiguration: A ``TLSConfiguration``. Defaults to ``TLSConfiguration/insecure``. - /// - backpressureStrategy: A ``BackPressureStrategy``. Defaults to ``BackPressureStrategy/none``. + /// - backpressureStrategy: A ``BackPressureStrategy``. + /// Defaults to ``BackPressureStrategy/watermark(low:high:)`` with a low watermark of 2 and a high of 10. public init( bindTarget: BindTarget, tlsConfiguration: TLSConfiguration = .insecure, - backpressureStrategy: BackPressureStrategy = .none + backpressureStrategy: BackPressureStrategy = .watermark(low: 2, high: 10) ) { self.bindTarget = bindTarget self.tlSConfiguration = tlsConfiguration