diff --git a/Sources/Hummingbird/Application.swift b/Sources/Hummingbird/Application.swift index 46ca6b67d..7d69dfec0 100644 --- a/Sources/Hummingbird/Application.swift +++ b/Sources/Hummingbird/Application.swift @@ -50,7 +50,7 @@ public protocol ApplicationProtocol: Service where Context: RequestContext { /// Build the responder var responder: Responder { get async throws } /// Server channel builder - var server: HTTPChannelBuilder { get } + var server: HTTPServerBuilder { get } /// event loop group used by application var eventLoopGroup: EventLoopGroup { get } @@ -69,7 +69,7 @@ public protocol ApplicationProtocol: Service where Context: RequestContext { extension ApplicationProtocol { /// Server channel setup - public var server: HTTPChannelBuilder { .http1() } + public var server: HTTPServerBuilder { .http1() } } extension ApplicationProtocol { @@ -94,8 +94,12 @@ extension ApplicationProtocol { let dateCache = DateCache() let responder = try await self.responder - // Function responding to HTTP request - @Sendable func respond(to request: Request, channel: Channel) async throws -> Response { + // create server `Service`` + let server = try self.server.buildServer( + configuration: self.configuration.httpServer, + eventLoopGroup: self.eventLoopGroup, + logger: self.logger + ) { request, channel in let context = Self.Responder.Context( channel: channel, logger: self.logger.with(metadataKey: "hb_id", value: .stringConvertible(RequestID())) @@ -108,16 +112,9 @@ extension ApplicationProtocol { response.headers[.server] = serverName } return response + } onServerRunning: { + await self.onServerRunning($0) } - // get server channel - let serverChannel = try self.server.build(respond) - // create server `Service`` - let server = serverChannel.server( - configuration: self.configuration.httpServer, - onServerRunning: self.onServerRunning, - eventLoopGroup: self.eventLoopGroup, - logger: self.logger - ) let serverService = server.withPrelude { for process in self.processesRunBeforeServerStart { try await process() @@ -173,7 +170,7 @@ public struct Application: ApplicationProtocol where R /// on server running private var _onServerRunning: @Sendable (Channel) async -> Void /// Server channel setup - public let server: HTTPChannelBuilder + public let server: HTTPServerBuilder /// services attached to the application. public var services: [any Service] /// Processes to be run before server is started @@ -193,7 +190,7 @@ public struct Application: ApplicationProtocol where R /// - logger: Logger application uses public init( responder: Responder, - server: HTTPChannelBuilder = .http1(), + server: HTTPServerBuilder = .http1(), configuration: ApplicationConfiguration = ApplicationConfiguration(), services: [Service] = [], onServerRunning: @escaping @Sendable (Channel) async -> Void = { _ in }, @@ -229,7 +226,7 @@ public struct Application: ApplicationProtocol where R /// - logger: Logger application uses public init( router: ResponderBuilder, - server: HTTPChannelBuilder = .http1(), + server: HTTPServerBuilder = .http1(), configuration: ApplicationConfiguration = ApplicationConfiguration(), services: [Service] = [], onServerRunning: @escaping @Sendable (Channel) async -> Void = { _ in }, diff --git a/Sources/HummingbirdCore/Server/HTTP/HTTPChannelBuilder.swift b/Sources/HummingbirdCore/Server/HTTP/HTTPServerBuilder.swift similarity index 50% rename from Sources/HummingbirdCore/Server/HTTP/HTTPChannelBuilder.swift rename to Sources/HummingbirdCore/Server/HTTP/HTTPServerBuilder.swift index 9995193bb..e5f0b4b17 100644 --- a/Sources/HummingbirdCore/Server/HTTP/HTTPChannelBuilder.swift +++ b/Sources/HummingbirdCore/Server/HTTP/HTTPServerBuilder.swift @@ -12,25 +12,46 @@ // //===----------------------------------------------------------------------===// +import Logging import NIOCore +import ServiceLifecycle -/// Build Channel Setup that takes an HTTP responder +/// Build server that takes an HTTP responder /// /// Used when building an ``Hummingbird/Application``. It delays the building -/// of the ``ServerChildChannel`` until the HTTP responder has been built. -public struct HTTPChannelBuilder: Sendable { +/// of the ``ServerChildChannel`` and ``Server`` until the HTTP responder has been built. +public struct HTTPServerBuilder: Sendable { /// build child channel from HTTP responder - public let build: @Sendable (@escaping HTTPChannelHandler.Responder) throws -> any ServerChildChannel + package let buildChildChannel: @Sendable (@escaping HTTPChannelHandler.Responder) throws -> any ServerChildChannel - /// Initialize HTTPChannelBuilder + /// Initialize HTTPServerBuilder /// - Parameter build: closure building child channel from HTTP responder public init(_ build: @escaping @Sendable (@escaping HTTPChannelHandler.Responder) throws -> any ServerChildChannel) { - self.build = build + self.buildChildChannel = build + } + + /// Build server + /// - Parameters: + /// - configuration: Server configuration + /// - eventLoopGroup: EventLoopGroup used by server + /// - logger: Logger used by server + /// - responder: HTTP responder + /// - onServerRunning: Closure to run once server is up and running + /// - Returns: Server Service + public func buildServer( + configuration: ServerConfiguration, + eventLoopGroup: EventLoopGroup, + logger: Logger, + responder: @escaping HTTPChannelHandler.Responder, + onServerRunning: (@Sendable (Channel) async -> Void)? = { _ in } + ) throws -> Service { + let childChannel = try buildChildChannel(responder) + return childChannel.server(configuration: configuration, onServerRunning: onServerRunning, eventLoopGroup: eventLoopGroup, logger: logger) } } -extension HTTPChannelBuilder { - /// Build HTTP1 channel +extension HTTPServerBuilder { + /// Return a `HTTPServerBuilder` that will build a HTTP1 server /// /// Use in ``Hummingbird/Application`` initialization. /// ``` @@ -40,10 +61,10 @@ extension HTTPChannelBuilder { /// ) /// ``` /// - Parameter additionalChannelHandlers: Additional channel handlers to add to channel pipeline - /// - Returns: HTTPChannelHandler builder + /// - Returns: HTTPServerBuilder builder public static func http1( additionalChannelHandlers: @autoclosure @escaping @Sendable () -> [any RemovableChannelHandler] = [] - ) -> HTTPChannelBuilder { + ) -> HTTPServerBuilder { return .init { responder in return HTTP1Channel(responder: responder, additionalChannelHandlers: additionalChannelHandlers) } diff --git a/Sources/HummingbirdHTTP2/HTTP2ChannelBuilder.swift b/Sources/HummingbirdHTTP2/HTTPServerBuilder+http2.swift similarity index 95% rename from Sources/HummingbirdHTTP2/HTTP2ChannelBuilder.swift rename to Sources/HummingbirdHTTP2/HTTPServerBuilder+http2.swift index 87e198e44..cee1d59aa 100644 --- a/Sources/HummingbirdHTTP2/HTTP2ChannelBuilder.swift +++ b/Sources/HummingbirdHTTP2/HTTPServerBuilder+http2.swift @@ -16,7 +16,7 @@ import HummingbirdCore import NIOCore import NIOSSL -extension HTTPChannelBuilder { +extension HTTPServerBuilder { /// Build HTTP channel with HTTP2 upgrade /// /// Use in ``Hummingbird/Application`` initialization. @@ -33,7 +33,7 @@ extension HTTPChannelBuilder { public static func http2Upgrade( tlsConfiguration: TLSConfiguration, additionalChannelHandlers: @autoclosure @escaping @Sendable () -> [any RemovableChannelHandler] = [] - ) throws -> HTTPChannelBuilder { + ) throws -> HTTPServerBuilder { return .init { responder in return try HTTP2UpgradeChannel( tlsConfiguration: tlsConfiguration, diff --git a/Sources/HummingbirdTLS/TLSChannelBuilder.swift b/Sources/HummingbirdTLS/HTTPServerBuilder+tls.swift similarity index 80% rename from Sources/HummingbirdTLS/TLSChannelBuilder.swift rename to Sources/HummingbirdTLS/HTTPServerBuilder+tls.swift index 21d53bc06..52325b1de 100644 --- a/Sources/HummingbirdTLS/TLSChannelBuilder.swift +++ b/Sources/HummingbirdTLS/HTTPServerBuilder+tls.swift @@ -15,8 +15,8 @@ import HummingbirdCore import NIOSSL -extension HTTPChannelBuilder { - /// Build child channel supporting HTTP with TLS +extension HTTPServerBuilder { + /// Build server supporting HTTP with TLS /// /// Use in ``Hummingbird/Application`` initialization. /// ``` @@ -30,11 +30,11 @@ extension HTTPChannelBuilder { /// - tlsConfiguration: TLS configuration /// - Returns: HTTPChannelHandler builder public static func tls( - _ base: HTTPChannelBuilder = .http1(), + _ base: HTTPServerBuilder = .http1(), tlsConfiguration: TLSConfiguration - ) throws -> HTTPChannelBuilder { + ) throws -> HTTPServerBuilder { return .init { responder in - return try base.build(responder).withTLS(tlsConfiguration: tlsConfiguration) + return try base.buildChildChannel(responder).withTLS(tlsConfiguration: tlsConfiguration) } } } diff --git a/Sources/HummingbirdTesting/TestApplication.swift b/Sources/HummingbirdTesting/TestApplication.swift index 86dc4668b..19cbd9ac8 100644 --- a/Sources/HummingbirdTesting/TestApplication.swift +++ b/Sources/HummingbirdTesting/TestApplication.swift @@ -30,7 +30,7 @@ internal struct TestApplication: ApplicationProtoc get async throws { try await self.base.responder } } - var server: HTTPChannelBuilder { + var server: HTTPServerBuilder { self.base.server } diff --git a/Tests/HummingbirdCoreTests/CoreTests.swift b/Tests/HummingbirdCoreTests/CoreTests.swift index 234591039..3b217c0d5 100644 --- a/Tests/HummingbirdCoreTests/CoreTests.swift +++ b/Tests/HummingbirdCoreTests/CoreTests.swift @@ -311,20 +311,21 @@ class HummingBirdCoreTests: XCTestCase { func testChildChannelGracefulShutdown() async throws { let handlerPromise = Promise() - let childChannel = try HTTPChannelBuilder.http1().build { request, _ in - await handlerPromise.complete(()) - try await Task.sleep(for: .milliseconds(500)) - return Response(status: .ok, body: .init(asyncSequence: request.body.delayed())) - } - await withThrowingTaskGroup(of: Void.self) { group in + try await withThrowingTaskGroup(of: Void.self) { group in let portPromise = Promise() let logger = Logger(label: "Hummingbird") - let server = childChannel.server( + let server = try HTTPServerBuilder.http1().buildServer( configuration: .init(address: .hostname(port: 0)), - onServerRunning: { await portPromise.complete($0.localAddress!.port!) }, eventLoopGroup: Self.eventLoopGroup, logger: logger - ) + ) { request, _ in + await handlerPromise.complete(()) + try await Task.sleep(for: .milliseconds(500)) + return Response(status: .ok, body: .init(asyncSequence: request.body.delayed())) + } onServerRunning: { + await portPromise.complete($0.localAddress!.port!) + } + let serviceGroup = ServiceGroup( configuration: .init( services: [server], diff --git a/Tests/HummingbirdCoreTests/TestUtils.swift b/Tests/HummingbirdCoreTests/TestUtils.swift index 02986efb0..35df31903 100644 --- a/Tests/HummingbirdCoreTests/TestUtils.swift +++ b/Tests/HummingbirdCoreTests/TestUtils.swift @@ -33,7 +33,7 @@ public enum TestErrors: Error { /// Helper function for testing a server public func testServer( responder: @escaping HTTPChannelHandler.Responder, - httpChannelSetup: HTTPChannelBuilder, + httpChannelSetup: HTTPServerBuilder, configuration: ServerConfiguration, eventLoopGroup: EventLoopGroup, logger: Logger, @@ -41,11 +41,12 @@ public func testServer( ) async throws -> Value { try await withThrowingTaskGroup(of: Void.self) { group in let promise = Promise() - let server = try httpChannelSetup.build(responder).server( + let server = try httpChannelSetup.buildServer( configuration: configuration, - onServerRunning: { await promise.complete($0.localAddress!.port!) }, eventLoopGroup: eventLoopGroup, - logger: logger + logger: logger, + responder: responder, + onServerRunning: { await promise.complete($0.localAddress!.port!) } ) let serviceGroup = ServiceGroup( configuration: .init( @@ -70,7 +71,7 @@ public func testServer( /// shutdown correctly public func testServer( responder: @escaping HTTPChannelHandler.Responder, - httpChannelSetup: HTTPChannelBuilder = .http1(), + httpChannelSetup: HTTPServerBuilder = .http1(), configuration: ServerConfiguration, eventLoopGroup: EventLoopGroup, logger: Logger,