Skip to content

Commit

Permalink
HTTPChannelBuilder should build server not channel
Browse files Browse the repository at this point in the history
Also renamed it to HTTPServerBuilder
  • Loading branch information
adam-fowler committed May 16, 2024
1 parent 3f9d6b3 commit ded1991
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 48 deletions.
29 changes: 13 additions & 16 deletions Sources/Hummingbird/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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 {
Expand All @@ -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()))
Expand All @@ -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()
Expand Down Expand Up @@ -173,7 +170,7 @@ public struct Application<Responder: HTTPResponder>: 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
Expand All @@ -193,7 +190,7 @@ public struct Application<Responder: HTTPResponder>: 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 },
Expand Down Expand Up @@ -229,7 +226,7 @@ public struct Application<Responder: HTTPResponder>: ApplicationProtocol where R
/// - logger: Logger application uses
public init<ResponderBuilder: HTTPResponderBuilder>(
router: ResponderBuilder,
server: HTTPChannelBuilder = .http1(),
server: HTTPServerBuilder = .http1(),
configuration: ApplicationConfiguration = ApplicationConfiguration(),
services: [Service] = [],
onServerRunning: @escaping @Sendable (Channel) async -> Void = { _ in },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// ```
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// ```
Expand All @@ -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)
}
}
}
2 changes: 1 addition & 1 deletion Sources/HummingbirdTesting/TestApplication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal struct TestApplication<BaseApp: ApplicationProtocol>: ApplicationProtoc
get async throws { try await self.base.responder }
}

var server: HTTPChannelBuilder {
var server: HTTPServerBuilder {
self.base.server
}

Expand Down
19 changes: 10 additions & 9 deletions Tests/HummingbirdCoreTests/CoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -311,20 +311,21 @@ class HummingBirdCoreTests: XCTestCase {
func testChildChannelGracefulShutdown() async throws {
let handlerPromise = Promise<Void>()

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<Int>()
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],
Expand Down
11 changes: 6 additions & 5 deletions Tests/HummingbirdCoreTests/TestUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,20 @@ public enum TestErrors: Error {
/// Helper function for testing a server
public func testServer<Value: Sendable>(
responder: @escaping HTTPChannelHandler.Responder,
httpChannelSetup: HTTPChannelBuilder,
httpChannelSetup: HTTPServerBuilder,
configuration: ServerConfiguration,
eventLoopGroup: EventLoopGroup,
logger: Logger,
_ test: @escaping @Sendable (Int) async throws -> Value
) async throws -> Value {
try await withThrowingTaskGroup(of: Void.self) { group in
let promise = Promise<Int>()
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(
Expand All @@ -70,7 +71,7 @@ public func testServer<Value: Sendable>(
/// shutdown correctly
public func testServer<Value: Sendable>(
responder: @escaping HTTPChannelHandler.Responder,
httpChannelSetup: HTTPChannelBuilder = .http1(),
httpChannelSetup: HTTPServerBuilder = .http1(),
configuration: ServerConfiguration,
eventLoopGroup: EventLoopGroup,
logger: Logger,
Expand Down

0 comments on commit ded1991

Please sign in to comment.