Skip to content

Commit 893634f

Browse files
authored
Add HTTP2 configuration (#19)
1 parent 3370a94 commit 893634f

File tree

2 files changed

+94
-5
lines changed

2 files changed

+94
-5
lines changed

Sources/HTTPServer/HTTPServer.swift

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,14 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
174174
)
175175

176176
case .certificateChainAndPrivateKey(let certificateChain, let privateKey):
177+
let http2Config = NIOHTTP2Handler.Configuration(httpServerHTTP2Configuration: configuration.http2)
177178
try await Self.serveSecureUpgrade(
178179
bindTarget: configuration.bindTarget,
179180
certificateChain: certificateChain,
180181
privateKey: privateKey,
181182
handler: handler,
182183
asyncChannelConfiguration: asyncChannelConfiguration,
184+
http2Configuration: http2Config,
183185
logger: logger
184186
)
185187
}
@@ -227,6 +229,7 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
227229
privateKey: Certificate.PrivateKey,
228230
handler: RequestHandler,
229231
asyncChannelConfiguration: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>.Configuration,
232+
http2Configuration: NIOHTTP2Handler.Configuration,
230233
logger: Logger
231234
) async throws {
232235
switch bindTarget.backing {
@@ -263,7 +266,7 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
263266
)
264267
)
265268
}.flatMap {
266-
channel.configureAsyncHTTPServerPipeline { channel in
269+
channel.configureAsyncHTTPServerPipeline(http2Configuration: http2Configuration) { channel in
267270
channel.eventLoop.makeCompletedFuture {
268271
try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: true))
269272

@@ -403,3 +406,49 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
403406
}
404407
}
405408
}
409+
410+
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
411+
extension NIOHTTP2Handler.Configuration {
412+
init(httpServerHTTP2Configuration http2Config: HTTPServerConfiguration.HTTP2) {
413+
let clampedTargetWindowSize = Self.clampTargetWindowSize(http2Config.targetWindowSize)
414+
let clampedMaxFrameSize = Self.clampMaxFrameSize(http2Config.maxFrameSize)
415+
416+
var http2HandlerConnectionConfiguration = NIOHTTP2Handler.ConnectionConfiguration()
417+
var http2HandlerHTTP2Settings = HTTP2Settings([
418+
HTTP2Setting(parameter: .initialWindowSize, value: clampedTargetWindowSize),
419+
HTTP2Setting(parameter: .maxFrameSize, value: clampedMaxFrameSize),
420+
])
421+
if let maxConcurrentStreams = http2Config.maxConcurrentStreams {
422+
http2HandlerHTTP2Settings.append(
423+
HTTP2Setting(parameter: .maxConcurrentStreams, value: maxConcurrentStreams)
424+
)
425+
}
426+
http2HandlerConnectionConfiguration.initialSettings = http2HandlerHTTP2Settings
427+
428+
var http2HandlerStreamConfiguration = NIOHTTP2Handler.StreamConfiguration()
429+
http2HandlerStreamConfiguration.targetWindowSize = clampedTargetWindowSize
430+
431+
self = NIOHTTP2Handler.Configuration(
432+
connection: http2HandlerConnectionConfiguration,
433+
stream: http2HandlerStreamConfiguration
434+
)
435+
}
436+
437+
/// Window size which mustn't exceed `2^31 - 1` (RFC 9113 § 6.5.2).
438+
private static func clampTargetWindowSize(_ targetWindowSize: Int) -> Int {
439+
min(targetWindowSize, Int(Int32.max))
440+
}
441+
442+
/// Max frame size must be in the range `2^14 ..< 2^24` (RFC 9113 § 4.2).
443+
private static func clampMaxFrameSize(_ maxFrameSize: Int) -> Int {
444+
let clampedMaxFrameSize: Int
445+
if maxFrameSize >= (1 << 24) {
446+
clampedMaxFrameSize = (1 << 24) - 1
447+
} else if maxFrameSize < (1 << 14) {
448+
clampedMaxFrameSize = (1 << 14)
449+
} else {
450+
clampedMaxFrameSize = maxFrameSize
451+
}
452+
return clampedMaxFrameSize
453+
}
454+
}

Sources/HTTPServer/HTTPServerConfiguration.swift

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public struct HTTPServerConfiguration: Sendable {
3737
///
3838
/// Provides options for running the server with or without TLS encryption.
3939
/// When using TLS, you must provide a certificate chain and private key.
40-
public struct TLSConfiguration: Sendable {
40+
public struct TLS: Sendable {
4141
enum Backing {
4242
case insecure
4343
case certificateChainAndPrivateKey(
@@ -63,6 +63,40 @@ public struct HTTPServerConfiguration: Sendable {
6363
}
6464
}
6565

66+
/// HTTP/2 specific configuration.
67+
public struct HTTP2: Sendable, Hashable {
68+
/// The maximum frame size to be used in an HTTP/2 connection.
69+
public var maxFrameSize: Int
70+
71+
/// The target window size for this connection.
72+
///
73+
/// - Note: This will also be set as the initial window size for the connection.
74+
public var targetWindowSize: Int
75+
76+
/// The number of concurrent streams on the HTTP/2 connection.
77+
public var maxConcurrentStreams: Int?
78+
79+
public init(
80+
maxFrameSize: Int,
81+
targetWindowSize: Int,
82+
maxConcurrentStreams: Int?
83+
) {
84+
self.maxFrameSize = maxFrameSize
85+
self.targetWindowSize = targetWindowSize
86+
self.maxConcurrentStreams = maxConcurrentStreams
87+
}
88+
89+
/// Default values. The max frame size defaults to 2^14, the target window size defaults to 2^16-1, and
90+
/// the max concurrent streams default to infinite.
91+
public static var defaults: Self {
92+
Self(
93+
maxFrameSize: 1 << 14,
94+
targetWindowSize: (1 << 16) - 1,
95+
maxConcurrentStreams: nil
96+
)
97+
}
98+
}
99+
66100
/// Configuration for the backpressure strategy to use when reading requests and writing back responses.
67101
public struct BackPressureStrategy: Sendable {
68102
enum Backing {
@@ -89,24 +123,30 @@ public struct HTTPServerConfiguration: Sendable {
89123
public var bindTarget: BindTarget
90124

91125
/// TLS configuration for the server.
92-
public var tlSConfiguration: TLSConfiguration
126+
public var tlSConfiguration: TLS
93127

94128
/// Backpressure strategy to use in the server.
95129
public var backpressureStrategy: BackPressureStrategy
96130

131+
/// Backpressure strategy to use in the server.
132+
public var http2: HTTP2
133+
97134
/// Create a new configuration.
98135
/// - Parameters:
99136
/// - bindTarget: A ``BindTarget``.
100137
/// - tlsConfiguration: A ``TLSConfiguration``. Defaults to ``TLSConfiguration/insecure``.
101138
/// - backpressureStrategy: A ``BackPressureStrategy``.
102139
/// Defaults to ``BackPressureStrategy/watermark(low:high:)`` with a low watermark of 2 and a high of 10.
140+
/// - http2: A ``HTTP2``. Defaults to ``HTTP2/defaults``.
103141
public init(
104142
bindTarget: BindTarget,
105-
tlsConfiguration: TLSConfiguration = .insecure,
106-
backpressureStrategy: BackPressureStrategy = .watermark(low: 2, high: 10)
143+
tlsConfiguration: TLS = .insecure,
144+
backpressureStrategy: BackPressureStrategy = .watermark(low: 2, high: 10),
145+
http2: HTTP2 = .defaults
107146
) {
108147
self.bindTarget = bindTarget
109148
self.tlSConfiguration = tlsConfiguration
110149
self.backpressureStrategy = backpressureStrategy
150+
self.http2 = http2
111151
}
112152
}

0 commit comments

Comments
 (0)