Classes

The following classes are available globally.

  • A ChannelHandler that implements a backoff for a ServerChannel when accept produces an IOError. These errors are often recoverable by reducing the rate at which we call accept.

    See more

    Declaration

    Swift

    public final class AcceptBackoffHandler : ChannelDuplexHandler, RemovableChannelHandler
  • ChannelHandler implementation which enforces back-pressure by stopping to read from the remote peer when it cannot write back fast enough. It will start reading again once pending data was written.

    See more

    Declaration

    Swift

    public final class BackPressureHandler : ChannelDuplexHandler, RemovableChannelHandler
  • Triggers an IdleStateEvent when a Channel has not performed read, write, or both operation for a while.

    See more

    Declaration

    Swift

    public final class IdleStateHandler : ChannelDuplexHandler, RemovableChannelHandler
  • A list of ChannelHandlers that handle or intercept inbound events and outbound operations of a Channel. ChannelPipeline implements an advanced form of the Intercepting Filter pattern to give a user full control over how an event is handled and how the ChannelHandlers in a pipeline interact with each other.

    Creation of a pipeline

    Each Channel has its own ChannelPipeline and it is created automatically when a new Channel is created.

    How an event flows in a pipeline

    The following diagram describes how I/O events are typically processed by ChannelHandlers in a ChannelPipeline. An I/O event is handled by either a ChannelInboundHandler or a ChannelOutboundHandler and is forwarded to the next handler in the ChannelPipeline by calling the event propagation methods defined in ChannelHandlerContext, such as ChannelHandlerContext.fireChannelRead and ChannelHandlerContext.write.

                                                       I/O Request
                                                       via `Channel` or
                                                       `ChannelHandlerContext`
                                                         |
     +---------------------------------------------------+---------------+
     |                           ChannelPipeline         |               |
     |                                TAIL              \|/              |
     |    +---------------------+            +-----------+----------+    |
     |    | Inbound Handler  N  |            | Outbound Handler  1  |    |
     |    +----------+----------+            +-----------+----------+    |
     |              /|\                                  |               |
     |               |                                  \|/              |
     |    +----------+----------+            +-----------+----------+    |
     |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
     |    +----------+----------+            +-----------+----------+    |
     |              /|\                                  .               |
     |               .                                   .               |
     | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
     |        [ method call]                       [method call]         |
     |               .                                   .               |
     |               .                                  \|/              |
     |    +----------+----------+            +-----------+----------+    |
     |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
     |    +----------+----------+            +-----------+----------+    |
     |              /|\                                  |               |
     |               |                                  \|/              |
     |    +----------+----------+            +-----------+----------+    |
     |    | Inbound Handler  1  |            | Outbound Handler  M  |    |
     |    +----------+----------+            +-----------+----------+    |
     |              /|\             HEAD                 |               |
     +---------------+-----------------------------------+---------------+
                     |                                  \|/
     +---------------+-----------------------------------+---------------+
     |               |                                   |               |
     |       [ Socket.read ]                    [ Socket.write ]         |
     |                                                                   |
     |  SwiftNIO Internal I/O Threads (Transport Implementation)         |
     +-------------------------------------------------------------------+
    

    An inbound event is handled by the inbound handlers in the head-to-tail direction as shown on the left side of the diagram. An inbound handler usually handles the inbound data generated by the I/O thread on the bottom of the diagram. The inbound data is often read from a remote peer via the actual input operation such as Socket.read. If an inbound event goes beyond the tail inbound handler, it is discarded silently, or logged if it needs your attention.

    An outbound event is handled by the outbound handlers in the tail-to-head direction as shown on the right side of the diagram. An outbound handler usually generates or transforms the outbound traffic such as write requests. If an outbound event goes beyond the head outbound handler, it is handled by an I/O thread associated with the Channel. The I/O thread often performs the actual output operation such as Socket.write.

    For example, let us assume that we created the following pipeline:

    ChannelPipeline p = ...
    let future = p.add(name: "1", handler: InboundHandlerA()).flatMap {
      p.add(name: "2", handler: InboundHandlerB())
    }.flatMap {
      p.add(name: "3", handler: OutboundHandlerA())
    }.flatMap {
      p.add(name: "4", handler: OutboundHandlerB())
    }.flatMap {
      p.add(name: "5", handler: InboundOutboundHandlerX())
    }
    // Handle the future as well ....
    

    In the example above, a class whose name starts with Inbound is an inbound handler. A class whose name starts with Outbound is an outbound handler.

    In the given example configuration, the handler evaluation order is 1, 2, 3, 4, 5 when an event goes inbound. When an event goes outbound, the order is 5, 4, 3, 2, 1. On top of this principle, ChannelPipeline skips the evaluation of certain handlers to shorten the stack depth:

    • 3 and 4 don’t implement ChannelInboundHandler, and therefore the actual evaluation order of an inbound event will be: 1, 2, and 5.
    • 1 and 2 don’t implement ChannelOutboundHandler, and therefore the actual evaluation order of a outbound event will be: 5, 4, and 3.
    • If 5 implements both ChannelInboundHandler and ChannelOutboundHandler, the evaluation order of an inbound and a outbound event could be 125 and 543 respectively.

    Note: Handlers may choose not to propagate messages down the pipeline immediately. For example a handler may need to wait for additional data before sending a protocol event to the next handler in the pipeline. Due to this you can’t assume that later handlers in the pipeline will receive the same number of events as were sent, or that events of different types will arrive in the same order. For example - a user event could overtake a data event if a handler is aggregating data events before propagating but immediately propagating user events.

    Forwarding an event to the next handler

    As you might noticed in the diagram above, a handler has to invoke the event propagation methods in ChannelHandlerContext to forward an event to its next handler. Those methods include:

    Building a pipeline

    A user is supposed to have one or more ChannelHandlers in a ChannelPipeline to receive I/O events (e.g. read) and to request I/O operations (e.g. write and close). For example, a typical server will have the following handlers in each channel’s pipeline, but your mileage may vary depending on the complexity and characteristics of the protocol and business logic:

    • Protocol Decoder - translates binary data (e.g. ByteBuffer) into a struct / class
    • Protocol Encoder - translates a struct / class into binary data (e.g. ByteBuffer)
    • Business Logic Handler - performs the actual business logic (e.g. database access)

    Thread safety

    A ChannelHandler can be added or removed at any time because a ChannelPipeline is thread safe.

    See more

    Declaration

    Swift

    public final class ChannelPipeline : ChannelInvoker
    extension ChannelPipeline: CustomDebugStringConvertible

Synchronous View

B2MDBuffer Helpers

ByteToMessageHandler: ChannelInboundHandler

  • A handler which turns a given MessageToByteEncoder into a ChannelOutboundHandler that can then be added to a ChannelPipeline.

    See more

    Declaration

    Swift

    public final class MessageToByteHandler<Encoder> : ChannelOutboundHandler where Encoder : MessageToByteEncoder
  • Returned once a task was scheduled to be repeatedly executed on the EventLoop.

    A RepeatedTask allows the user to cancel() the repeated scheduling of further tasks.

    See more

    Declaration

    Swift

    public final class RepeatedTask
  • Holder for a result that will be provided later.

    Functions that promise to do work asynchronously can return an EventLoopFuture<Value>. The recipient of such an object can then observe it to be notified when the operation completes.

    The provider of a EventLoopFuture<Value> can create and return a placeholder object before the actual result is available. For example:

    func getNetworkData(args) -> EventLoopFuture<NetworkResponse> {
        let promise = eventLoop.makePromise(of: NetworkResponse.self)
        queue.async {
            . . . do some work . . .
            promise.succeed(response)
            . . . if it fails, instead . . .
            promise.fail(error)
        }
        return promise.futureResult
    }
    

    Note that this function returns immediately; the promise object will be given a value later on. This behaviour is common to Future/Promise implementations in many programming languages. If you are unfamiliar with this kind of object, the following resources may be helpful:

    If you receive a EventLoopFuture<Value> from another function, you have a number of options: The most common operation is to use flatMap() or map() to add a function that will be called with the eventual result. Both methods returns a new EventLoopFuture<Value> immediately that will receive the return value from your function, but they behave differently. If you have a function that can return synchronously, the map function will transform the result of type Value to a the new result of type NewValue and return an EventLoopFuture<NewValue>.

    let networkData = getNetworkData(args)
    
    // When network data is received, convert it.
    let processedResult: EventLoopFuture<Processed> = networkData.map { (n: NetworkResponse) -> Processed in
        ... parse network data ....
        return processedResult
    }
    

    If however you need to do more asynchronous processing, you can call flatMap(). The return value of the function passed to flatMap must be a new EventLoopFuture<NewValue> object: the return value of flatMap() is a new EventLoopFuture<NewValue> that will contain the eventual result of both the original operation and the subsequent one.

    // When converted network data is available, begin the database operation.
    let databaseResult: EventLoopFuture<DBResult> = processedResult.flatMap { (p: Processed) -> EventLoopFuture<DBResult> in
        return someDatabaseOperation(p)
    }
    

    In essence, future chains created via flatMap() provide a form of data-driven asynchronous programming that allows you to dynamically declare data dependencies for your various operations.

    EventLoopFuture chains created via flatMap() are sufficient for most purposes. All of the registered functions will eventually run in order. If one of those functions throws an error, that error will bypass the remaining functions. You can use flatMapError() to handle and optionally recover from errors in the middle of a chain.

    At the end of an EventLoopFuture chain, you can use whenSuccess() or whenFailure() to add an observer callback that will be invoked with the result or error at that point. (Note: If you ever find yourself invoking promise.succeed() from inside a whenSuccess() callback, you probably should use flatMap() or cascade(to:) instead.)

    EventLoopFuture objects are typically obtained by:

    • Using .flatMap() on an existing future to create a new future for the next step in a series of operations.
    • Initializing an EventLoopFuture that already has a value or an error

    Threading and Futures

    One of the major performance advantages of NIO over something like Node.js or Python’s asyncio is that NIO will by default run multiple event loops at once, on different threads. As most network protocols do not require blocking operation, at least in their low level implementations, this provides enormous speedups on machines with many cores such as most modern servers.

    However, it can present a challenge at higher levels of abstraction when coordination between those threads becomes necessary. This is usually the case whenever the events on one connection (that is, one Channel) depend on events on another one. As these Channels may be scheduled on different event loops (and so different threads) care needs to be taken to ensure that communication between the two loops is done in a thread-safe manner that avoids concurrent mutation of shared state from multiple loops at once.

    The main primitives NIO provides for this use are the EventLoopPromise and EventLoopFuture. As their names suggest, these two objects are aware of event loops, and so can help manage the safety and correctness of your programs. However, understanding the exact semantics of these objects is critical to ensuring the safety of your code.

    Callbacks

    The most important principle of the EventLoopPromise and EventLoopFuture is this: all callbacks registered on an EventLoopFuture will execute on the thread corresponding to the event loop that created the Future, regardless of what thread succeeds or fails the corresponding EventLoopPromise.

    This means that if your code created the EventLoopPromise, you can be extremely confident of what thread the callback will execute on: after all, you held the event loop in hand when you created the EventLoopPromise. However, if your code is handed an EventLoopFuture or EventLoopPromise, and you want to register callbacks on those objects, you cannot be confident that those callbacks will execute on the same EventLoop that your code does.

    This presents a problem: how do you ensure thread-safety when registering callbacks on an arbitrary EventLoopFuture? The short answer is that when you are holding an EventLoopFuture, you can always obtain a new EventLoopFuture whose callbacks will execute on your event loop. You do this by calling EventLoopFuture.hop(to:). This function returns a new EventLoopFuture whose callbacks are guaranteed to fire on the provided event loop. As an added bonus, hopTo will check whether the provided EventLoopFuture was already scheduled to dispatch on the event loop in question, and avoid doing any work if that was the case.

    This means that for any EventLoopFuture that your code did not create itself (via EventLoopPromise.futureResult), use of hopTo is strongly encouraged to help guarantee thread-safety. It should only be elided when thread-safety is provably not needed.

    The “thread affinity” of EventLoopFutures is critical to writing safe, performant concurrent code without boilerplate. It allows you to avoid needing to write or use locks in your own code, instead using the natural synchronization of the EventLoop to manage your thread-safety. In general, if any of your ChannelHandlers or EventLoopFuture callbacks need to invoke a lock (either directly or in the form of DispatchQueue) this should be considered a code smell worth investigating: the EventLoop-based synchronization guarantees of EventLoopFuture should be sufficient to guarantee thread-safety.

    See more

    Declaration

    Swift

    public final class EventLoopFuture<Value>
    extension EventLoopFuture: Equatable
  • A NIOFileHandle is a handle to an open file.

    When creating a NIOFileHandle it takes ownership of the underlying file descriptor. When a NIOFileHandle is no longer needed you must close it or take back ownership of the file descriptor using takeDescriptorOwnership.

    Note

    One underlying file descriptor should usually be managed by one NIOFileHandle only.

    Warning

    Failing to manage the lifetime of a NIOFileHandle correctly will result in undefined behaviour.

    Warning

    NIOFileHandle objects are not thread-safe and are mutable. They also cannot be fully thread-safe as they refer to a global underlying file descriptor.

    See more

    Declaration

    Swift

    public final class NIOFileHandle : FileDescriptor
    extension NIOFileHandle: CustomStringConvertible
  • A representation of a single network interface on a system.

    See more

    Declaration

    Swift

    @available(*, deprecated, renamed: "NIONetworkDevice")
    public final class NIONetworkInterface
    extension NIONetworkInterface: CustomDebugStringConvertible
    extension NIONetworkInterface: Equatable
  • A ChannelInboundHandler that closes the channel when an error is caught

    See more

    Declaration

    Swift

    public final class NIOCloseOnErrorHandler : ChannelInboundHandler

NIOSingleStepByteToMessageDecoder: ByteToMessageDecoder

  • NIOSingleStepByteToMessageProcessor uses a NIOSingleStepByteToMessageDecoder to produce messages from a stream of incoming bytes. It works like ByteToMessageHandler but may be used outside of the channel pipeline. This allows processing of wrapped protocols in a general way.

    A NIOSingleStepByteToMessageProcessor is first initialized with a NIOSingleStepByteToMessageDecoder. Then call process as each ByteBuffer is received from the stream. The closure is called repeatedly with each message produced by the decoder.

    When your stream ends, call finishProcessing to ensure all buffered data is passed to your decoder. This will call decodeLast one or more times with any remaining data.

    Example

    Below is an example of a protocol decoded by TwoByteStringCodec that is sent over HTTP. RawBodyMessageHandler forwards the headers and trailers directly and uses NIOSingleStepByteToMessageProcessor to send whole decoded messages.

    class TwoByteStringCodec: NIOSingleStepByteToMessageDecoder {
        typealias InboundOut = String
    
        public func decode(buffer: inout ByteBuffer) throws -> InboundOut? {
            return buffer.readString(length: 2)
        }
    
        public func decodeLast(buffer: inout ByteBuffer, seenEOF: Bool) throws -> InboundOut? {
            return try self.decode(buffer: &buffer)
        }
    }
    
    class RawBodyMessageHandler: ChannelInboundHandler {
        typealias InboundIn = HTTPServerRequestPart // alias for HTTPPart<HTTPRequestHead, ByteBuffer>
        // This converts the body from ByteBuffer to String, our message type
        typealias InboundOut = HTTPPart<HTTPRequestHead, String>
    
        private var messageProcessor: NIOSingleStepByteToMessageProcessor<TwoByteStringCodec>? = nil
    
        func channelRead(context: ChannelHandlerContext, data: NIOAny) {
            let req = self.unwrapInboundIn(data)
            do {
                switch req {
                case .head(let head):
                    // simply forward on the head
                    context.fireChannelRead(self.wrapInboundOut(.head(head)))
                case .body(let body):
                    if self.messageProcessor == nil {
                        self.messageProcessor = NIOSingleStepByteToMessageProcessor(TwoByteStringCodec())
                    }
                    try self.messageProcessor!.process(buffer: body) { message in
                        self.channelReadMessage(context: context, message: message)
                    }
                case .end(let trailers):
                    // Forward on any remaining messages and the trailers
                    try self.messageProcessor?.finishProcessing(seenEOF: false) { message in
                        self.channelReadMessage(context: context, message: message)
                    }
                    context.fireChannelRead(self.wrapInboundOut(.end(trailers)))
                }
            } catch {
                context.fireErrorCaught(error)
            }
        }
    
        // Forward on the body messages as whole messages
        func channelReadMessage(context: ChannelHandlerContext, message: String) {
            context.fireChannelRead(self.wrapInboundOut(.body(message)))
        }
    }
    
    private class DecodedBodyHTTPHandler: ChannelInboundHandler {
        typealias InboundIn = HTTPPart<HTTPRequestHead, String>
        typealias OutboundOut = HTTPServerResponsePart
    
        var msgs: [String] = []
    
        func channelRead(context: ChannelHandlerContext, data: NIOAny) {
            let message = self.unwrapInboundIn(data)
    
            switch message {
            case .head(let head):
                print("head: \(head)")
            case .body(let msg):
                self.msgs.append(msg)
            case .end(let trailers):
                print("trailers: \(trailers)")
                var responseBuffer = context.channel.allocator.buffer(capacity: 32)
                for msg in msgs {
                    responseBuffer.writeString(msg)
                    responseBuffer.writeStaticString("\n")
                }
                var headers = HTTPHeaders()
                headers.add(name: "content-length", value: String(responseBuffer.readableBytes))
    
                context.write(self.wrapOutboundOut(HTTPServerResponsePart.head(
                    HTTPResponseHead(version: .http1_1,
                                     status: .ok, headers: headers))), promise: nil)
    
                context.write(self.wrapOutboundOut(HTTPServerResponsePart.body(
                    .byteBuffer(responseBuffer))), promise: nil)
                context.writeAndFlush(self.wrapOutboundOut(HTTPServerResponsePart.end(nil)), promise: nil)
            }
        }
    }
    
    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
    let bootstrap = ServerBootstrap(group: group).childChannelInitializer({channel in
        channel.pipeline.configureHTTPServerPipeline(withPipeliningAssistance: true, withErrorHandling: true).flatMap { _ in
            channel.pipeline.addHandlers([RawBodyMessageHandler(), DecodedBodyHTTPHandler()])
        }
    })
    let channelFuture = bootstrap.bind(host: "127.0.0.1", port: 0)
    
    See more

    Declaration

    Swift

    public final class NIOSingleStepByteToMessageProcessor<Decoder> where Decoder : NIOSingleStepByteToMessageDecoder