From 7e795a6fe21587ff18f4ed044eb8edad19d0f15c Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 30 Apr 2024 11:18:56 +0000 Subject: [PATCH 1/5] Attempt at setting EventLoop executor for server channel --- .../Server/EventLoopExecutor.swift | 47 ++++++ .../Server/HTTP/HTTP1Channel.swift | 19 ++- Sources/HummingbirdCore/Server/Server.swift | 145 +++++++++++------- .../Server/ServerChildChannel.swift | 5 +- Sources/HummingbirdHTTP2/HTTP2Channel.swift | 86 +++++++---- 5 files changed, 208 insertions(+), 94 deletions(-) create mode 100644 Sources/HummingbirdCore/Server/EventLoopExecutor.swift diff --git a/Sources/HummingbirdCore/Server/EventLoopExecutor.swift b/Sources/HummingbirdCore/Server/EventLoopExecutor.swift new file mode 100644 index 000000000..8f0b67cfd --- /dev/null +++ b/Sources/HummingbirdCore/Server/EventLoopExecutor.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Hummingbird server framework project +// +// Copyright (c) 2024 the Hummingbird authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import NIOCore + +#if compiler(>=6.0) + final class EventLoopExecutor: TaskExecutor, SerialExecutor { + @usableFromInline let eventLoop: EventLoop + + init(eventLoop: EventLoop) { + self.eventLoop = eventLoop + } + + func asUnownedTaskExecutor() -> UnownedTaskExecutor { + UnownedTaskExecutor(ordinary: self) + } + + @inlinable + func enqueue(_ job: consuming ExecutorJob) { + let job = UnownedJob(job) + self.eventLoop.execute { + job.runSynchronously(on: self.asUnownedTaskExecutor()) + } + } + + @inlinable + func asUnownedSerialExecutor() -> UnownedSerialExecutor { + UnownedSerialExecutor(complexEquality: self) + } + + @inlinable + func isSameExclusiveExecutionContext(other: EventLoopExecutor) -> Bool { + self.eventLoop === other.eventLoop + } + } +#endif // swift(>=6.0) diff --git a/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift b/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift index 6c2d6e224..e2c0cb916 100644 --- a/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift +++ b/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift @@ -18,6 +18,10 @@ import NIOCore import NIOHTTPTypes import NIOHTTPTypesHTTP1 +extension NIOAsyncChannel: ChildChannel { + public var eventLoop: EventLoop { self.channel.eventLoop } +} + /// Child channel for processing HTTP1 public struct HTTP1Channel: ServerChildChannel, HTTPChannelHandler { public typealias Value = NIOAsyncChannel @@ -41,14 +45,14 @@ public struct HTTP1Channel: ServerChildChannel, HTTPChannelHandler { /// - Returns: Object to process input/output on child channel public func setup(channel: Channel, logger: Logger) -> EventLoopFuture { let childChannelHandlers: [any ChannelHandler] = - [HTTP1ToHTTPServerCodec(secure: false)] + - self.additionalChannelHandlers() + - [HTTPUserEventHandler(logger: logger)] + [HTTP1ToHTTPServerCodec(secure: false)] + self.additionalChannelHandlers() + [ + HTTPUserEventHandler(logger: logger) + ] return channel.eventLoop.makeCompletedFuture { try channel.pipeline.syncOperations.configureHTTPServerPipeline( - withPipeliningAssistance: false, // HTTP is pipelined by NIOAsyncChannel + withPipeliningAssistance: false, // HTTP is pipelined by NIOAsyncChannel withErrorHandling: true, - withOutboundHeaderValidation: false // Swift HTTP Types are already doing this validation + withOutboundHeaderValidation: false // Swift HTTP Types are already doing this validation ) try channel.pipeline.syncOperations.addHandlers(childChannelHandlers) return try NIOAsyncChannel( @@ -62,7 +66,10 @@ public struct HTTP1Channel: ServerChildChannel, HTTPChannelHandler { /// - Parameters: /// - value: Object to process input/output on child channel /// - logger: Logger to use while processing messages - public func handle(value asyncChannel: NIOCore.NIOAsyncChannel, logger: Logging.Logger) async { + public func handle( + value asyncChannel: NIOCore.NIOAsyncChannel, + logger: Logging.Logger + ) async { await handleHTTP(asyncChannel: asyncChannel, logger: logger) } diff --git a/Sources/HummingbirdCore/Server/Server.swift b/Sources/HummingbirdCore/Server/Server.swift index 5d9a44ec2..3b0ecaa77 100644 --- a/Sources/HummingbirdCore/Server/Server.swift +++ b/Sources/HummingbirdCore/Server/Server.swift @@ -16,11 +16,12 @@ import Logging import NIOCore import NIOExtras import NIOPosix +import ServiceLifecycle + #if canImport(Network) -import Network -import NIOTransportServices + import Network + import NIOTransportServices #endif -import ServiceLifecycle /// HTTP server class public actor Server: Service { @@ -118,9 +119,21 @@ public actor Server: Service { do { try await asyncChannel.executeThenClose { inbound in for try await childChannel in inbound { - group.addTask { - await childChannelSetup.handle(value: childChannel, logger: logger) - } + #if compiler(>=6.0) + group.addTask( + executorPreference: EventLoopExecutor( + eventLoop: childChannel.eventLoop + ) + ) { + await childChannelSetup.handle( + value: childChannel, logger: logger) + } + #else + group.addTask { + await childChannelSetup.handle( + value: childChannel, logger: logger) + } + #endif } } } catch { @@ -179,24 +192,30 @@ public actor Server: Service { /// Start server /// - Parameter responder: Object that provides responses to requests sent to the server /// - Returns: EventLoopFuture that is fulfilled when server has started - nonisolated func makeServer(childChannelSetup: ChildChannel, configuration: ServerConfiguration) async throws -> AsyncServerChannel { + nonisolated func makeServer(childChannelSetup: ChildChannel, configuration: ServerConfiguration) + async throws -> AsyncServerChannel + { let bootstrap: ServerBootstrapProtocol #if canImport(Network) - if let tsBootstrap = self.createTSBootstrap(configuration: configuration) { - bootstrap = tsBootstrap - } else { - #if os(iOS) || os(tvOS) - self.logger.warning("Running BSD sockets on iOS or tvOS is not recommended. Please use NIOTSEventLoopGroup, to run with the Network framework") - #endif - if configuration.tlsOptions.options != nil { - self.logger.warning("tlsOptions set in Configuration will not be applied to a BSD sockets server. Please use NIOTSEventLoopGroup, to run with the Network framework") + if let tsBootstrap = self.createTSBootstrap(configuration: configuration) { + bootstrap = tsBootstrap + } else { + #if os(iOS) || os(tvOS) + self.logger.warning( + "Running BSD sockets on iOS or tvOS is not recommended. Please use NIOTSEventLoopGroup, to run with the Network framework" + ) + #endif + if configuration.tlsOptions.options != nil { + self.logger.warning( + "tlsOptions set in Configuration will not be applied to a BSD sockets server. Please use NIOTSEventLoopGroup, to run with the Network framework" + ) + } + bootstrap = self.createSocketsBootstrap(configuration: configuration) } - bootstrap = self.createSocketsBootstrap(configuration: configuration) - } #else - bootstrap = self.createSocketsBootstrap( - configuration: configuration - ) + bootstrap = self.createSocketsBootstrap( + configuration: configuration + ) #endif do { @@ -212,7 +231,9 @@ public actor Server: Service { logger: self.logger ) } - self.logger.info("Server started and listening on \(host):\(asyncChannel.channel.localAddress?.port ?? port)") + self.logger.info( + "Server started and listening on \(host):\(asyncChannel.channel.localAddress?.port ?? port)" + ) return asyncChannel case .unixDomainSocket(let path): @@ -242,32 +263,45 @@ public actor Server: Service { return ServerBootstrap(group: self.eventLoopGroup) // Specify backlog and enable SO_REUSEADDR for the server itself .serverChannelOption(ChannelOptions.backlog, value: numericCast(configuration.backlog)) - .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: configuration.reuseAddress ? 1 : 0) - .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: configuration.reuseAddress ? 1 : 0) + .serverChannelOption( + ChannelOptions.socketOption(.so_reuseaddr), + value: configuration.reuseAddress ? 1 : 0 + ) + .childChannelOption( + ChannelOptions.socketOption(.so_reuseaddr), + value: configuration.reuseAddress ? 1 : 0 + ) .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 1) .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) } #if canImport(Network) - /// create a NIOTransportServices bootstrap using Network.framework - @available(macOS 10.14, iOS 12, tvOS 12, *) - private nonisolated func createTSBootstrap( - configuration: ServerConfiguration - ) -> NIOTSListenerBootstrap? { - guard let bootstrap = NIOTSListenerBootstrap(validatingGroup: self.eventLoopGroup)? - .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: configuration.reuseAddress ? 1 : 0) - // Set the handlers that are applied to the accepted Channels - .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: configuration.reuseAddress ? 1 : 0) - .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) - else { - return nil - } + /// create a NIOTransportServices bootstrap using Network.framework + @available(macOS 10.14, iOS 12, tvOS 12, *) + private nonisolated func createTSBootstrap( + configuration: ServerConfiguration + ) -> NIOTSListenerBootstrap? { + guard + let bootstrap = NIOTSListenerBootstrap(validatingGroup: self.eventLoopGroup)? + .serverChannelOption( + ChannelOptions.socketOption(.so_reuseaddr), + value: configuration.reuseAddress ? 1 : 0 + ) + // Set the handlers that are applied to the accepted Channels + .childChannelOption( + ChannelOptions.socketOption(.so_reuseaddr), + value: configuration.reuseAddress ? 1 : 0 + ) + .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) + else { + return nil + } - if let tlsOptions = configuration.tlsOptions.options { - return bootstrap.tlsOptions(tlsOptions) + if let tlsOptions = configuration.tlsOptions.options { + return bootstrap.tlsOptions(tlsOptions) + } + return bootstrap } - return bootstrap - } #endif } @@ -276,14 +310,16 @@ protocol ServerBootstrapProtocol { func bind( host: String, port: Int, - serverBackPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?, + serverBackPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies + .HighLowWatermark?, childChannelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture ) async throws -> NIOAsyncChannel func bind( unixDomainSocketPath: String, cleanupExistingSocketFile: Bool, - serverBackPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?, + serverBackPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies + .HighLowWatermark?, childChannelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture ) async throws -> NIOAsyncChannel } @@ -292,19 +328,20 @@ protocol ServerBootstrapProtocol { extension ServerBootstrap: ServerBootstrapProtocol {} #if canImport(Network) -@available(macOS 10.14, iOS 12, tvOS 12, *) -extension NIOTSListenerBootstrap: ServerBootstrapProtocol { - // need to be able to extend `NIOTSListenerBootstrap` to conform to `ServerBootstrapProtocol` - // before we can use TransportServices - func bind( - unixDomainSocketPath: String, - cleanupExistingSocketFile: Bool, - serverBackPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark?, - childChannelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture - ) async throws -> NIOAsyncChannel { - preconditionFailure("Binding to a unixDomainSocketPath is currently not available") + @available(macOS 10.14, iOS 12, tvOS 12, *) + extension NIOTSListenerBootstrap: ServerBootstrapProtocol { + // need to be able to extend `NIOTSListenerBootstrap` to conform to `ServerBootstrapProtocol` + // before we can use TransportServices + func bind( + unixDomainSocketPath: String, + cleanupExistingSocketFile: Bool, + serverBackPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies + .HighLowWatermark?, + childChannelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture + ) async throws -> NIOAsyncChannel { + preconditionFailure("Binding to a unixDomainSocketPath is currently not available") + } } -} #endif extension Server: CustomStringConvertible { diff --git a/Sources/HummingbirdCore/Server/ServerChildChannel.swift b/Sources/HummingbirdCore/Server/ServerChildChannel.swift index 3654731f4..30d10715a 100644 --- a/Sources/HummingbirdCore/Server/ServerChildChannel.swift +++ b/Sources/HummingbirdCore/Server/ServerChildChannel.swift @@ -16,9 +16,12 @@ import Logging import NIOCore import ServiceLifecycle +public protocol ChildChannel: Sendable { + var eventLoop: EventLoop { get } +} /// HTTPServer child channel setup protocol public protocol ServerChildChannel: Sendable { - associatedtype Value: Sendable + associatedtype Value: ChildChannel /// Setup child channel /// - Parameters: diff --git a/Sources/HummingbirdHTTP2/HTTP2Channel.swift b/Sources/HummingbirdHTTP2/HTTP2Channel.swift index 355d007e6..9d33e84d0 100644 --- a/Sources/HummingbirdHTTP2/HTTP2Channel.swift +++ b/Sources/HummingbirdHTTP2/HTTP2Channel.swift @@ -25,7 +25,19 @@ import NIOSSL /// Child channel for processing HTTP1 with the option of upgrading to HTTP2 public struct HTTP2UpgradeChannel: HTTPChannelHandler { - public typealias Value = EventLoopFuture, NIOHTTP2Handler.AsyncStreamMultiplexer)>> + public struct Value: ChildChannel { + let negotiatedResult: + EventLoopFuture< + NIONegotiatedHTTPVersion< + HTTP1Channel.Value, + ( + NIOAsyncChannel, + NIOHTTP2Handler.AsyncStreamMultiplexer + ) + > + > + public let eventLoop: EventLoop + } private let sslContext: NIOSSLContext private let http1: HTTP1Channel @@ -40,13 +52,16 @@ public struct HTTP2UpgradeChannel: HTTPChannelHandler { public init( tlsConfiguration: TLSConfiguration, additionalChannelHandlers: @escaping @Sendable () -> [any RemovableChannelHandler] = { [] }, - responder: @escaping @Sendable (Request, Channel) async throws -> Response = { _, _ in throw HTTPError(.notImplemented) } + responder: @escaping @Sendable (Request, Channel) async throws -> Response = { _, _ in + throw HTTPError(.notImplemented) + } ) throws { var tlsConfiguration = tlsConfiguration tlsConfiguration.applicationProtocols = NIOHTTP2SupportedALPNProtocols self.sslContext = try NIOSSLContext(configuration: tlsConfiguration) self.additionalChannelHandlers = additionalChannelHandlers - self.http1 = HTTP1Channel(responder: responder, additionalChannelHandlers: additionalChannelHandlers) + self.http1 = HTTP1Channel( + responder: responder, additionalChannelHandlers: additionalChannelHandlers) } /// Setup child channel for HTTP1 with HTTP2 upgrade @@ -56,42 +71,47 @@ public struct HTTP2UpgradeChannel: HTTPChannelHandler { /// - Returns: Object to process input/output on child channel public func setup(channel: Channel, logger: Logger) -> EventLoopFuture { do { - try channel.pipeline.syncOperations.addHandler(NIOSSLServerHandler(context: self.sslContext)) + try channel.pipeline.syncOperations.addHandler( + NIOSSLServerHandler(context: self.sslContext)) } catch { return channel.eventLoop.makeFailedFuture(error) } - return channel.configureAsyncHTTPServerPipeline { http1Channel -> EventLoopFuture in - let childChannelHandlers: [ChannelHandler] = - [HTTP1ToHTTPServerCodec(secure: false)] + - self.additionalChannelHandlers() + - [HTTPUserEventHandler(logger: logger)] + return + channel.configureAsyncHTTPServerPipeline { + http1Channel -> EventLoopFuture in + let childChannelHandlers: [ChannelHandler] = + [HTTP1ToHTTPServerCodec(secure: false)] + self.additionalChannelHandlers() + [ + HTTPUserEventHandler(logger: logger) + ] - return http1Channel - .pipeline - .addHandlers(childChannelHandlers) - .flatMapThrowing { - try HTTP1Channel.Value(wrappingChannelSynchronously: http1Channel) + return http1Channel + .pipeline + .addHandlers(childChannelHandlers) + .flatMapThrowing { + try HTTP1Channel.Value(wrappingChannelSynchronously: http1Channel) + } + } http2ConnectionInitializer: { + http2Channel -> EventLoopFuture> in + http2Channel.eventLoop.makeCompletedFuture { + try NIOAsyncChannel( + wrappingChannelSynchronously: http2Channel) } - } http2ConnectionInitializer: { http2Channel -> EventLoopFuture> in - http2Channel.eventLoop.makeCompletedFuture { - try NIOAsyncChannel(wrappingChannelSynchronously: http2Channel) - } - } http2StreamInitializer: { http2ChildChannel -> EventLoopFuture in - let childChannelHandlers: [ChannelHandler] = - self.additionalChannelHandlers() + [ - HTTPUserEventHandler(logger: logger), - ] + } http2StreamInitializer: { http2ChildChannel -> EventLoopFuture in + let childChannelHandlers: [ChannelHandler] = + self.additionalChannelHandlers() + [ + HTTPUserEventHandler(logger: logger) + ] - return http2ChildChannel - .pipeline - .addHandler(HTTP2FramePayloadToHTTPServerCodec()) - .flatMap { - http2ChildChannel.pipeline.addHandlers(childChannelHandlers) - }.flatMapThrowing { - try HTTP1Channel.Value(wrappingChannelSynchronously: http2ChildChannel) - } - } + return http2ChildChannel + .pipeline + .addHandler(HTTP2FramePayloadToHTTPServerCodec()) + .flatMap { + http2ChildChannel.pipeline.addHandlers(childChannelHandlers) + }.flatMapThrowing { + try HTTP1Channel.Value(wrappingChannelSynchronously: http2ChildChannel) + } + }.map { Value(negotiatedResult: $0, eventLoop: channel.eventLoop) } } /// handle messages being passed down the channel pipeline @@ -100,7 +120,7 @@ public struct HTTP2UpgradeChannel: HTTPChannelHandler { /// - logger: Logger to use while processing messages public func handle(value: Value, logger: Logger) async { do { - let channel = try await value.get() + let channel = try await value.negotiatedResult.get() switch channel { case .http1_1(let http1): await handleHTTP(asyncChannel: http1, logger: logger) From b93e85a2cac70f5f2d193d34fd39cc3d7b0d4bd3 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 30 Apr 2024 12:29:02 +0000 Subject: [PATCH 2/5] Store executors in EventLoop Executor Dictionary --- .../Server/EventLoopExecutor.swift | 16 ++++++++++++++++ Sources/HummingbirdCore/Server/Server.swift | 9 ++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Sources/HummingbirdCore/Server/EventLoopExecutor.swift b/Sources/HummingbirdCore/Server/EventLoopExecutor.swift index 8f0b67cfd..5c42cdf78 100644 --- a/Sources/HummingbirdCore/Server/EventLoopExecutor.swift +++ b/Sources/HummingbirdCore/Server/EventLoopExecutor.swift @@ -44,4 +44,20 @@ import NIOCore self.eventLoop === other.eventLoop } } + + struct EventLoopExecutorMap { + init(eventLoopGroup: EventLoopGroup) { + var executors: [ObjectIdentifier: EventLoopExecutor] = [:] + for eventLoop in eventLoopGroup.makeIterator() { + executors[ObjectIdentifier(eventLoop)] = EventLoopExecutor(eventLoop: eventLoop) + } + self.executors = executors + } + + subscript(eventLoop: EventLoop) -> EventLoopExecutor? { + return self.executors[ObjectIdentifier(eventLoop)] + } + + let executors: [ObjectIdentifier: EventLoopExecutor] + } #endif // swift(>=6.0) diff --git a/Sources/HummingbirdCore/Server/Server.swift b/Sources/HummingbirdCore/Server/Server.swift index 3b0ecaa77..294784cb3 100644 --- a/Sources/HummingbirdCore/Server/Server.swift +++ b/Sources/HummingbirdCore/Server/Server.swift @@ -114,6 +114,10 @@ public actor Server: Service { await onServerRunning?(asyncChannel.channel) let logger = self.logger + #if compiler(>=6.0) + let eventLoopExecutorMap = EventLoopExecutorMap( + eventLoopGroup: self.eventLoopGroup) + #endif // We can now start to handle our work. await withDiscardingTaskGroup { group in do { @@ -121,9 +125,8 @@ public actor Server: Service { for try await childChannel in inbound { #if compiler(>=6.0) group.addTask( - executorPreference: EventLoopExecutor( - eventLoop: childChannel.eventLoop - ) + executorPreference: eventLoopExecutorMap[ + childChannel.eventLoop] ) { await childChannelSetup.handle( value: childChannel, logger: logger) From 052b178935e0055aa927f83bee7026d6f63746b4 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 30 Apr 2024 13:03:05 +0000 Subject: [PATCH 3/5] ChildChannelValue --- Sources/HummingbirdCore/Server/ServerChildChannel.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/HummingbirdCore/Server/ServerChildChannel.swift b/Sources/HummingbirdCore/Server/ServerChildChannel.swift index 30d10715a..c93f9ed1c 100644 --- a/Sources/HummingbirdCore/Server/ServerChildChannel.swift +++ b/Sources/HummingbirdCore/Server/ServerChildChannel.swift @@ -16,12 +16,13 @@ import Logging import NIOCore import ServiceLifecycle -public protocol ChildChannel: Sendable { +public protocol ChildChannelValue: Sendable { var eventLoop: EventLoop { get } } + /// HTTPServer child channel setup protocol public protocol ServerChildChannel: Sendable { - associatedtype Value: ChildChannel + associatedtype Value: ChildChannelValue /// Setup child channel /// - Parameters: From 06b2786ba55d40cb51fd3932935e6d455242390b Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 30 Apr 2024 13:04:26 +0000 Subject: [PATCH 4/5] Conform Values to ChildChannelValue --- Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift | 2 +- Sources/HummingbirdHTTP2/HTTP2Channel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift b/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift index e2c0cb916..a292d32bb 100644 --- a/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift +++ b/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift @@ -18,7 +18,7 @@ import NIOCore import NIOHTTPTypes import NIOHTTPTypesHTTP1 -extension NIOAsyncChannel: ChildChannel { +extension NIOAsyncChannel: ChildChannelValue { public var eventLoop: EventLoop { self.channel.eventLoop } } diff --git a/Sources/HummingbirdHTTP2/HTTP2Channel.swift b/Sources/HummingbirdHTTP2/HTTP2Channel.swift index 9d33e84d0..a54095254 100644 --- a/Sources/HummingbirdHTTP2/HTTP2Channel.swift +++ b/Sources/HummingbirdHTTP2/HTTP2Channel.swift @@ -25,7 +25,7 @@ import NIOSSL /// Child channel for processing HTTP1 with the option of upgrading to HTTP2 public struct HTTP2UpgradeChannel: HTTPChannelHandler { - public struct Value: ChildChannel { + public struct Value: ChildChannelValue { let negotiatedResult: EventLoopFuture< NIONegotiatedHTTPVersion< From 2ab6f43b15758dd0cacfffdfe741826c6a2d571d Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 30 Apr 2024 14:20:45 +0100 Subject: [PATCH 5/5] SwiftFormat --- .../Server/EventLoopExecutor.swift | 70 ++++----- .../Server/HTTP/HTTP1Channel.swift | 6 +- Sources/HummingbirdCore/Server/Server.swift | 141 +++++++++--------- Sources/HummingbirdHTTP2/HTTP2Channel.swift | 7 +- 4 files changed, 114 insertions(+), 110 deletions(-) diff --git a/Sources/HummingbirdCore/Server/EventLoopExecutor.swift b/Sources/HummingbirdCore/Server/EventLoopExecutor.swift index 5c42cdf78..8db501365 100644 --- a/Sources/HummingbirdCore/Server/EventLoopExecutor.swift +++ b/Sources/HummingbirdCore/Server/EventLoopExecutor.swift @@ -15,49 +15,49 @@ import NIOCore #if compiler(>=6.0) - final class EventLoopExecutor: TaskExecutor, SerialExecutor { - @usableFromInline let eventLoop: EventLoop +final class EventLoopExecutor: TaskExecutor, SerialExecutor { + @usableFromInline let eventLoop: EventLoop - init(eventLoop: EventLoop) { - self.eventLoop = eventLoop - } - - func asUnownedTaskExecutor() -> UnownedTaskExecutor { - UnownedTaskExecutor(ordinary: self) - } + init(eventLoop: EventLoop) { + self.eventLoop = eventLoop + } - @inlinable - func enqueue(_ job: consuming ExecutorJob) { - let job = UnownedJob(job) - self.eventLoop.execute { - job.runSynchronously(on: self.asUnownedTaskExecutor()) - } - } + func asUnownedTaskExecutor() -> UnownedTaskExecutor { + UnownedTaskExecutor(ordinary: self) + } - @inlinable - func asUnownedSerialExecutor() -> UnownedSerialExecutor { - UnownedSerialExecutor(complexEquality: self) + @inlinable + func enqueue(_ job: consuming ExecutorJob) { + let job = UnownedJob(job) + self.eventLoop.execute { + job.runSynchronously(on: self.asUnownedTaskExecutor()) } + } - @inlinable - func isSameExclusiveExecutionContext(other: EventLoopExecutor) -> Bool { - self.eventLoop === other.eventLoop - } + @inlinable + func asUnownedSerialExecutor() -> UnownedSerialExecutor { + UnownedSerialExecutor(complexEquality: self) } - struct EventLoopExecutorMap { - init(eventLoopGroup: EventLoopGroup) { - var executors: [ObjectIdentifier: EventLoopExecutor] = [:] - for eventLoop in eventLoopGroup.makeIterator() { - executors[ObjectIdentifier(eventLoop)] = EventLoopExecutor(eventLoop: eventLoop) - } - self.executors = executors - } + @inlinable + func isSameExclusiveExecutionContext(other: EventLoopExecutor) -> Bool { + self.eventLoop === other.eventLoop + } +} - subscript(eventLoop: EventLoop) -> EventLoopExecutor? { - return self.executors[ObjectIdentifier(eventLoop)] +struct EventLoopExecutorMap { + init(eventLoopGroup: EventLoopGroup) { + var executors: [ObjectIdentifier: EventLoopExecutor] = [:] + for eventLoop in eventLoopGroup.makeIterator() { + executors[ObjectIdentifier(eventLoop)] = EventLoopExecutor(eventLoop: eventLoop) } + self.executors = executors + } - let executors: [ObjectIdentifier: EventLoopExecutor] + subscript(eventLoop: EventLoop) -> EventLoopExecutor? { + return self.executors[ObjectIdentifier(eventLoop)] } -#endif // swift(>=6.0) + + let executors: [ObjectIdentifier: EventLoopExecutor] +} +#endif // swift(>=6.0) diff --git a/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift b/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift index a292d32bb..9a8efe112 100644 --- a/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift +++ b/Sources/HummingbirdCore/Server/HTTP/HTTP1Channel.swift @@ -46,13 +46,13 @@ public struct HTTP1Channel: ServerChildChannel, HTTPChannelHandler { public func setup(channel: Channel, logger: Logger) -> EventLoopFuture { let childChannelHandlers: [any ChannelHandler] = [HTTP1ToHTTPServerCodec(secure: false)] + self.additionalChannelHandlers() + [ - HTTPUserEventHandler(logger: logger) + HTTPUserEventHandler(logger: logger), ] return channel.eventLoop.makeCompletedFuture { try channel.pipeline.syncOperations.configureHTTPServerPipeline( - withPipeliningAssistance: false, // HTTP is pipelined by NIOAsyncChannel + withPipeliningAssistance: false, // HTTP is pipelined by NIOAsyncChannel withErrorHandling: true, - withOutboundHeaderValidation: false // Swift HTTP Types are already doing this validation + withOutboundHeaderValidation: false // Swift HTTP Types are already doing this validation ) try channel.pipeline.syncOperations.addHandlers(childChannelHandlers) return try NIOAsyncChannel( diff --git a/Sources/HummingbirdCore/Server/Server.swift b/Sources/HummingbirdCore/Server/Server.swift index 294784cb3..443bad83b 100644 --- a/Sources/HummingbirdCore/Server/Server.swift +++ b/Sources/HummingbirdCore/Server/Server.swift @@ -19,8 +19,8 @@ import NIOPosix import ServiceLifecycle #if canImport(Network) - import Network - import NIOTransportServices +import Network +import NIOTransportServices #endif /// HTTP server class @@ -115,8 +115,8 @@ public actor Server: Service { let logger = self.logger #if compiler(>=6.0) - let eventLoopExecutorMap = EventLoopExecutorMap( - eventLoopGroup: self.eventLoopGroup) + let eventLoopExecutorMap = EventLoopExecutorMap( + eventLoopGroup: self.eventLoopGroup) #endif // We can now start to handle our work. await withDiscardingTaskGroup { group in @@ -124,18 +124,21 @@ public actor Server: Service { try await asyncChannel.executeThenClose { inbound in for try await childChannel in inbound { #if compiler(>=6.0) - group.addTask( - executorPreference: eventLoopExecutorMap[ - childChannel.eventLoop] - ) { - await childChannelSetup.handle( - value: childChannel, logger: logger) - } + group.addTask( + executorPreference: eventLoopExecutorMap[ + childChannel.eventLoop + ] + ) { + await childChannelSetup.handle( + value: childChannel, logger: logger + ) + } #else - group.addTask { - await childChannelSetup.handle( - value: childChannel, logger: logger) - } + group.addTask { + await childChannelSetup.handle( + value: childChannel, logger: logger + ) + } #endif } } @@ -200,25 +203,25 @@ public actor Server: Service { { let bootstrap: ServerBootstrapProtocol #if canImport(Network) - if let tsBootstrap = self.createTSBootstrap(configuration: configuration) { - bootstrap = tsBootstrap - } else { - #if os(iOS) || os(tvOS) - self.logger.warning( - "Running BSD sockets on iOS or tvOS is not recommended. Please use NIOTSEventLoopGroup, to run with the Network framework" - ) - #endif - if configuration.tlsOptions.options != nil { - self.logger.warning( - "tlsOptions set in Configuration will not be applied to a BSD sockets server. Please use NIOTSEventLoopGroup, to run with the Network framework" - ) - } - bootstrap = self.createSocketsBootstrap(configuration: configuration) + if let tsBootstrap = self.createTSBootstrap(configuration: configuration) { + bootstrap = tsBootstrap + } else { + #if os(iOS) || os(tvOS) + self.logger.warning( + "Running BSD sockets on iOS or tvOS is not recommended. Please use NIOTSEventLoopGroup, to run with the Network framework" + ) + #endif + if configuration.tlsOptions.options != nil { + self.logger.warning( + "tlsOptions set in Configuration will not be applied to a BSD sockets server. Please use NIOTSEventLoopGroup, to run with the Network framework" + ) } + bootstrap = self.createSocketsBootstrap(configuration: configuration) + } #else - bootstrap = self.createSocketsBootstrap( - configuration: configuration - ) + bootstrap = self.createSocketsBootstrap( + configuration: configuration + ) #endif do { @@ -279,32 +282,32 @@ public actor Server: Service { } #if canImport(Network) - /// create a NIOTransportServices bootstrap using Network.framework - @available(macOS 10.14, iOS 12, tvOS 12, *) - private nonisolated func createTSBootstrap( - configuration: ServerConfiguration - ) -> NIOTSListenerBootstrap? { - guard - let bootstrap = NIOTSListenerBootstrap(validatingGroup: self.eventLoopGroup)? - .serverChannelOption( - ChannelOptions.socketOption(.so_reuseaddr), - value: configuration.reuseAddress ? 1 : 0 - ) - // Set the handlers that are applied to the accepted Channels - .childChannelOption( - ChannelOptions.socketOption(.so_reuseaddr), - value: configuration.reuseAddress ? 1 : 0 - ) - .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) - else { - return nil - } + /// create a NIOTransportServices bootstrap using Network.framework + @available(macOS 10.14, iOS 12, tvOS 12, *) + private nonisolated func createTSBootstrap( + configuration: ServerConfiguration + ) -> NIOTSListenerBootstrap? { + guard + let bootstrap = NIOTSListenerBootstrap(validatingGroup: self.eventLoopGroup)? + .serverChannelOption( + ChannelOptions.socketOption(.so_reuseaddr), + value: configuration.reuseAddress ? 1 : 0 + ) + // Set the handlers that are applied to the accepted Channels + .childChannelOption( + ChannelOptions.socketOption(.so_reuseaddr), + value: configuration.reuseAddress ? 1 : 0 + ) + .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) + else { + return nil + } - if let tlsOptions = configuration.tlsOptions.options { - return bootstrap.tlsOptions(tlsOptions) - } - return bootstrap + if let tlsOptions = configuration.tlsOptions.options { + return bootstrap.tlsOptions(tlsOptions) } + return bootstrap + } #endif } @@ -331,20 +334,20 @@ protocol ServerBootstrapProtocol { extension ServerBootstrap: ServerBootstrapProtocol {} #if canImport(Network) - @available(macOS 10.14, iOS 12, tvOS 12, *) - extension NIOTSListenerBootstrap: ServerBootstrapProtocol { - // need to be able to extend `NIOTSListenerBootstrap` to conform to `ServerBootstrapProtocol` - // before we can use TransportServices - func bind( - unixDomainSocketPath: String, - cleanupExistingSocketFile: Bool, - serverBackPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies - .HighLowWatermark?, - childChannelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture - ) async throws -> NIOAsyncChannel { - preconditionFailure("Binding to a unixDomainSocketPath is currently not available") - } +@available(macOS 10.14, iOS 12, tvOS 12, *) +extension NIOTSListenerBootstrap: ServerBootstrapProtocol { + // need to be able to extend `NIOTSListenerBootstrap` to conform to `ServerBootstrapProtocol` + // before we can use TransportServices + func bind( + unixDomainSocketPath: String, + cleanupExistingSocketFile: Bool, + serverBackPressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies + .HighLowWatermark?, + childChannelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture + ) async throws -> NIOAsyncChannel { + preconditionFailure("Binding to a unixDomainSocketPath is currently not available") } +} #endif extension Server: CustomStringConvertible { diff --git a/Sources/HummingbirdHTTP2/HTTP2Channel.swift b/Sources/HummingbirdHTTP2/HTTP2Channel.swift index a54095254..c5429c470 100644 --- a/Sources/HummingbirdHTTP2/HTTP2Channel.swift +++ b/Sources/HummingbirdHTTP2/HTTP2Channel.swift @@ -61,7 +61,8 @@ public struct HTTP2UpgradeChannel: HTTPChannelHandler { self.sslContext = try NIOSSLContext(configuration: tlsConfiguration) self.additionalChannelHandlers = additionalChannelHandlers self.http1 = HTTP1Channel( - responder: responder, additionalChannelHandlers: additionalChannelHandlers) + responder: responder, additionalChannelHandlers: additionalChannelHandlers + ) } /// Setup child channel for HTTP1 with HTTP2 upgrade @@ -82,7 +83,7 @@ public struct HTTP2UpgradeChannel: HTTPChannelHandler { http1Channel -> EventLoopFuture in let childChannelHandlers: [ChannelHandler] = [HTTP1ToHTTPServerCodec(secure: false)] + self.additionalChannelHandlers() + [ - HTTPUserEventHandler(logger: logger) + HTTPUserEventHandler(logger: logger), ] return http1Channel @@ -100,7 +101,7 @@ public struct HTTP2UpgradeChannel: HTTPChannelHandler { } http2StreamInitializer: { http2ChildChannel -> EventLoopFuture in let childChannelHandlers: [ChannelHandler] = self.additionalChannelHandlers() + [ - HTTPUserEventHandler(logger: logger) + HTTPUserEventHandler(logger: logger), ] return http2ChildChannel