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

2.x.x - Generalized Client #364

Merged
merged 9 commits into from
Jan 29, 2024
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
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ let package = Package(
// test targets
.testTarget(name: "HummingbirdTests", dependencies: [
.byName(name: "Hummingbird"),
.byName(name: "HummingbirdTLS"),
.byName(name: "HummingbirdHTTP2"),
.byName(name: "HummingbirdXCT"),
]),
.testTarget(name: "HummingbirdJobsTests", dependencies: [
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 HBApplicationConfiguration: Sendable {
// MARK: Member variables

/// Bind address for server
public var address: HBBindAddress
public var address: HBAddress
/// 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 HBApplicationConfiguration: 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: HBBindAddress = .hostname(),
address: HBAddress = .hostname(),
serverName: String? = nil,
backlog: Int = 256,
reuseAddress: Bool = true
Expand All @@ -72,7 +72,7 @@ public struct HBApplicationConfiguration: 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: HBBindAddress = .hostname(),
address: HBAddress = .hostname(),
serverName: String? = nil,
reuseAddress: Bool = true,
tlsOptions: TSTLSOptions
Expand All @@ -88,7 +88,7 @@ public struct HBApplicationConfiguration: Sendable {

/// Create new configuration struct with updated values
public func with(
address: HBBindAddress? = nil,
address: HBAddress? = 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.HBBindAddress
@_exported import struct HummingbirdCore.HBAddress
@_exported import struct HummingbirdCore.HBHTTPError
@_exported import protocol HummingbirdCore.HBHTTPResponseError
@_exported import struct HummingbirdCore.HBRequest
Expand Down
151 changes: 151 additions & 0 deletions Sources/HummingbirdCore/Client/Client.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//===----------------------------------------------------------------------===//
//
// 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 Logging
import NIOCore
import NIOPosix
import ServiceLifecycle
#if canImport(Network)
import Network
import NIOTransportServices
#endif

public struct HBClient<ClientChannel: HBClientChannel> {
typealias ChannelResult = ClientChannel.Value
/// Logger used by Server
let logger: Logger
let eventLoopGroup: EventLoopGroup
let clientChannel: ClientChannel
let address: HBAddress
#if canImport(Network)
let tlsOptions: NWProtocolTLS.Options?
#endif

/// Initialize Client
public init(
_ clientChannel: ClientChannel,
address: HBAddress,
eventLoopGroup: EventLoopGroup = MultiThreadedEventLoopGroup.singleton,
logger: Logger
) {
self.clientChannel = clientChannel
self.address = address
self.eventLoopGroup = eventLoopGroup
self.logger = logger
#if canImport(Network)
self.tlsOptions = nil
#endif
}

#if canImport(Network)
/// Initialize Client with TLS options
public init(
_ clientChannel: ClientChannel,
address: HBAddress,
transportServicesTLSOptions: TSTLSOptions,
eventLoopGroup: EventLoopGroup = MultiThreadedEventLoopGroup.singleton,
logger: Logger
) throws {
self.clientChannel = clientChannel
self.address = address
self.eventLoopGroup = eventLoopGroup
self.logger = logger
self.tlsOptions = transportServicesTLSOptions.options
}
#endif

public func run() async throws {
let channelResult = try await self.makeClient(
clientChannel: self.clientChannel,
address: self.address
)
try await self.clientChannel.handle(value: channelResult, logger: self.logger)
}

/// Connect to server
func makeClient(clientChannel: ClientChannel, address: HBAddress) async throws -> ChannelResult {
// get bootstrap
let bootstrap: ClientBootstrapProtocol
#if canImport(Network)
if let tsBootstrap = self.createTSBootstrap() {
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
bootstrap = self.createSocketsBootstrap()
}
#else
bootstrap = self.createSocketsBootstrap()
#endif

// connect
let result: ChannelResult
do {
switch address.value {
case .hostname(let host, let port):
result = try await bootstrap
.connect(host: host, port: port) { channel in
clientChannel.setup(channel: channel, logger: self.logger)
}
self.logger.debug("Client connnected to \(host):\(port)")
case .unixDomainSocket(let path):
result = try await bootstrap
.connect(unixDomainSocketPath: path) { channel in
clientChannel.setup(channel: channel, logger: self.logger)
}
self.logger.debug("Client connnected to socket path \(path)")

Check warning on line 108 in Sources/HummingbirdCore/Client/Client.swift

View check run for this annotation

Codecov / codecov/patch

Sources/HummingbirdCore/Client/Client.swift#L104-L108

Added lines #L104 - L108 were not covered by tests
}
return result
} catch {
throw error
}
}

Check warning on line 114 in Sources/HummingbirdCore/Client/Client.swift

View check run for this annotation

Codecov / codecov/patch

Sources/HummingbirdCore/Client/Client.swift#L112-L114

Added lines #L112 - L114 were not covered by tests

/// create a BSD sockets based bootstrap
private func createSocketsBootstrap() -> ClientBootstrap {
return ClientBootstrap(group: self.eventLoopGroup)
}

#if canImport(Network)
/// create a NIOTransportServices bootstrap using Network.framework
private func createTSBootstrap() -> NIOTSConnectionBootstrap? {
guard let bootstrap = NIOTSConnectionBootstrap(validatingGroup: self.eventLoopGroup) else {
return nil
}
if let tlsOptions {
return bootstrap.tlsOptions(tlsOptions)
}
return bootstrap
}
#endif
}

protocol ClientBootstrapProtocol {
func connect<Output: Sendable>(
host: String,
port: Int,
channelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture<Output>
) async throws -> Output

func connect<Output: Sendable>(
unixDomainSocketPath: String,
channelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture<Output>
) async throws -> Output
}

extension ClientBootstrap: ClientBootstrapProtocol {}
#if canImport(Network)
extension NIOTSConnectionBootstrap: ClientBootstrapProtocol {}
#endif
34 changes: 34 additions & 0 deletions Sources/HummingbirdCore/Client/ClientChannel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2023 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 Logging
import NIOCore

/// HBClient child channel setup protocol
public protocol HBClientChannel: Sendable {
associatedtype Value: Sendable

/// Setup child channel
/// - Parameters:
/// - channel: Child channel
/// - logger: Logger used during setup
/// - Returns: Object to process input/output on child channel
func setup(channel: Channel, logger: Logger) -> EventLoopFuture<Value>

/// handle messages being passed down the channel pipeline
/// - Parameters:
/// - value: Object to process input/output on child channel
/// - logger: Logger to use while processing messages
func handle(value: Value, logger: Logger) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//

/// Address to bind server to
public struct HBBindAddress: Sendable, Equatable {
public struct HBAddress: Sendable, Equatable {
enum _Internal: Equatable {
case hostname(_ host: String = "127.0.0.1", port: Int = 8080)
case unixDomainSocket(path: String)
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 HBServerConfiguration: Sendable {
/// Bind address for server
public let address: HBBindAddress
public let address: HBAddress
/// 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 HBServerConfiguration: 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: HBBindAddress = .hostname(),
address: HBAddress = .hostname(),
serverName: String? = nil,
backlog: Int = 256,
reuseAddress: Bool = true
Expand All @@ -61,7 +61,7 @@ public struct HBServerConfiguration: Sendable {
/// - tlsOptions: TLS options for when you are using NIOTransportServices
#if canImport(Network)
public init(
address: HBBindAddress = .hostname(),
address: HBAddress = .hostname(),
serverName: String? = nil,
backlog: Int = 256,
reuseAddress: Bool = true,
Expand Down
Loading
Loading