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
51 changes: 50 additions & 1 deletion Sources/HTTPServer/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,14 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
)

case .certificateChainAndPrivateKey(let certificateChain, let privateKey):
let http2Config = NIOHTTP2Handler.Configuration(httpServerHTTP2Configuration: configuration.http2)
try await Self.serveSecureUpgrade(
bindTarget: configuration.bindTarget,
certificateChain: certificateChain,
privateKey: privateKey,
handler: handler,
asyncChannelConfiguration: asyncChannelConfiguration,
http2Configuration: http2Config,
logger: logger
)
}
Expand Down Expand Up @@ -227,6 +229,7 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
privateKey: Certificate.PrivateKey,
handler: RequestHandler,
asyncChannelConfiguration: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>.Configuration,
http2Configuration: NIOHTTP2Handler.Configuration,
logger: Logger
) async throws {
switch bindTarget.backing {
Expand Down Expand Up @@ -263,7 +266,7 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
)
)
}.flatMap {
channel.configureAsyncHTTPServerPipeline { channel in
channel.configureAsyncHTTPServerPipeline(http2Configuration: http2Configuration) { channel in
channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: true))

Expand Down Expand Up @@ -403,3 +406,49 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
}
}
}

@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
extension NIOHTTP2Handler.Configuration {
init(httpServerHTTP2Configuration http2Config: HTTPServerConfiguration.HTTP2) {
let clampedTargetWindowSize = Self.clampTargetWindowSize(http2Config.targetWindowSize)
let clampedMaxFrameSize = Self.clampMaxFrameSize(http2Config.maxFrameSize)

var http2HandlerConnectionConfiguration = NIOHTTP2Handler.ConnectionConfiguration()
var http2HandlerHTTP2Settings = HTTP2Settings([
HTTP2Setting(parameter: .initialWindowSize, value: clampedTargetWindowSize),
HTTP2Setting(parameter: .maxFrameSize, value: clampedMaxFrameSize),
])
if let maxConcurrentStreams = http2Config.maxConcurrentStreams {
http2HandlerHTTP2Settings.append(
HTTP2Setting(parameter: .maxConcurrentStreams, value: maxConcurrentStreams)
)
}
http2HandlerConnectionConfiguration.initialSettings = http2HandlerHTTP2Settings

var http2HandlerStreamConfiguration = NIOHTTP2Handler.StreamConfiguration()
http2HandlerStreamConfiguration.targetWindowSize = clampedTargetWindowSize

self = NIOHTTP2Handler.Configuration(
connection: http2HandlerConnectionConfiguration,
stream: http2HandlerStreamConfiguration
)
}

/// Window size which mustn't exceed `2^31 - 1` (RFC 9113 § 6.5.2).
private static func clampTargetWindowSize(_ targetWindowSize: Int) -> Int {
min(targetWindowSize, Int(Int32.max))
}

/// Max frame size must be in the range `2^14 ..< 2^24` (RFC 9113 § 4.2).
private static func clampMaxFrameSize(_ maxFrameSize: Int) -> Int {
let clampedMaxFrameSize: Int
if maxFrameSize >= (1 << 24) {
clampedMaxFrameSize = (1 << 24) - 1
} else if maxFrameSize < (1 << 14) {
clampedMaxFrameSize = (1 << 14)
} else {
clampedMaxFrameSize = maxFrameSize
}
return clampedMaxFrameSize
}
}
48 changes: 44 additions & 4 deletions Sources/HTTPServer/HTTPServerConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public struct HTTPServerConfiguration: Sendable {
///
/// Provides options for running the server with or without TLS encryption.
/// When using TLS, you must provide a certificate chain and private key.
public struct TLSConfiguration: Sendable {
public struct TLS: Sendable {
enum Backing {
case insecure
case certificateChainAndPrivateKey(
Expand All @@ -63,6 +63,40 @@ public struct HTTPServerConfiguration: Sendable {
}
}

/// HTTP/2 specific configuration.
public struct HTTP2: Sendable, Hashable {
/// The maximum frame size to be used in an HTTP/2 connection.
public var maxFrameSize: Int

/// The target window size for this connection.
///
/// - Note: This will also be set as the initial window size for the connection.
public var targetWindowSize: Int

/// The number of concurrent streams on the HTTP/2 connection.
public var maxConcurrentStreams: Int?

public init(
maxFrameSize: Int,
targetWindowSize: Int,
maxConcurrentStreams: Int?
) {
self.maxFrameSize = maxFrameSize
self.targetWindowSize = targetWindowSize
self.maxConcurrentStreams = maxConcurrentStreams
}

/// 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.
public static var defaults: Self {
Self(
maxFrameSize: 1 << 14,
targetWindowSize: (1 << 16) - 1,
maxConcurrentStreams: nil
)
}
}

/// Configuration for the backpressure strategy to use when reading requests and writing back responses.
public struct BackPressureStrategy: Sendable {
enum Backing {
Expand All @@ -89,24 +123,30 @@ public struct HTTPServerConfiguration: Sendable {
public var bindTarget: BindTarget

/// TLS configuration for the server.
public var tlSConfiguration: TLSConfiguration
public var tlSConfiguration: TLS

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

/// Backpressure strategy to use in the server.
public var http2: HTTP2

/// Create a new configuration.
/// - Parameters:
/// - bindTarget: A ``BindTarget``.
/// - tlsConfiguration: A ``TLSConfiguration``. Defaults to ``TLSConfiguration/insecure``.
/// - backpressureStrategy: A ``BackPressureStrategy``.
/// Defaults to ``BackPressureStrategy/watermark(low:high:)`` with a low watermark of 2 and a high of 10.
/// - http2: A ``HTTP2``. Defaults to ``HTTP2/defaults``.
public init(
bindTarget: BindTarget,
tlsConfiguration: TLSConfiguration = .insecure,
backpressureStrategy: BackPressureStrategy = .watermark(low: 2, high: 10)
tlsConfiguration: TLS = .insecure,
backpressureStrategy: BackPressureStrategy = .watermark(low: 2, high: 10),
http2: HTTP2 = .defaults
) {
self.bindTarget = bindTarget
self.tlSConfiguration = tlsConfiguration
self.backpressureStrategy = backpressureStrategy
self.http2 = http2
}
}
Loading