swift-nio/Sources/NIOCore/Channel.swift

417 lines
17 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2021 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
//
//===----------------------------------------------------------------------===//
import NIOConcurrencyHelpers
/// The core `Channel` methods that are for internal use of the `Channel` implementation only.
///
/// - warning: If you are not implementing a custom `Channel` type, you should never call any of these.
///
/// - note: All methods must be called from the `EventLoop` thread.
public protocol ChannelCore: AnyObject {
/// Returns the local bound `SocketAddress`.
func localAddress0() throws -> SocketAddress
/// Return the connected `SocketAddress`.
func remoteAddress0() throws -> SocketAddress
/// Register with the `EventLoop` to receive I/O notifications.
///
/// - parameters:
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func register0(promise: EventLoopPromise<Void>?)
/// Register channel as already connected or bound socket.
/// - parameters:
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func registerAlreadyConfigured0(promise: EventLoopPromise<Void>?)
/// Bind to a `SocketAddress`.
///
/// - parameters:
/// - to: The `SocketAddress` to which we should bind the `Channel`.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func bind0(to: SocketAddress, promise: EventLoopPromise<Void>?)
/// Connect to a `SocketAddress`.
///
/// - parameters:
/// - to: The `SocketAddress` to which we should connect the `Channel`.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func connect0(to: SocketAddress, promise: EventLoopPromise<Void>?)
/// Write the given data to the outbound buffer.
///
/// - parameters:
/// - data: The data to write, wrapped in a `NIOAny`.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func write0(_ data: NIOAny, promise: EventLoopPromise<Void>?)
/// Try to flush out all previous written messages that are pending.
func flush0()
/// Request that the `Channel` perform a read when data is ready.
func read0()
/// Close the `Channel`.
///
/// - parameters:
/// - error: The `Error` which will be used to fail any pending writes.
/// - mode: The `CloseMode` to apply.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func close0(error: Error, mode: CloseMode, promise: EventLoopPromise<Void>?)
/// Trigger an outbound event.
///
/// - parameters:
/// - event: The triggered event.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func triggerUserOutboundEvent0(_ event: Any, promise: EventLoopPromise<Void>?)
/// Called when data was read from the `Channel` but it was not consumed by any `ChannelInboundHandler` in the `ChannelPipeline`.
///
/// - parameters:
/// - data: The data that was read, wrapped in a `NIOAny`.
func channelRead0(_ data: NIOAny)
/// Called when an inbound error was encountered but was not consumed by any `ChannelInboundHandler` in the `ChannelPipeline`.
///
/// - parameters:
/// - error: The `Error` that was encountered.
func errorCaught0(error: Error)
}
/// A `Channel` is easiest thought of as a network socket. But it can be anything that is capable of I/O operations such
/// as read, write, connect, and bind.
///
/// - note: All operations on `Channel` are thread-safe.
///
/// In SwiftNIO, all I/O operations are asynchronous and hence all operations on `Channel` are asynchronous too. This means
/// that all I/O operations will return immediately, usually before the work has been completed. The `EventLoopPromise`s
/// passed to or returned by the operations are used to retrieve the result of an operation after it has completed.
///
/// A `Channel` owns its `ChannelPipeline` which handles all I/O events and requests associated with the `Channel`.
public protocol Channel: AnyObject, ChannelOutboundInvoker, _NIOPreconcurrencySendable {
/// The `Channel`'s `ByteBuffer` allocator. This is _the only_ supported way of allocating `ByteBuffer`s to be used with this `Channel`.
var allocator: ByteBufferAllocator { get }
/// The `closeFuture` will fire when the `Channel` has been closed.
var closeFuture: EventLoopFuture<Void> { get }
/// The `ChannelPipeline` which handles all I/O events and requests associated with this `Channel`.
var pipeline: ChannelPipeline { get }
/// The local `SocketAddress`.
var localAddress: SocketAddress? { get }
/// The remote peer's `SocketAddress`.
var remoteAddress: SocketAddress? { get }
/// `Channel`s are hierarchical and might have a parent `Channel`. `Channel` hierarchies are in use for certain
/// protocols such as HTTP/2.
var parent: Channel? { get }
/// Set `option` to `value` on this `Channel`.
func setOption<Option: ChannelOption>(_ option: Option, value: Option.Value) -> EventLoopFuture<Void>
/// Get the value of `option` for this `Channel`.
func getOption<Option: ChannelOption>(_ option: Option) -> EventLoopFuture<Option.Value>
/// Returns if this `Channel` is currently writable.
var isWritable: Bool { get }
/// Returns if this `Channel` is currently active. Active is defined as the period of time after the
/// `channelActive` and before `channelInactive` has fired. The main use for this is to know if `channelActive`
/// or `channelInactive` can be expected next when `handlerAdded` was received.
var isActive: Bool { get }
/// Reach out to the `_ChannelCore`.
///
/// - warning: Unsafe, this is for use in NIO's core only.
var _channelCore: ChannelCore { get }
/// Returns a view of the `Channel` exposing synchronous versions of `setOption` and `getOption`.
/// The default implementation returns `nil`, and `Channel` implementations must opt in to
/// support this behavior.
var syncOptions: NIOSynchronousChannelOptions? { get }
}
extension Channel {
/// Default implementation: `NIOSynchronousChannelOptions` are not supported.
public var syncOptions: NIOSynchronousChannelOptions? {
return nil
}
}
public protocol NIOSynchronousChannelOptions {
/// Set `option` to `value` on this `Channel`.
///
/// - Important: Must be called on the `EventLoop` the `Channel` is running on.
func setOption<Option: ChannelOption>(_ option: Option, value: Option.Value) throws
/// Get the value of `option` for this `Channel`.
///
/// - Important: Must be called on the `EventLoop` the `Channel` is running on.
func getOption<Option: ChannelOption>(_ option: Option) throws -> Option.Value
}
/// Default implementations which will start on the head of the `ChannelPipeline`.
extension Channel {
public func bind(to address: SocketAddress, promise: EventLoopPromise<Void>?) {
pipeline.bind(to: address, promise: promise)
}
public func connect(to address: SocketAddress, promise: EventLoopPromise<Void>?) {
pipeline.connect(to: address, promise: promise)
}
public func write(_ data: NIOAny, promise: EventLoopPromise<Void>?) {
pipeline.write(data, promise: promise)
}
public func flush() {
pipeline.flush()
}
public func writeAndFlush(_ data: NIOAny, promise: EventLoopPromise<Void>?) {
pipeline.writeAndFlush(data, promise: promise)
}
public func read() {
pipeline.read()
}
public func close(mode: CloseMode = .all, promise: EventLoopPromise<Void>?) {
pipeline.close(mode: mode, promise: promise)
}
public func register(promise: EventLoopPromise<Void>?) {
pipeline.register(promise: promise)
}
public func registerAlreadyConfigured0(promise: EventLoopPromise<Void>?) {
promise?.fail(ChannelError.operationUnsupported)
}
public func triggerUserOutboundEvent(_ event: Any, promise: EventLoopPromise<Void>?) {
pipeline.triggerUserOutboundEvent(event, promise: promise)
}
}
/// Provides special extension to make writing data to the `Channel` easier by removing the need to wrap data in `NIOAny` manually.
extension Channel {
/// Write data into the `Channel`, automatically wrapping with `NIOAny`.
///
/// - seealso: `ChannelOutboundInvoker.write`.
public func write<T>(_ any: T) -> EventLoopFuture<Void> {
return self.write(NIOAny(any))
}
/// Write data into the `Channel`, automatically wrapping with `NIOAny`.
///
/// - seealso: `ChannelOutboundInvoker.write`.
public func write<T>(_ any: T, promise: EventLoopPromise<Void>?) {
self.write(NIOAny(any), promise: promise)
}
/// Write and flush data into the `Channel`, automatically wrapping with `NIOAny`.
///
/// - seealso: `ChannelOutboundInvoker.writeAndFlush`.
public func writeAndFlush<T>(_ any: T) -> EventLoopFuture<Void> {
return self.writeAndFlush(NIOAny(any))
}
/// Write and flush data into the `Channel`, automatically wrapping with `NIOAny`.
///
/// - seealso: `ChannelOutboundInvoker.writeAndFlush`.
public func writeAndFlush<T>(_ any: T, promise: EventLoopPromise<Void>?) {
self.writeAndFlush(NIOAny(any), promise: promise)
}
}
extension ChannelCore {
/// Unwraps the given `NIOAny` as a specific concrete type.
///
/// This method is intended for use when writing custom `ChannelCore` implementations.
/// This can safely be called in methods like `write0` to extract data from the `NIOAny`
/// provided in those cases.
///
/// Note that if the unwrap fails, this will cause a runtime trap. `ChannelCore`
/// implementations should be concrete about what types they support writing. If multiple
/// types are supported, consider using a tagged union to store the type information like
/// NIO's own `IOData`, which will minimise the amount of runtime type checking.
///
/// - parameters:
/// - data: The `NIOAny` to unwrap.
/// - as: The type to extract from the `NIOAny`.
/// - returns: The content of the `NIOAny`.
@inlinable
public func unwrapData<T>(_ data: NIOAny, as: T.Type = T.self) -> T {
return data.forceAs()
}
/// Attempts to unwrap the given `NIOAny` as a specific concrete type.
///
/// This method is intended for use when writing custom `ChannelCore` implementations.
/// This can safely be called in methods like `write0` to extract data from the `NIOAny`
/// provided in those cases.
///
/// If the unwrap fails, this will return `nil`. `ChannelCore` implementations should almost
/// always support only one runtime type, so in general they should avoid using this and prefer
/// using `unwrapData` instead. This method exists for rare use-cases where tolerating type
/// mismatches is acceptable.
///
/// - parameters:
/// - data: The `NIOAny` to unwrap.
/// - as: The type to extract from the `NIOAny`.
/// - returns: The content of the `NIOAny`, or `nil` if the type is incorrect.
/// - warning: If you are implementing a `ChannelCore`, you should use `unwrapData` unless you
/// are doing something _extremely_ unusual.
@inlinable
public func tryUnwrapData<T>(_ data: NIOAny, as: T.Type = T.self) -> T? {
return data.tryAs()
}
/// Removes the `ChannelHandler`s from the `ChannelPipeline` belonging to `channel`, and
/// closes that `ChannelPipeline`.
///
/// This method is intended for use when writing custom `ChannelCore` implementations.
/// This can be called from `close0` to tear down the `ChannelPipeline` when closure is
/// complete.
///
/// - parameters:
/// - channel: The `Channel` whose `ChannelPipeline` will be closed.
@available(*, deprecated, renamed: "removeHandlers(pipeline:)")
public func removeHandlers(channel: Channel) {
self.removeHandlers(pipeline: channel.pipeline)
}
/// Removes the `ChannelHandler`s from the `ChannelPipeline` `pipeline`, and
/// closes that `ChannelPipeline`.
///
/// This method is intended for use when writing custom `ChannelCore` implementations.
/// This can be called from `close0` to tear down the `ChannelPipeline` when closure is
/// complete.
///
/// - parameters:
/// - pipeline: The `ChannelPipline` to be closed.
public func removeHandlers(pipeline: ChannelPipeline) {
pipeline.removeHandlers()
}
}
/// An error that can occur on `Channel` operations.
public enum ChannelError: Error {
/// Tried to connect on a `Channel` that is already connecting.
case connectPending
/// Connect operation timed out
case connectTimeout(TimeAmount)
/// Unsupported operation triggered on a `Channel`. For example `connect` on a `ServerSocketChannel`.
case operationUnsupported
/// An I/O operation (e.g. read/write/flush) called on a channel that is already closed.
case ioOnClosedChannel
/// Close was called on a channel that is already closed.
case alreadyClosed
/// Output-side of the channel is closed.
case outputClosed
/// Input-side of the channel is closed.
case inputClosed
/// A read operation reached end-of-file. This usually means the remote peer closed the socket but it's still
/// open locally.
case eof
/// A `DatagramChannel` `write` was made with a buffer that is larger than the MTU for the connection, and so the
/// datagram was not written. Either shorten the datagram or manually fragment, and then try again.
case writeMessageTooLarge
/// A `DatagramChannel` `write` was made with an address that was not reachable and so could not be delivered.
case writeHostUnreachable
/// The local address of the `Channel` could not be determined.
case unknownLocalAddress
/// The address family of the multicast group was not valid for this `Channel`.
case badMulticastGroupAddressFamily
/// The address family of the provided multicast group join is not valid for this `Channel`.
case badInterfaceAddressFamily
/// An attempt was made to join a multicast group that does not correspond to a multicast
/// address.
case illegalMulticastAddress(SocketAddress)
/// Multicast is not supported on Interface
@available(*, deprecated, renamed: "NIOMulticastNotSupportedError")
case multicastNotSupported(NIONetworkInterface)
/// An operation that was inappropriate given the current `Channel` state was attempted.
case inappropriateOperationForState
/// An attempt was made to remove a ChannelHandler that is not removable.
case unremovableHandler
}
extension ChannelError: Equatable { }
/// The removal of a `ChannelHandler` using `ChannelPipeline.removeHandler` has been attempted more than once.
public struct NIOAttemptedToRemoveHandlerMultipleTimesError: Error {}
public enum DatagramChannelError {
public struct WriteOnUnconnectedSocketWithoutAddress: Error {
public init() {}
}
public struct WriteOnConnectedSocketWithInvalidAddress: Error {
let envelopeRemoteAddress: SocketAddress
let connectedRemoteAddress: SocketAddress
public init(
envelopeRemoteAddress: SocketAddress,
connectedRemoteAddress: SocketAddress
) {
self.envelopeRemoteAddress = envelopeRemoteAddress
self.connectedRemoteAddress = connectedRemoteAddress
}
}
}
/// An `Channel` related event that is passed through the `ChannelPipeline` to notify the user.
public enum ChannelEvent: Equatable, Sendable {
/// `ChannelOptions.allowRemoteHalfClosure` is `true` and input portion of the `Channel` was closed.
case inputClosed
/// Output portion of the `Channel` was closed.
case outputClosed
}
/// A `Channel` user event that is sent when the `Channel` has been asked to quiesce.
///
/// The action(s) that should be taken after receiving this event are both application and protocol dependent. If the
/// protocol supports a notion of requests and responses, it might make sense to stop accepting new requests but finish
/// processing the request currently in flight.
public struct ChannelShouldQuiesceEvent: Sendable {
public init() {
}
}