From 0dfc5918ce38242747876a6c37298116c442783b Mon Sep 17 00:00:00 2001 From: Aryan Shah Date: Wed, 27 May 2026 11:20:06 +0100 Subject: [PATCH] Update default HTTP/2 settings Motivation: As part of passing the HTTP/2 configuration values to `NIOHTTP2Handler`, we [override](https://github.com/swift-server/swift-http-server/blob/32abef29361e9add364e50e1fc85ac761f7d0651/Sources/NIOHTTPServer/NIOHTTPServer.swift#L349) [`NIOHTTP2Handler`'s `ConnectionConfiguration`'s `initialSettings`](https://github.com/apple/swift-nio-http2/blob/61d1b44f6e4e118792be1cff88ee2bc0267c6f9a/Sources/NIOHTTP2/HTTP2ChannelHandler.swift#L1512). This means we do not use the sensible defaults provided for `ConnectionConfiguration`'s `initialSettings` by the [`nioDefaultSettings` property](https://github.com/apple/swift-nio-http2/blob/61d1b44f6e4e118792be1cff88ee2bc0267c6f9a/Sources/NIOHTTP2/HTTP2ChannelHandler.swift#L20), which sets `maxConcurrentStreams` to 100, and `maxHeaderListSize` to `1 << 14`. Modifications: - Updated the value of the `internal` `defaultMaxConcurrentStreams` property and the public `maxConcurrentStreams` property in `NIOHTTPServerConfiguration.HTTP2` from `nil` (translates to no limit on the max concurrent streams) to `100`. - The types of these properties were also updated from `Int?` to `Int`, which also required the initializer to be updated. Users can no longer express unlimited `maxConcurrentStreams` and must either provide a concrete limit or implicitly use the default value of `100`. - Updated the `swift-configuration` integration to reflect this change. - Added the values contained in `nioDefaultSettings` to the initial settings array that we override. - Raised the `swift-nio-http2` dependency version to `1.44.0` in order to use the `NIOHPACK` product, which provides the default value for `maxHeaderListSize`. Result: We now set sensible default HTTP/2 settings. --- Package.swift | 5 +++-- .../NIOHTTPServer+SwiftConfiguration.swift | 7 ++----- .../Configuration/NIOHTTPServerConfiguration.swift | 10 +++++----- .../SwiftConfigurationIntegration.md | 4 ++-- Sources/NIOHTTPServer/NIOHTTPServer.swift | 11 +++++------ .../NIOHTTPServerSwiftConfigurationTests.swift | 4 ++-- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/Package.swift b/Package.swift index be6d235..1f27f53 100644 --- a/Package.swift +++ b/Package.swift @@ -60,10 +60,10 @@ let package = Package( .package(url: "https://github.com/apple/swift-distributed-tracing.git", from: "1.0.0"), .package(url: "https://github.com/apple/swift-certificates.git", from: "1.16.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-nio.git", from: "2.92.2"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.100.0"), .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.36.0"), .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.30.0"), - .package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.40.0"), + .package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.44.0"), .package(url: "https://github.com/apple/swift-configuration.git", from: "1.0.0"), .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.10.0"), ], @@ -90,6 +90,7 @@ let package = Package( .product(name: "NIOPosix", package: "swift-nio"), .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOHTTP2", package: "swift-nio-http2"), + .product(name: "NIOHPACK", package: "swift-nio-http2"), .product(name: "NIOSSL", package: "swift-nio-ssl"), .product(name: "Logging", package: "swift-log"), .product(name: "NIOHTTPTypesHTTP1", package: "swift-nio-extras"), diff --git a/Sources/NIOHTTPServer/Configuration/NIOHTTPServer+SwiftConfiguration.swift b/Sources/NIOHTTPServer/Configuration/NIOHTTPServer+SwiftConfiguration.swift index 11dcdbb..ce55ff8 100644 --- a/Sources/NIOHTTPServer/Configuration/NIOHTTPServer+SwiftConfiguration.swift +++ b/Sources/NIOHTTPServer/Configuration/NIOHTTPServer+SwiftConfiguration.swift @@ -395,7 +395,7 @@ extension NIOHTTPServerConfiguration.HTTP2 { /// - `maxFrameSize` (int, optional, default: 2^14): The maximum frame size to be used in an HTTP/2 connection. /// - `targetWindowSize` (int, optional, default: 2^16 - 1): The target window size to be used in an HTTP/2 /// connection. - /// - `maxConcurrentStreams` (int, optional, default: nil): The maximum number of concurrent streams in an HTTP/2 + /// - `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. @@ -411,10 +411,7 @@ extension NIOHTTPServerConfiguration.HTTP2 { forKey: "targetWindowSize", default: NIOHTTPServerConfiguration.HTTP2.defaultTargetWindowSize ), - /// The default value, ``NIOHTTPServerConfiguration.HTTP2.DEFAULT_TARGET_WINDOW_SIZE``, is `nil`. However, - /// we can only specify a non-nil `default` argument to `config.int(...)`. But `config.int(...)` already - /// defaults to `nil` if it can't find the `"maxConcurrentStreams"` key, so that works for us. - maxConcurrentStreams: config.int(forKey: "maxConcurrentStreams"), + maxConcurrentStreams: config.int(forKey: "maxConcurrentStreams", default: 100), gracefulShutdown: .init(config: config.scoped(to: "gracefulShutdown")) ) } diff --git a/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfiguration.swift b/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfiguration.swift index d2c7f56..5cf6e05 100644 --- a/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfiguration.swift +++ b/Sources/NIOHTTPServer/Configuration/NIOHTTPServerConfiguration.swift @@ -131,7 +131,7 @@ public struct NIOHTTPServerConfiguration: Sendable { public var targetWindowSize: Int /// The number of concurrent streams on the HTTP/2 connection. - public var maxConcurrentStreams: Int? + public var maxConcurrentStreams: Int /// The graceful shutdown configuration. public var gracefulShutdown: GracefulShutdownConfiguration @@ -162,7 +162,7 @@ public struct NIOHTTPServerConfiguration: Sendable { public init( maxFrameSize: Int = Self.defaultMaxFrameSize, targetWindowSize: Int = Self.defaultTargetWindowSize, - maxConcurrentStreams: Int? = Self.defaultMaxConcurrentStreams, + maxConcurrentStreams: Int = Self.defaultMaxConcurrentStreams, gracefulShutdown: GracefulShutdownConfiguration = .init() ) { self.maxFrameSize = maxFrameSize @@ -178,10 +178,10 @@ public struct NIOHTTPServerConfiguration: Sendable { static var defaultTargetWindowSize: Int { (1 << 16) - 1 } @inlinable - static var defaultMaxConcurrentStreams: Int? { nil } + static var defaultMaxConcurrentStreams: Int { 100 } - /// Default values. The max frame size defaults to 2^14, the target window size defaults to 2^16-1, and - /// the max concurrent streams default to infinite. + /// Default values. The max frame size defaults to 2^14, the target window size defaults to 2^16-1, and the max + /// concurrent streams default to 100. public static var defaults: Self { Self( maxFrameSize: Self.defaultMaxFrameSize, diff --git a/Sources/NIOHTTPServer/Documentation.docc/SwiftConfigurationIntegration.md b/Sources/NIOHTTPServer/Documentation.docc/SwiftConfigurationIntegration.md index 5cbcb7b..772ca54 100644 --- a/Sources/NIOHTTPServer/Documentation.docc/SwiftConfigurationIntegration.md +++ b/Sources/NIOHTTPServer/Documentation.docc/SwiftConfigurationIntegration.md @@ -47,7 +47,7 @@ respective key prefix. | `http` | `versions` | `string array` | Required (permitted values: `"http1_1"`, `"http2"`) | - | | `http.http2` | `maxFrameSize` | `int` | Optional | 2^14 | | | `targetWindowSize` | `int` | Optional | 2^16-1 | -| | `maxConcurrentStreams` | `int` | Optional | nil | +| | `maxConcurrentStreams` | `int` | Optional | 100 | | `http.http2.gracefulShutdown` | `maximumDuration` | `int` | Optional | nil | | `transportSecurity` | `mode` | `string` | Required (permitted values: `"plaintext"`, `"tls"`, `"mTLS"`) | - | | | `credentialSource` | `string` | Required for `"tls"` and `"mTLS"` (permitted values: `"inline"`, `"file"`) | - | @@ -95,7 +95,7 @@ key were omitted. "http2": { "maxFrameSize": 16384, // default: 2^14 (16384) "targetWindowSize": 65535, // default: 2^16 - 1 (65535) - "maxConcurrentStreams": 100, // default: nil (no limit) + "maxConcurrentStreams": 100, // default: 100 "gracefulShutdown": { "maximumDuration": 30 // default: nil (no time limit) } diff --git a/Sources/NIOHTTPServer/NIOHTTPServer.swift b/Sources/NIOHTTPServer/NIOHTTPServer.swift index cac5e8f..fd4f81e 100644 --- a/Sources/NIOHTTPServer/NIOHTTPServer.swift +++ b/Sources/NIOHTTPServer/NIOHTTPServer.swift @@ -19,6 +19,7 @@ import NIOCertificateReloading import NIOConcurrencyHelpers import NIOCore import NIOExtras +import NIOHPACK import NIOHTTP1 import NIOHTTP2 import NIOHTTPTypes @@ -337,15 +338,13 @@ extension NIOHTTP2Handler.Configuration { let clampedMaxFrameSize = Self.clampMaxFrameSize(http2Config.maxFrameSize) var http2HandlerConnectionConfiguration = NIOHTTP2Handler.ConnectionConfiguration() - var http2HandlerHTTP2Settings = HTTP2Settings([ + let http2HandlerHTTP2Settings = HTTP2Settings([ HTTP2Setting(parameter: .initialWindowSize, value: clampedTargetWindowSize), HTTP2Setting(parameter: .maxFrameSize, value: clampedMaxFrameSize), + HTTP2Setting(parameter: .maxConcurrentStreams, value: http2Config.maxConcurrentStreams), + HTTP2Setting(parameter: .maxHeaderListSize, value: HPACKDecoder.defaultMaxHeaderListSize), ]) - if let maxConcurrentStreams = http2Config.maxConcurrentStreams { - http2HandlerHTTP2Settings.append( - HTTP2Setting(parameter: .maxConcurrentStreams, value: maxConcurrentStreams) - ) - } + http2HandlerConnectionConfiguration.initialSettings = http2HandlerHTTP2Settings var http2HandlerStreamConfiguration = NIOHTTP2Handler.StreamConfiguration() diff --git a/Tests/NIOHTTPServerTests/NIOHTTPServerSwiftConfigurationTests.swift b/Tests/NIOHTTPServerTests/NIOHTTPServerSwiftConfigurationTests.swift index f910ce3..904f3b1 100644 --- a/Tests/NIOHTTPServerTests/NIOHTTPServerSwiftConfigurationTests.swift +++ b/Tests/NIOHTTPServerTests/NIOHTTPServerSwiftConfigurationTests.swift @@ -300,7 +300,7 @@ struct NIOHTTPServerSwiftConfigurationTests { #expect(http2.maxFrameSize == NIOHTTPServerConfiguration.HTTP2.defaultMaxFrameSize) #expect(http2.targetWindowSize == NIOHTTPServerConfiguration.HTTP2.defaultTargetWindowSize) - #expect(http2.maxConcurrentStreams == nil) + #expect(http2.maxConcurrentStreams == 100) #expect(http2.gracefulShutdown == .init(maximumGracefulShutdownDuration: nil)) } @@ -335,7 +335,7 @@ struct NIOHTTPServerSwiftConfigurationTests { #expect(http2.maxFrameSize == 5) #expect(http2.targetWindowSize == NIOHTTPServerConfiguration.HTTP2.defaultTargetWindowSize) - #expect(http2.maxConcurrentStreams == nil) + #expect(http2.maxConcurrentStreams == 100) #expect(http2.gracefulShutdown.maximumGracefulShutdownDuration == nil) } }