swift-nio/Sources/NIO/Bootstrap.swift

739 lines
32 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// A `ServerBootstrap` is an easy way to bootstrap a `ServerSocketChannel` when creating network servers.
///
/// Example:
///
/// ```swift
/// let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
/// defer {
/// try! group.syncShutdownGracefully()
/// }
/// let bootstrap = ServerBootstrap(group: group)
/// // Specify backlog and enable SO_REUSEADDR for the server itself
/// .serverChannelOption(ChannelOptions.backlog, value: 256)
/// .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
///
/// // Set the handlers that are applied to the accepted child `Channel`s.
/// .childChannelInitializer { channel in
/// // Ensure we don't read faster then we can write by adding the BackPressureHandler into the pipeline.
/// channel.pipeline.add(handler: BackPressureHandler()).flatMap { () in
/// // make sure to instantiate your `ChannelHandlers` inside of
/// // the closure as it will be invoked once per connection.
/// channel.pipeline.add(handler: MyChannelHandler())
/// }
/// }
///
/// // Enable TCP_NODELAY and SO_REUSEADDR for the accepted Channels
/// .childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
/// .childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
/// .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 16)
/// .childChannelOption(ChannelOptions.recvAllocator, value: AdaptiveRecvByteBufferAllocator())
/// let channel = try! bootstrap.bind(host: host, port: port).wait()
/// /* the server will now be accepting connections */
///
/// try! channel.closeFuture.wait() // wait forever as we never close the Channel
/// ```
///
/// The `EventLoopFuture` returned by `bind` will fire with a `ServerSocketChannel`. This is the channel that owns the listening socket.
/// Each time it accepts a new connection it will fire a `SocketChannel` through the `ChannelPipeline` via `fireChannelRead`: as a result,
/// the `ServerSocketChannel` operates on `Channel`s as inbound messages. Outbound messages are not supported on a `ServerSocketChannel`
/// which means that each write attempt will fail.
///
/// Accepted `SocketChannel`s operate on `ByteBuffer` as inbound data, and `IOData` as outbound data.
public final class ServerBootstrap {
private let group: EventLoopGroup
private let childGroup: EventLoopGroup
private var serverChannelInit: ((Channel) -> EventLoopFuture<Void>)?
private var childChannelInit: ((Channel) -> EventLoopFuture<Void>)?
private var serverChannelOptions = ChannelOptionStorage()
private var childChannelOptions = ChannelOptionStorage()
/// Create a `ServerBootstrap` for the `EventLoopGroup` `group`.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use for the `ServerSocketChannel`.
public convenience init(group: EventLoopGroup) {
self.init(group: group, childGroup: group)
}
/// Create a `ServerBootstrap`.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use for the `bind` of the `ServerSocketChannel` and to accept new `SocketChannel`s with.
/// - childGroup: The `EventLoopGroup` to run the accepted `SocketChannel`s on.
public init(group: EventLoopGroup, childGroup: EventLoopGroup) {
self.group = group
self.childGroup = childGroup
}
/// Initialize the `ServerSocketChannel` with `initializer`. The most common task in initializer is to add
/// `ChannelHandler`s to the `ChannelPipeline`.
///
/// The `ServerSocketChannel` uses the accepted `Channel`s as inbound messages.
///
/// - note: To set the initializer for the accepted `SocketChannel`s, look at `ServerBootstrap.childChannelInitializer`.
///
/// - parameters:
/// - initializer: A closure that initializes the provided `Channel`.
public func serverChannelInitializer(_ initializer: @escaping (Channel) -> EventLoopFuture<Void>) -> Self {
self.serverChannelInit = initializer
return self
}
/// Initialize the accepted `SocketChannel`s with `initializer`. The most common task in initializer is to add
/// `ChannelHandler`s to the `ChannelPipeline`.
///
/// - warning: The `initializer` will be invoked once for every accepted connection. Therefore it's usually the
/// right choice to instantiate stateful `ChannelHandler`s within the closure to make sure they are not
/// accidentally shared across `Channel`s. There are expert use-cases where stateful handler need to be
/// shared across `Channel`s in which case the user is responsible to synchronise the state access
/// appropriately.
///
/// The accepted `Channel` will operate on `ByteBuffer` as inbound and `IOData` as outbound messages.
///
/// - parameters:
/// - initializer: A closure that initializes the provided `Channel`.
public func childChannelInitializer(_ initializer: @escaping (Channel) -> EventLoopFuture<Void>) -> Self {
self.childChannelInit = initializer
return self
}
/// Specifies a `ChannelOption` to be applied to the `ServerSocketChannel`.
///
/// - note: To specify options for the accepted `SocketChannel`s, look at `ServerBootstrap.childChannelOption`.
///
/// - parameters:
/// - option: The option to be applied.
/// - value: The value for the option.
public func serverChannelOption<T: ChannelOption>(_ option: T, value: T.OptionType) -> Self {
serverChannelOptions.put(key: option, value: value)
return self
}
/// Specifies a `ChannelOption` to be applied to the accepted `SocketChannel`s.
///
/// - parameters:
/// - option: The option to be applied.
/// - value: The value for the option.
public func childChannelOption<T: ChannelOption>(_ option: T, value: T.OptionType) -> Self {
childChannelOptions.put(key: option, value: value)
return self
}
/// Bind the `ServerSocketChannel` to `host` and `port`.
///
/// - parameters:
/// - host: The host to bind on.
/// - port: The port to bind on.
public func bind(host: String, port: Int) -> EventLoopFuture<Channel> {
return bind0 {
return try SocketAddress.makeAddressResolvingHost(host, port: port)
}
}
/// Bind the `ServerSocketChannel` to `address`.
///
/// - parameters:
/// - address: The `SocketAddress` to bind on.
public func bind(to address: SocketAddress) -> EventLoopFuture<Channel> {
return bind0 { address }
}
/// Bind the `ServerSocketChannel` 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.
public func bind(unixDomainSocketPath: String) -> EventLoopFuture<Channel> {
return bind0 {
try SocketAddress(unixDomainSocketPath: unixDomainSocketPath)
}
}
/// Use the existing bound socket file descriptor.
///
/// - parameters:
/// - descriptor: The _Unix file descriptor_ representing the bound stream socket.
public func withBoundSocket(descriptor: CInt) -> EventLoopFuture<Channel> {
func makeChannel(_ eventLoop: SelectableEventLoop, _ childEventLoopGroup: EventLoopGroup) throws -> ServerSocketChannel {
return try ServerSocketChannel(descriptor: descriptor, eventLoop: eventLoop, group: childEventLoopGroup)
}
return bind0(makeServerChannel: makeChannel) { (eventLoop, serverChannel) in
let promise = eventLoop.makePromise(of: Void.self)
serverChannel.registerAlreadyConfigured0(promise: promise)
return promise.futureResult
}
}
private func bind0(_ makeSocketAddress: () throws -> SocketAddress) -> EventLoopFuture<Channel> {
let address: SocketAddress
do {
address = try makeSocketAddress()
} catch {
return group.next().makeFailedFuture(error)
}
func makeChannel(_ eventLoop: SelectableEventLoop, _ childEventLoopGroup: EventLoopGroup) throws -> ServerSocketChannel {
return try ServerSocketChannel(eventLoop: eventLoop,
group: childEventLoopGroup,
protocolFamily: address.protocolFamily)
}
return bind0(makeServerChannel: makeChannel) { (eventGroup, serverChannel) in
serverChannel.registerAndDoSynchronously { serverChannel in
serverChannel.bind(to: address)
}
}
}
private func bind0(makeServerChannel: (_ eventLoop: SelectableEventLoop, _ childGroup: EventLoopGroup) throws -> ServerSocketChannel, _ register: @escaping (EventLoop, ServerSocketChannel) -> EventLoopFuture<Void>) -> EventLoopFuture<Channel> {
let eventLoop = self.group.next()
let childEventLoopGroup = self.childGroup
let serverChannelOptions = self.serverChannelOptions
let serverChannelInit = self.serverChannelInit ?? { _ in eventLoop.makeSucceededFuture(()) }
let childChannelInit = self.childChannelInit
let childChannelOptions = self.childChannelOptions
let serverChannel: ServerSocketChannel
do {
serverChannel = try makeServerChannel(eventLoop as! SelectableEventLoop, childEventLoopGroup)
} catch {
return eventLoop.makeFailedFuture(error)
}
return eventLoop.submit {
return serverChannelInit(serverChannel).flatMap {
serverChannel.pipeline.add(handler: AcceptHandler(childChannelInitializer: childChannelInit,
childChannelOptions: childChannelOptions))
}.flatMap {
serverChannelOptions.applyAll(channel: serverChannel)
}.flatMap {
register(eventLoop, serverChannel)
}.map {
serverChannel as Channel
}.flatMapError { error in
serverChannel.close0(error: error, mode: .all, promise: nil)
return eventLoop.makeFailedFuture(error)
}
}.flatMap {
$0
}
}
private class AcceptHandler: ChannelInboundHandler {
public typealias InboundIn = SocketChannel
private let childChannelInit: ((Channel) -> EventLoopFuture<Void>)?
private let childChannelOptions: ChannelOptionStorage
init(childChannelInitializer: ((Channel) -> EventLoopFuture<Void>)?, childChannelOptions: ChannelOptionStorage) {
self.childChannelInit = childChannelInitializer
self.childChannelOptions = childChannelOptions
}
func userInboundEventTriggered(ctx: ChannelHandlerContext, event: Any) {
if event is ChannelShouldQuiesceEvent {
ctx.channel.close(promise: nil)
}
ctx.fireUserInboundEventTriggered(event)
}
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
let accepted = self.unwrapInboundIn(data)
let ctxEventLoop = ctx.eventLoop
let childEventLoop = accepted.eventLoop
let childChannelInit = self.childChannelInit ?? { (_: Channel) in childEventLoop.makeSucceededFuture(()) }
@inline(__always)
func setupChildChannel() -> EventLoopFuture<Void> {
return self.childChannelOptions.applyAll(channel: accepted).flatMap { () -> EventLoopFuture<Void> in
childEventLoop.assertInEventLoop()
return childChannelInit(accepted)
}
}
@inline(__always)
func fireThroughPipeline(_ future: EventLoopFuture<Void>) {
ctxEventLoop.assertInEventLoop()
future.flatMap { (_) -> EventLoopFuture<Void> in
ctxEventLoop.assertInEventLoop()
guard !ctx.pipeline.destroyed else {
return ctx.eventLoop.makeFailedFuture(ChannelError.ioOnClosedChannel)
}
ctx.fireChannelRead(data)
return ctx.eventLoop.makeSucceededFuture(())
}.whenFailure { error in
ctxEventLoop.assertInEventLoop()
self.closeAndFire(ctx: ctx, accepted: accepted, err: error)
}
}
if childEventLoop === ctxEventLoop {
fireThroughPipeline(setupChildChannel())
} else {
fireThroughPipeline(childEventLoop.submit {
return setupChildChannel()
}.flatMap { $0 }.hopTo(eventLoop: ctxEventLoop))
}
}
private func closeAndFire(ctx: ChannelHandlerContext, accepted: SocketChannel, err: Error) {
accepted.close(promise: nil)
if ctx.eventLoop.inEventLoop {
ctx.fireErrorCaught(err)
} else {
ctx.eventLoop.execute {
ctx.fireErrorCaught(err)
}
}
}
}
}
private extension Channel {
func registerAndDoSynchronously(_ body: @escaping (Channel) -> EventLoopFuture<Void>) -> EventLoopFuture<Void> {
// this is pretty delicate at the moment:
// In many cases `body` must be _synchronously_ follow `register`, otherwise in our current
// implementation, `epoll` will send us `EPOLLHUP`. To have it run synchronously, we need to invoke the
// `flatMap` on the eventloop that the `register` will succeed on.
self.eventLoop.assertInEventLoop()
return self.register().flatMap {
self.eventLoop.assertInEventLoop()
return body(self)
}
}
}
/// A `ClientBootstrap` is an easy way to bootstrap a `SocketChannel` when creating network clients.
///
/// Usually you re-use a `ClientBootstrap` once you set it up and called `connect` multiple times on it.
/// This way you ensure that the same `EventLoop`s will be shared across all your connections.
///
/// Example:
///
/// ```swift
/// let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
/// defer {
/// try! group.syncShutdownGracefully()
/// }
/// let bootstrap = ClientBootstrap(group: group)
/// // Enable SO_REUSEADDR.
/// .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
/// .channelInitializer { channel in
/// // always instantiate the handler _within_ the closure as
/// // it may be called multiple times (for example if the hostname
/// // resolves to both IPv4 and IPv6 addresses, cf. Happy Eyeballs).
/// channel.pipeline.add(handler: MyChannelHandler())
/// }
/// try! bootstrap.connect(host: "example.org", port: 12345).wait()
/// /* the Channel is now connected */
/// ```
///
/// The connected `SocketChannel` will operate on `ByteBuffer` as inbound and on `IOData` as outbound messages.
public final class ClientBootstrap {
private let group: EventLoopGroup
private var channelInitializer: ((Channel) -> EventLoopFuture<Void>)?
private var channelOptions = ChannelOptionStorage()
private var connectTimeout: TimeAmount = TimeAmount.seconds(10)
private var resolver: Resolver?
/// Create a `ClientBootstrap` on the `EventLoopGroup` `group`.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public init(group: EventLoopGroup) {
self.group = group
}
/// Initialize the connected `SocketChannel` with `initializer`. The most common task in initializer is to add
/// `ChannelHandler`s to the `ChannelPipeline`.
///
/// The connected `Channel` will operate on `ByteBuffer` as inbound and `IOData` as outbound messages.
///
/// - warning: The `handler` closure may be invoked _multiple times_ so it's usually the right choice to instantiate
/// `ChannelHandler`s within `handler`. The reason `handler` may be invoked multiple times is that to
/// successfully set up a connection multiple connections might be setup in the process. Assuming a
/// hostname that resolves to both IPv4 and IPv6 addresses, NIO will follow
/// [_Happy Eyeballs_](https://en.wikipedia.org/wiki/Happy_Eyeballs) and race both an IPv4 and an IPv6
/// connection. It is possible that both connections get fully established before the IPv4 connection
/// will be closed again because the IPv6 connection 'won the race'. Therefore the `channelInitializer`
/// might be called multiple times and it's important not to share stateful `ChannelHandler`s in more
/// than one `Channel`.
///
/// - parameters:
/// - handler: A closure that initializes the provided `Channel`.
public func channelInitializer(_ handler: @escaping (Channel) -> EventLoopFuture<Void>) -> Self {
self.channelInitializer = handler
return self
}
/// Specifies a `ChannelOption` to be applied to the `SocketChannel`.
///
/// - parameters:
/// - option: The option to be applied.
/// - value: The value for the option.
public func channelOption<T: ChannelOption>(_ option: T, value: T.OptionType) -> Self {
channelOptions.put(key: option, value: value)
return self
}
/// Specifies a timeout to apply to a connection attempt.
//
/// - parameters:
/// - timeout: The timeout that will apply to the connection attempt.
public func connectTimeout(_ timeout: TimeAmount) -> Self {
self.connectTimeout = timeout
return self
}
/// Specifies the `Resolver` to use or `nil` if the default should be used.
///
/// - parameters:
/// - resolver: The resolver that will be used during the connection attempt.
public func resolver(_ resolver: Resolver?) -> Self {
self.resolver = resolver
return self
}
/// Specify the `host` and `port` to connect to for the TCP `Channel` that will be established.
///
/// - parameters:
/// - host: The host to connect to.
/// - port: The port to connect to.
/// - returns: An `EventLoopFuture<Channel>` to deliver the `Channel` when connected.
public func connect(host: String, port: Int) -> EventLoopFuture<Channel> {
let loop = self.group.next()
let connector = HappyEyeballsConnector(resolver: resolver ?? GetaddrinfoResolver(loop: loop, aiSocktype: Posix.SOCK_STREAM, aiProtocol: Posix.IPPROTO_TCP),
loop: loop,
host: host,
port: port,
connectTimeout: self.connectTimeout) { eventLoop, protocolFamily in
return self.execute(eventLoop: eventLoop, protocolFamily: protocolFamily) { $0.eventLoop.makeSucceededFuture(()) }
}
return connector.resolveAndConnect()
}
/// Specify the `address` to connect to for the TCP `Channel` that will be established.
///
/// - parameters:
/// - address: The address to connect to.
/// - returns: An `EventLoopFuture<Channel>` to deliver the `Channel` when connected.
public func connect(to address: SocketAddress) -> EventLoopFuture<Channel> {
return execute(eventLoop: group.next(), protocolFamily: address.protocolFamily) { channel in
let connectPromise = channel.eventLoop.makePromise(of: Void.self)
channel.connect(to: address, promise: connectPromise)
let cancelTask = channel.eventLoop.scheduleTask(in: self.connectTimeout) {
connectPromise.fail(ChannelError.connectTimeout(self.connectTimeout))
channel.close(promise: nil)
}
connectPromise.futureResult.whenComplete { (_: Result<Void, Error>) in
cancelTask.cancel()
}
return connectPromise.futureResult
}
}
/// Specify the `unixDomainSocket` path to connect to for the UDS `Channel` that will be established.
///
/// - parameters:
/// - unixDomainSocketPath: The _Unix domain socket_ path to connect to.
/// - returns: An `EventLoopFuture<Channel>` to deliver the `Channel` when connected.
public func connect(unixDomainSocketPath: String) -> EventLoopFuture<Channel> {
do {
let address = try SocketAddress(unixDomainSocketPath: unixDomainSocketPath)
return connect(to: address)
} catch {
return group.next().makeFailedFuture(error)
}
}
/// Use the existing connected socket file descriptor.
///
/// - parameters:
/// - descriptor: The _Unix file descriptor_ representing the connected stream socket.
/// - returns: an `EventLoopFuture<Channel>` to deliver the `Channel` immediately.
public func withConnectedSocket(descriptor: CInt) -> EventLoopFuture<Channel> {
let eventLoop = group.next()
let channelInitializer = self.channelInitializer ?? { _ in eventLoop.makeSucceededFuture(()) }
let channel: SocketChannel
do {
channel = try SocketChannel(eventLoop: eventLoop as! SelectableEventLoop, descriptor: descriptor)
} catch {
return eventLoop.makeFailedFuture(error)
}
return channelInitializer(channel).flatMap {
self.channelOptions.applyAll(channel: channel)
}.flatMap {
let promise = eventLoop.makePromise(of: Void.self)
channel.registerAlreadyConfigured0(promise: promise)
return promise.futureResult
}.map {
channel
}.flatMapError { error in
channel.close0(error: error, mode: .all, promise: nil)
return channel.eventLoop.makeFailedFuture(error)
}
}
private func execute(eventLoop: EventLoop,
protocolFamily: Int32,
_ body: @escaping (Channel) -> EventLoopFuture<Void>) -> EventLoopFuture<Channel> {
let channelInitializer = self.channelInitializer ?? { _ in eventLoop.makeSucceededFuture(()) }
let channelOptions = self.channelOptions
let promise = eventLoop.makePromise(of: Channel.self)
let channel: SocketChannel
do {
channel = try SocketChannel(eventLoop: eventLoop as! SelectableEventLoop, protocolFamily: protocolFamily)
} catch let err {
promise.fail(err)
return promise.futureResult
}
@inline(__always)
func setupChannel() -> EventLoopFuture<Channel> {
eventLoop.assertInEventLoop()
channelInitializer(channel).flatMap {
channelOptions.applyAll(channel: channel)
}.flatMap {
channel.registerAndDoSynchronously(body)
}.map {
channel
}.flatMapError { error in
channel.close0(error: error, mode: .all, promise: nil)
return channel.eventLoop.makeFailedFuture(error)
}.cascade(promise: promise)
return promise.futureResult
}
if eventLoop.inEventLoop {
return setupChannel()
} else {
return eventLoop.submit(setupChannel).flatMap { $0 }
}
}
}
/// A `DatagramBootstrap` is an easy way to bootstrap a `DatagramChannel` when creating datagram clients
/// and servers.
///
/// Example:
///
/// ```swift
/// let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
/// defer {
/// try! group.syncShutdownGracefully()
/// }
/// let bootstrap = DatagramBootstrap(group: group)
/// // Enable SO_REUSEADDR.
/// .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
/// .channelInitializer { channel in
/// channel.pipeline.add(handler: MyChannelHandler())
/// }
/// let channel = try! bootstrap.bind(host: "127.0.0.1", port: 53).wait()
/// /* the Channel is now ready to send/receive datagrams */
///
/// try channel.closeFuture.wait() // Wait until the channel un-binds.
/// ```
///
/// The `DatagramChannel` will operate on `AddressedEnvelope<ByteBuffer>` as inbound and outbound messages.
public final class DatagramBootstrap {
private let group: EventLoopGroup
private var channelInitializer: ((Channel) -> EventLoopFuture<Void>)?
private var channelOptions = ChannelOptionStorage()
/// Create a `DatagramBootstrap` on the `EventLoopGroup` `group`.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public init(group: EventLoopGroup) {
self.group = group
}
/// Initialize the bound `DatagramChannel` with `initializer`. The most common task in initializer is to add
/// `ChannelHandler`s to the `ChannelPipeline`.
///
/// - parameters:
/// - handler: A closure that initializes the provided `Channel`.
public func channelInitializer(_ handler: @escaping (Channel) -> EventLoopFuture<Void>) -> Self {
self.channelInitializer = handler
return self
}
/// Specifies a `ChannelOption` to be applied to the `DatagramChannel`.
///
/// - parameters:
/// - option: The option to be applied.
/// - value: The value for the option.
public func channelOption<T: ChannelOption>(_ option: T, value: T.OptionType) -> Self {
channelOptions.put(key: option, value: value)
return self
}
/// Use the existing bound socket file descriptor.
///
/// - parameters:
/// - descriptor: The _Unix file descriptor_ representing the bound datagram socket.
public func withBoundSocket(descriptor: CInt) -> EventLoopFuture<Channel> {
func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel {
return try DatagramChannel(eventLoop: eventLoop, descriptor: descriptor)
}
return bind0(makeChannel: makeChannel) { (eventLoop, channel) in
let promise = eventLoop.makePromise(of: Void.self)
channel.registerAlreadyConfigured0(promise: promise)
return promise.futureResult
}
}
/// Bind the `DatagramChannel` to `host` and `port`.
///
/// - parameters:
/// - host: The host to bind on.
/// - port: The port to bind on.
public func bind(host: String, port: Int) -> EventLoopFuture<Channel> {
return bind0 {
return try SocketAddress.makeAddressResolvingHost(host, port: port)
}
}
/// Bind the `DatagramChannel` to `address`.
///
/// - parameters:
/// - address: The `SocketAddress` to bind on.
public func bind(to address: SocketAddress) -> EventLoopFuture<Channel> {
return bind0 { address }
}
/// Bind the `DatagramChannel` to a UNIX Domain Socket.
///
/// - parameters:
/// - unixDomainSocketPath: The path of the UNIX Domain Socket to bind on. `path` must not exist, it will be created by the system.
public func bind(unixDomainSocketPath: String) -> EventLoopFuture<Channel> {
return bind0 {
return try SocketAddress(unixDomainSocketPath: unixDomainSocketPath)
}
}
private func bind0(_ makeSocketAddress: () throws -> SocketAddress) -> EventLoopFuture<Channel> {
let address: SocketAddress
do {
address = try makeSocketAddress()
} catch {
return group.next().makeFailedFuture(error)
}
func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel {
return try DatagramChannel(eventLoop: eventLoop,
protocolFamily: address.protocolFamily)
}
return bind0(makeChannel: makeChannel) { (eventLoop, channel) in
channel.register().flatMap {
channel.bind(to: address)
}
}
}
private func bind0(makeChannel: (_ eventLoop: SelectableEventLoop) throws -> DatagramChannel, _ registerAndBind: @escaping (EventLoop, DatagramChannel) -> EventLoopFuture<Void>) -> EventLoopFuture<Channel> {
let eventLoop = self.group.next()
let channelInitializer = self.channelInitializer ?? { _ in eventLoop.makeSucceededFuture(()) }
let channelOptions = self.channelOptions
let channel: DatagramChannel
do {
channel = try makeChannel(eventLoop as! SelectableEventLoop)
} catch {
return eventLoop.makeFailedFuture(error)
}
return channelInitializer(channel).flatMap {
channelOptions.applyAll(channel: channel)
}.flatMap {
registerAndBind(eventLoop, channel)
}.map {
channel
}.flatMapError { error in
eventLoop.makeFailedFuture(error)
}
}
}
/* for tests */ internal struct ChannelOptionStorage {
private var storage: [(Any, (Any, (Channel) -> (Any, Any) -> EventLoopFuture<Void>))] = []
mutating func put<K: ChannelOption & Equatable>(key: K, value: K.OptionType) {
return self.put(key: key, value: value, equalsFunc: ==)
}
// HACK: this function should go for NIO 2.0, all ChannelOptions should be equatable
mutating func put<K: ChannelOption>(key: K, value: K.OptionType) {
if K.self == SocketOption.self {
return self.put(key: key as! SocketOption, value: value as! SocketOptionValue) { lhs, rhs in
switch (lhs, rhs) {
case (.const(let lLevel, let lName), .const(let rLevel, let rName)):
return lLevel == rLevel && lName == rName
}
}
} else {
return self.put(key: key, value: value) { _, _ in true }
}
}
mutating func put<K: ChannelOption>(key: K,
value newValue: K.OptionType,
equalsFunc: (K, K) -> Bool) {
func applier(_ t: Channel) -> (Any, Any) -> EventLoopFuture<Void> {
return { (x, y) in
return t.setOption(option: x as! K, value: y as! K.OptionType)
}
}
var hasSet = false
self.storage = self.storage.map { typeAndValue in
let (type, value) = typeAndValue
if type is K && equalsFunc(type as! K, key) {
hasSet = true
return (key, (newValue, applier))
} else {
return (type, value)
}
}
if !hasSet {
self.storage.append((key, (newValue, applier)))
}
}
func applyAll(channel: Channel) -> EventLoopFuture<Void> {
let applyPromise = channel.eventLoop.makePromise(of: Void.self)
var it = self.storage.makeIterator()
func applyNext() {
guard let (key, (value, applier)) = it.next() else {
// If we reached the end, everything is applied.
applyPromise.succeed(())
return
}
applier(channel)(key, value).map {
applyNext()
}.cascadeFailure(promise: applyPromise)
}
applyNext()
return applyPromise.futureResult
}
}