Skip to content

Commit

Permalink
Add a custom bind address (#63)
Browse files Browse the repository at this point in the history
* Add a custom bind address

* Update `import NIO` in BindAddress to `import NIOCore`

Co-authored-by: Adam Fowler <adamfowler71@gmail.com>

* Add comments to the methods in the HTTPServerBootstrap protocol

* Add a unit test for the HBBindAddress.custom case

* Add an availability annotation allowing the tests to compiling on iOS

* Fix an issue causing the HBBindAddress.custom test to fail on Linux

* Update the formatting using SwiftFormat

---------

Co-authored-by: Adam Fowler <adamfowler71@gmail.com>
  • Loading branch information
AgentFeeble and adam-fowler committed Oct 4, 2023
1 parent 5fd8895 commit ca84a9b
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 3 deletions.
4 changes: 4 additions & 0 deletions Sources/HummingbirdCore/Server/BindAddress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
//
//===----------------------------------------------------------------------===//

import NIOCore

/// Address to bind server to
public enum HBBindAddress {
/// bind address define by host and port
case hostname(_ host: String = "127.0.0.1", port: Int = 8080)
/// bind address defined by unxi domain socket
case unixDomainSocket(path: String)
/// a custom function used to perform the HTTPServerBootstrap binding
case custom((HTTPServerBootstrap) -> EventLoopFuture<Channel>)

/// if address is hostname and port return port
public var port: Int? {
Expand Down
34 changes: 33 additions & 1 deletion Sources/HummingbirdCore/Server/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ public final class HBHTTPServer {
self.channel = channel
responder.logger.info("Server started and listening on socket path \(path)")
}
case .custom(let bindFunction):
bindFuture = bindFunction(bootstrap)
.map { channel in
self.channel = channel
responder.logger.info("Server started and listening using custom binding function")
}
}

return bindFuture
Expand Down Expand Up @@ -229,9 +235,35 @@ public final class HBHTTPServer {
}

/// Protocol for bootstrap.
protocol HTTPServerBootstrap {
public protocol HTTPServerBootstrap {
/// Bind the server channel to `host` and `port`.
///
/// - parameters:
/// - host: The host to bind on.
/// - port: The port to bind on.
func bind(host: String, port: Int) -> EventLoopFuture<Channel>

/// Bind the server channel to a UNIX Domain Socket.
///
/// - parameters:
/// - unixDomainSocketPath: The _Unix domain socket_ path to bind to. `unixDomainSocketPath` must not exist, it will be created by the system.
func bind(unixDomainSocketPath: String) -> EventLoopFuture<Channel>

/// Specifies a `ChannelOption` to be applied to the server channel.
///
/// - note: To specify options for the accepted child channels, look at `HTTPServerBootstrap.childChannelOption`.
///
/// - parameters:
/// - option: The option to be applied.
/// - value: The value for the option.
func serverChannelOption<Option: ChannelOption>(_ option: Option, value: Option.Value) -> Self

/// Specifies a `ChannelOption` to be applied to the accepted child channels.
///
/// - parameters:
/// - option: The option to be applied.
/// - value: The value for the option.
func childChannelOption<Option: ChannelOption>(_ option: Option, value: Option.Value) -> Self
}

// Extend both `ServerBootstrap` and `NIOTSListenerBootstrap` to conform to `HTTPServerBootstrap`
Expand Down
4 changes: 2 additions & 2 deletions Sources/HummingbirdHTTP2/ChannelInitializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ struct HTTP2UpgradeChannelInitializer: HBChannelInitializer {
func initialize(channel: Channel, childHandlers: [RemovableChannelHandler], configuration: HBHTTPServer.Configuration) -> EventLoopFuture<Void> {
channel.configureHTTP2SecureUpgrade(
h2ChannelConfigurator: { channel in
http2.initialize(channel: channel, childHandlers: childHandlers, configuration: configuration)
self.http2.initialize(channel: channel, childHandlers: childHandlers, configuration: configuration)
},
http1ChannelConfigurator: { channel in
http1.initialize(channel: channel, childHandlers: childHandlers, configuration: configuration)
self.http1.initialize(channel: channel, childHandlers: childHandlers, configuration: configuration)
}
)
}
Expand Down
33 changes: 33 additions & 0 deletions Tests/HummingbirdCoreTests/CoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ class HummingBirdCoreTests: XCTestCase {
XCTAssertNoThrow(try future.wait())
}

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
func testConsumeBodyInTask() {
struct Responder: HBHTTPResponder {
func respond(to request: HBHTTPRequest, context: ChannelHandlerContext, onComplete: @escaping (Result<HBHTTPResponse, Error>) -> Void) {
Expand Down Expand Up @@ -718,6 +719,38 @@ class HummingBirdCoreTests: XCTestCase {
}
XCTAssertNoThrow(try timeoutPromise.futureResult.wait())
}

func testCustomBindAddress() throws {
struct Responder: HBHTTPResponder {
func respond(to request: HBHTTPRequest, context: ChannelHandlerContext, onComplete: @escaping (Result<HBHTTPResponse, Error>) -> Void) {
onComplete(.failure(HBHTTPError(.unauthorized)))
}
}

// Set the channel option to a non-standard value in the custom bind address function, and then verify that the
// value has been set after starting the server.
// We're testing `SocketOption` option specifically because it is supported by both the `ServerBootstrap` and
// `NIOTSListenerBootstrap`, allowing this test to be run on all platforms.
let channelValue: SocketOptionValue = 123
let channelOption = ChannelOptions.Types.SocketOption(level: IPPROTO_TCP, name: TCP_KEEPCNT)

var didCallCustomBind = false
let eventLoopGroup = Self.eventLoopGroup!
let address = HBBindAddress.custom { serverBootstrap in
didCallCustomBind = true
return serverBootstrap.serverChannelOption(channelOption, value: channelValue)
.bind(host: "127.0.0.1", port: 0)
}
let server = HBHTTPServer(group: eventLoopGroup, configuration: .init(address: address))
XCTAssertNoThrow(try server.start(responder: Responder()).wait())
defer { XCTAssertNoThrow(try server.stop().wait()) }

XCTAssertTrue(didCallCustomBind)
guard let channel = server.channel else {
throw HBHTTPServer.Error.serverNotRunning
}
XCTAssertEqual(channelValue, try channel.getOption(channelOption).wait())
}
}

/// Channel Handler for serializing request header and data
Expand Down

0 comments on commit ca84a9b

Please sign in to comment.