Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Cleanup: HTTPChannelBuilder should build server not channel #452

Merged
merged 3 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
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
8 changes: 4 additions & 4 deletions Sources/Hummingbird/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public struct ApplicationConfiguration: Sendable {
// MARK: Member variables

/// Bind address for server
public var address: Address
public var address: BindAddress
/// Server name to return in "server" header
public var serverName: String?
/// Defines the maximum length for the queue of pending connections
Expand All @@ -49,7 +49,7 @@ public struct ApplicationConfiguration: Sendable {
/// the client may receive an error with an indication of ECONNREFUSE
/// - reuseAddress: Allows socket to be bound to an address that is already in use.
public init(
address: Address = .hostname(),
address: BindAddress = .hostname(),
serverName: String? = nil,
backlog: Int = 256,
reuseAddress: Bool = true
Expand All @@ -72,7 +72,7 @@ public struct ApplicationConfiguration: Sendable {
/// - reuseAddress: Allows socket to be bound to an address that is already in use.
/// - tlsOptions: TLS options for when you are using NIOTransportServices
public init(
address: Address = .hostname(),
address: BindAddress = .hostname(),
serverName: String? = nil,
reuseAddress: Bool = true,
tlsOptions: TSTLSOptions
Expand All @@ -88,7 +88,7 @@ public struct ApplicationConfiguration: Sendable {

/// Create new configuration struct with updated values
public func with(
address: Address? = nil,
address: BindAddress? = nil,
serverName: String? = nil,
backlog: Int? = nil,
reuseAddress: Bool? = nil
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hummingbird/Exports.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//

@_exported import struct HummingbirdCore.Address
@_exported import struct HummingbirdCore.BindAddress
@_exported import struct HummingbirdCore.HTTPError
@_exported import protocol HummingbirdCore.HTTPResponseError
@_exported import struct HummingbirdCore.Request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//

/// Address to bind server to
public struct Address: Sendable, Equatable {
public struct BindAddress: Sendable, Equatable {
enum _Internal: Equatable {
case hostname(_ host: String = "127.0.0.1", port: Int = 8080)
case unixDomainSocket(path: String)
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 }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use an empty closure as a default instead of nil for a reason?

) 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
6 changes: 3 additions & 3 deletions Sources/HummingbirdCore/Server/ServerConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import NIOCore
/// HTTP server configuration
public struct ServerConfiguration: Sendable {
/// Bind address for server
public let address: Address
public let address: BindAddress
/// Server name to return in "server" header
public let serverName: String?
/// Defines the maximum length for the queue of pending connections
Expand All @@ -37,7 +37,7 @@ public struct ServerConfiguration: Sendable {
/// the client may receive an error with an indication of ECONNREFUSE
/// - reuseAddress: Allows socket to be bound to an address that is already in use.
public init(
address: Address = .hostname(),
address: BindAddress = .hostname(),
serverName: String? = nil,
backlog: Int = 256,
reuseAddress: Bool = true
Expand All @@ -61,7 +61,7 @@ public struct ServerConfiguration: Sendable {
/// - tlsOptions: TLS options for when you are using NIOTransportServices
#if canImport(Network)
public init(
address: Address = .hostname(),
address: BindAddress = .hostname(),
serverName: String? = nil,
backlog: Int = 256,
reuseAddress: Bool = true,
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
Loading