ChannelPipeline

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.

ChannelPipeline.swift:141
final class ChannelPipeline

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.

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:

  • Inbound event propagation methods defined in ChannelInboundInvoker

  • Outbound event propagation methods defined in ChannelOutboundInvoker.

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.