swift-nio/Sources/NIOCore/ChannelHandler.swift

373 lines
20 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
//
//===----------------------------------------------------------------------===//
/// Base protocol for handlers that handle I/O events or intercept an I/O operation.
///
/// All methods are called from within the `EventLoop` that is assigned to the `Channel` itself.
//
/// You should _never_ implement this protocol directly. Please implement one of its sub-protocols.
public protocol ChannelHandler: AnyObject {
/// Called when this `ChannelHandler` is added to the `ChannelPipeline`.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func handlerAdded(context: ChannelHandlerContext)
/// Called when this `ChannelHandler` is removed from the `ChannelPipeline`.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func handlerRemoved(context: ChannelHandlerContext)
}
/// Untyped `ChannelHandler` which handles outbound I/O events or intercept an outbound I/O operation.
///
/// Despite the fact that `write` is one of the methods on this `protocol`, you should avoid assuming that "outbound" events are to do with
/// writing to channel sources. Instead, "outbound" events are events that are passed *to* the channel source (e.g. a socket): that is, things you tell
/// the channel source to do. That includes `write` ("write this data to the channel source"), but it also includes `read` ("please begin attempting to read from
/// the channel source") and `bind` ("please bind the following address"), which have nothing to do with sending data.
///
/// We _strongly_ advise against implementing this protocol directly. Please implement `ChannelOutboundHandler`.
public protocol _ChannelOutboundHandler: ChannelHandler {
/// Called to request that the `Channel` register itself for I/O events with its `EventLoop`.
/// This should call `context.register` to forward the operation to the next `_ChannelOutboundHandler` in the `ChannelPipeline` or
/// complete the `EventLoopPromise` to let the caller know that the operation completed.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func register(context: ChannelHandlerContext, promise: EventLoopPromise<Void>?)
/// Called to request that the `Channel` bind to a specific `SocketAddress`.
///
/// This should call `context.bind` to forward the operation to the next `_ChannelOutboundHandler` in the `ChannelPipeline` or
/// complete the `EventLoopPromise` to let the caller know that the operation completed.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - to: The `SocketAddress` to which this `Channel` should bind.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func bind(context: ChannelHandlerContext, to: SocketAddress, promise: EventLoopPromise<Void>?)
/// Called to request that the `Channel` connect to a given `SocketAddress`.
///
/// This should call `context.connect` to forward the operation to the next `_ChannelOutboundHandler` in the `ChannelPipeline` or
/// complete the `EventLoopPromise` to let the caller know that the operation completed.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - to: The `SocketAddress` to which the the `Channel` should connect.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func connect(context: ChannelHandlerContext, to: SocketAddress, promise: EventLoopPromise<Void>?)
/// Called to request a write operation. The write operation will write the messages through the
/// `ChannelPipeline`. Those are then ready to be flushed to the actual `Channel` when
/// `Channel.flush` or `ChannelHandlerContext.flush` is called.
///
/// This should call `context.write` to forward the operation to the next `_ChannelOutboundHandler` in the `ChannelPipeline` or
/// complete the `EventLoopPromise` to let the caller know that the operation completed.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - data: The data to write through the `Channel`, wrapped in a `NIOAny`.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?)
/// Called to request that the `Channel` flush all pending writes. The flush operation will try to flush out all previous written messages
/// that are pending.
///
/// This should call `context.flush` to forward the operation to the next `_ChannelOutboundHandler` in the `ChannelPipeline` or just
/// discard it if the flush should be suppressed.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func flush(context: ChannelHandlerContext)
/// Called to request that the `Channel` perform a read when data is ready. The read operation will signal that we are ready to read more data.
///
/// This should call `context.read` to forward the operation to the next `_ChannelOutboundHandler` in the `ChannelPipeline` or just
/// discard it if the read should be suppressed.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func read(context: ChannelHandlerContext)
/// Called to request that the `Channel` close itself down`.
///
/// This should call `context.close` to forward the operation to the next `_ChannelOutboundHandler` in the `ChannelPipeline` or
/// complete the `EventLoopPromise` to let the caller know that the operation completed.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - 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 close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise<Void>?)
/// Called when an user outbound event is triggered.
///
/// This should call `context.triggerUserOutboundEvent` to forward the operation to the next `_ChannelOutboundHandler` in the `ChannelPipeline` or
/// complete the `EventLoopPromise` to let the caller know that the operation completed.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - event: The triggered event.
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
func triggerUserOutboundEvent(context: ChannelHandlerContext, event: Any, promise: EventLoopPromise<Void>?)
}
/// Untyped `ChannelHandler` which handles inbound I/O events.
///
/// Despite the fact that `channelRead` is one of the methods on this `protocol`, you should avoid assuming that "inbound" events are to do with
/// reading from channel sources. Instead, "inbound" events are events that originate *from* the channel source (e.g. the socket): that is, events that the
/// channel source tells you about. This includes things like `channelRead` ("there is some data to read"), but it also includes things like
/// `channelWritabilityChanged` ("this source is no longer marked writable").
///
/// We _strongly_ advise against implementing this protocol directly. Please implement `ChannelInboundHandler`.
public protocol _ChannelInboundHandler: ChannelHandler {
/// Called when the `Channel` has successfully registered with its `EventLoop` to handle I/O.
///
/// This should call `context.fireChannelRegistered` to forward the operation to the next `_ChannelInboundHandler` in the `ChannelPipeline` if you want to allow the next handler to also handle the event.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func channelRegistered(context: ChannelHandlerContext)
/// Called when the `Channel` has unregistered from its `EventLoop`, and so will no longer be receiving I/O events.
///
/// This should call `context.fireChannelUnregistered` to forward the operation to the next `_ChannelInboundHandler` in the `ChannelPipeline` if you want to allow the next handler to also handle the event.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func channelUnregistered(context: ChannelHandlerContext)
/// Called when the `Channel` has become active, and is able to send and receive data.
///
/// This should call `context.fireChannelActive` to forward the operation to the next `_ChannelInboundHandler` in the `ChannelPipeline` if you want to allow the next handler to also handle the event.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func channelActive(context: ChannelHandlerContext)
/// Called when the `Channel` has become inactive and is no longer able to send and receive data`.
///
/// This should call `context.fireChannelInactive` to forward the operation to the next `_ChannelInboundHandler` in the `ChannelPipeline` if you want to allow the next handler to also handle the event.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func channelInactive(context: ChannelHandlerContext)
/// Called when some data has been read from the remote peer.
///
/// This should call `context.fireChannelRead` to forward the operation to the next `_ChannelInboundHandler` in the `ChannelPipeline` if you want to allow the next handler to also handle the event.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - data: The data read from the remote peer, wrapped in a `NIOAny`.
func channelRead(context: ChannelHandlerContext, data: NIOAny)
/// Called when the `Channel` has completed its current read loop, either because no more data is available to read from the transport at this time, or because the `Channel` needs to yield to the event loop to process other I/O events for other `Channel`s.
/// If `ChannelOptions.autoRead` is `false` no further read attempt will be made until `ChannelHandlerContext.read` or `Channel.read` is explicitly called.
///
/// This should call `context.fireChannelReadComplete` to forward the operation to the next `_ChannelInboundHandler` in the `ChannelPipeline` if you want to allow the next handler to also handle the event.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func channelReadComplete(context: ChannelHandlerContext)
/// The writability state of the `Channel` has changed, either because it has buffered more data than the writability high water mark, or because the amount of buffered data has dropped below the writability low water mark.
/// You can check the state with `Channel.isWritable`.
///
/// This should call `context.fireChannelWritabilityChanged` to forward the operation to the next `_ChannelInboundHandler` in the `ChannelPipeline` if you want to allow the next handler to also handle the event.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
func channelWritabilityChanged(context: ChannelHandlerContext)
/// Called when a user inbound event has been triggered.
///
/// This should call `context.fireUserInboundEventTriggered` to forward the operation to the next `_ChannelInboundHandler` in the `ChannelPipeline` if you want to allow the next handler to also handle the event.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - event: The event.
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any)
/// An error was encountered earlier in the inbound `ChannelPipeline`.
///
/// This should call `context.fireErrorCaught` to forward the operation to the next `_ChannelInboundHandler` in the `ChannelPipeline` if you want to allow the next handler to also handle the error.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` which this `ChannelHandler` belongs to.
/// - error: The `Error` that was encountered.
func errorCaught(context: ChannelHandlerContext, error: Error)
}
// Default implementations for the ChannelHandler protocol
extension ChannelHandler {
/// Do nothing by default.
public func handlerAdded(context: ChannelHandlerContext) {
}
/// Do nothing by default.
public func handlerRemoved(context: ChannelHandlerContext) {
}
}
/// Provides default implementations for all methods defined by `_ChannelOutboundHandler`.
///
/// These default implementations will just call `context.methodName` to forward to the next `_ChannelOutboundHandler` in
/// the `ChannelPipeline` until the operation is handled by the `Channel` itself.
extension _ChannelOutboundHandler {
public func register(context: ChannelHandlerContext, promise: EventLoopPromise<Void>?) {
context.register(promise: promise)
}
public func bind(context: ChannelHandlerContext, to address: SocketAddress, promise: EventLoopPromise<Void>?) {
context.bind(to: address, promise: promise)
}
public func connect(context: ChannelHandlerContext, to address: SocketAddress, promise: EventLoopPromise<Void>?) {
context.connect(to: address, promise: promise)
}
public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
context.write(data, promise: promise)
}
public func flush(context: ChannelHandlerContext) {
context.flush()
}
public func read(context: ChannelHandlerContext) {
context.read()
}
public func close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise<Void>?) {
context.close(mode: mode, promise: promise)
}
public func triggerUserOutboundEvent(context: ChannelHandlerContext, event: Any, promise: EventLoopPromise<Void>?) {
context.triggerUserOutboundEvent(event, promise: promise)
}
}
/// Provides default implementations for all methods defined by `_ChannelInboundHandler`.
///
/// These default implementations will just `context.fire*` to forward to the next `_ChannelInboundHandler` in
/// the `ChannelPipeline` until the operation is handled by the `Channel` itself.
extension _ChannelInboundHandler {
public func channelRegistered(context: ChannelHandlerContext) {
context.fireChannelRegistered()
}
public func channelUnregistered(context: ChannelHandlerContext) {
context.fireChannelUnregistered()
}
public func channelActive(context: ChannelHandlerContext) {
context.fireChannelActive()
}
public func channelInactive(context: ChannelHandlerContext) {
context.fireChannelInactive()
}
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
context.fireChannelRead(data)
}
public func channelReadComplete(context: ChannelHandlerContext) {
context.fireChannelReadComplete()
}
public func channelWritabilityChanged(context: ChannelHandlerContext) {
context.fireChannelWritabilityChanged()
}
public func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
context.fireUserInboundEventTriggered(event)
}
public func errorCaught(context: ChannelHandlerContext, error: Error) {
context.fireErrorCaught(error)
}
}
/// A `RemovableChannelHandler` is a `ChannelHandler` that can be dynamically removed from a `ChannelPipeline` whilst
/// the `Channel` is operating normally.
/// A `RemovableChannelHandler` is required to remove itself from the `ChannelPipeline` (using
/// `ChannelHandlerContext.removeHandler`) as soon as possible.
///
/// - note: When a `Channel` gets torn down, every `ChannelHandler` in the `Channel`'s `ChannelPipeline` will be
/// removed from the `ChannelPipeline`. Those removals however happen synchronously and are not going through
/// the methods of this protocol.
public protocol RemovableChannelHandler: ChannelHandler {
/// Ask the receiving `RemovableChannelHandler` to remove itself from the `ChannelPipeline` as soon as possible.
/// The receiving `RemovableChannelHandler` may elect to remove itself sometime after this method call, rather than
/// immediately, but if it does so it must take the necessary precautions to handle events arriving between the
/// invocation of this method and the call to `ChannelHandlerContext.removeHandler` that triggers the actual
/// removal.
///
/// - note: Like the other `ChannelHandler` methods, this method should not be invoked by the user directly. To
/// remove a `RemovableChannelHandler` from the `ChannelPipeline`, use `ChannelPipeline.remove`.
///
/// - parameters:
/// - context: The `ChannelHandlerContext` of the `RemovableChannelHandler` to be removed from the `ChannelPipeline`.
/// - removalToken: The removal token to hand to `ChannelHandlerContext.removeHandler` to trigger the actual
/// removal from the `ChannelPipeline`.
func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken)
}
extension RemovableChannelHandler {
// Implements the default behaviour which is to synchronously remove the handler from the pipeline. Thanks to this,
// stateless `ChannelHandler`s can just use `RemovableChannelHandler` as a marker-protocol and declare themselves
// as removable without writing any extra code.
public func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) {
precondition(context.handler === self)
context.leavePipeline(removalToken: removalToken)
}
}
/// The result of protocol negotiation.
@_spi(AsyncChannel)
public enum NIOProtocolNegotiationResult<NegotiationResult> {
/// Indicates that the protocol negotiation finished.
case finished(NegotiationResult)
/// Indicates that protocol negotiation has been deferred to the next handler.
case deferredResult(EventLoopFuture<NIOProtocolNegotiationResult<NegotiationResult>>)
}
@_spi(AsyncChannel)
extension NIOProtocolNegotiationResult: Equatable where NegotiationResult: Equatable {}
@_spi(AsyncChannel)
extension NIOProtocolNegotiationResult: Sendable where NegotiationResult: Sendable {}
/// A ``ProtocolNegotiationHandler`` is a ``ChannelHandler`` that is responsible for negotiating networking protocols.
///
/// Typically these handlers are at the tail of the pipeline and wait until the peer indicated what protocol should be used. Once, the protocol
/// has been negotiated the handlers allow user code to configure the pipeline.
@_spi(AsyncChannel)
public protocol NIOProtocolNegotiationHandler: ChannelHandler {
associatedtype NegotiationResult
/// The future which gets succeeded with the protocol negotiation result.
var protocolNegotiationResult: EventLoopFuture<NIOProtocolNegotiationResult<NegotiationResult>> { get }
}