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 @@ -279,4 +279,40 @@ extension NIOQUIC.QUICConfiguration {
)
}
}

@available(anyAppleOS 26.0, *)
extension NIOQUIC.Authenticator {
/// Creates an `Authenticator` instance from X.509 TLS credentials.
///
/// Returns `nil` for raw public key credentials, because NIOQUIC reads the public/private key paths directly from
/// `QUICConfiguration.authenticationConfiguration` (no `Authenticator` instance is required in that case).
///
/// - Parameter transportSecurity: The server's transport security configuration.
///
/// - Throws:
/// - ``NIOHTTPServerConfigurationError/incompatibleTransportSecurity`` if `transportSecurity` is `.plaintext`.
/// - ``NIOHTTPServerConfigurationError/inMemoryOrReloadingTLSCredentialsNotSupportedOverHTTP3`` if the X.509
/// credentials are provided as in-memory `X509.Certificate`/`X509.Certificate.PrivateKey` objects or as a
/// `CertificateReloader` instance.
/// - An underlying error from `Authenticator`'s initializer if the certificate chain or private key cannot be
/// loaded.
convenience init(_ transportSecurity: NIOHTTPServerConfiguration.TransportSecurity) throws {
switch transportSecurity.backing {
case .plaintext:
throw NIOHTTPServerConfigurationError.incompatibleTransportSecurity

case .tls(let tlsCredentials), .mTLS(let tlsCredentials, _):
switch tlsCredentials.backing {
case .reloading:
throw NIOHTTPServerConfigurationError.onlyPEMFileCredentialsCurrentlySupportedOverHTTP3

case .pemFile(let certificateChainPath, let privateKeyPath):
try self.init(certificateFilePath: certificateChainPath, privateKeyFilePath: privateKeyPath)

case .inMemory(let certificateChain, let privateKey):
try self.init(certificates: certificateChain, privateKey: privateKey)
}
}
}
}
#endif // HTTP3
22 changes: 22 additions & 0 deletions Sources/NIOHTTPServer/NIOHTTPServer+Connection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import NIOCore
import NIOHTTP2
import NIOHTTPTypes

#if HTTP3
@_spi(HTTP3AsyncInterface) import NIOHTTP3
import NIOQUIC
#endif

@available(anyAppleOS 26.0, *)
extension NIOHTTPServer {
/// An active HTTP server connection.
Expand All @@ -32,15 +37,26 @@ extension NIOHTTPServer {
/// owns the channel and drives `executeThenClose`, so the writer is finished cleanly even if the connection
/// handler returns without calling ``handleRequests(handler:)``).
/// - `http2` carries the connection channel and stream multiplexer.
/// - `http3` carries an ``HTTP3ServerConnection``.
enum HTTPProtocol: Sendable {
case http1_1(
inbound: NIOAsyncChannelInboundStream<HTTPRequestPart>,
outbound: NIOAsyncChannelOutboundWriter<HTTPResponsePart>
)

case http2(
connectionChannel: any Channel,
multiplexer: NIOHTTP2Handler.AsyncStreamMultiplexer<NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>>
)

#if HTTP3
case http3(
connection: HTTP3ServerConnection<
NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
NIOQUIC.QUICStreamCreator
>
)
#endif
}

let server: NIOHTTPServer
Expand Down Expand Up @@ -76,13 +92,19 @@ extension NIOHTTPServer {
handler: handler,
context: context
)

case .http2(let connectionChannel, let multiplexer):
await server.handleHTTP2Connection(
connectionChannel: connectionChannel,
multiplexer: multiplexer,
handler: handler,
context: context
)

#if HTTP3
case .http3(let connection):
await server.handleHTTP3Connection(connection: connection, handler: handler, context: context)
#endif
}
}

Expand Down
8 changes: 6 additions & 2 deletions Sources/NIOHTTPServer/NIOHTTPServer+ConnectionContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ public import X509
extension NIOHTTPServer {
/// The application-level HTTP version negotiated for a connection.
public enum HTTPVersion: String, Sendable, Hashable {
case http1_1 = "http/1.1"
case http2 = "http/2"
case plaintextHTTP1_1 = "Plaintext HTTP/1.1"
case http1_1 = "HTTP/1.1"
case http2 = "HTTP/2"
#if HTTP3
case http3 = "HTTP/3"
#endif
}
}

Expand Down
26 changes: 5 additions & 21 deletions Sources/NIOHTTPServer/NIOHTTPServer+HTTP1_1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@ extension NIOHTTPServer {
) async {
do {
try await requestChannel.executeThenClose { inbound, outbound in
let context = NIOHTTPServer.makeHTTP1ConnectionContext(
requestChannel: requestChannel,
let context = ConnectionContext(
httpVersion: .plaintextHTTP1_1,
remoteAddress: try? NIOHTTPServer.SocketAddress(requestChannel.channel.remoteAddress),
localAddress: try? NIOHTTPServer.SocketAddress(requestChannel.channel.localAddress),
peerCertificateChainFuture: nil
)
let connection = Connection(
Expand Down Expand Up @@ -158,8 +160,6 @@ extension NIOHTTPServer {
throw error
}

try self.addressesBound(serverChannels.map { (serverChannel, _) in serverChannel.channel.localAddress })

return serverChannels
}

Expand All @@ -172,10 +172,7 @@ extension NIOHTTPServer {
channel.pipeline.configureHTTPServerPipeline().flatMapThrowing {
try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: isSecure))
try channel.pipeline.syncOperations.addHandler(HTTPKeepAliveHandler())
try channel
.pipeline
.syncOperations
.addTimeoutHandlers(self.configuration.connectionTimeouts)
try channel.pipeline.syncOperations.addTimeoutHandlers(self.configuration.connectionTimeouts)

return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
wrappingChannelSynchronously: channel,
Expand All @@ -184,19 +181,6 @@ extension NIOHTTPServer {
}
}

/// Builds a ``ConnectionContext`` for an HTTP/1.1 request channel.
static func makeHTTP1ConnectionContext(
requestChannel: NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
peerCertificateChainFuture: EventLoopFuture<NIOSSL.ValidatedCertificateChain?>?
) -> ConnectionContext {
ConnectionContext(
httpVersion: .http1_1,
remoteAddress: try? NIOHTTPServer.SocketAddress(requestChannel.channel.remoteAddress),
localAddress: try? NIOHTTPServer.SocketAddress(requestChannel.channel.localAddress),
peerCertificateChainFuture: peerCertificateChainFuture
)
}

/// Drives the request loop on an HTTP/1.1 connection that may carry
/// multiple serial requests (keep-alive). Invoked from
/// ``NIOHTTPServer/Connection/handleRequests(handler:)`` for the
Expand Down
Loading
Loading