⚠️ Requires Swift 5.7

Omnibus is a set of helpers for SwiftNIO that allow you to leverage Swift’s generics type system to create NIO Channels.

It depends on Swift 5.7’s new ResultBuilder feature, Partial Blocks, and leverages this to enable type-checked channel building.

Channels are written in the same order as they’re used in NIO normally. Inbound data comes in from the top, using the channel’s read (Inbound) types. For official NIO channels that’s IOData, and official channels also write (Outbound) IOData.

Each handler transforms either the Inboud, Outbound or both types using ChannelInboundHandler, ChannelOutboundHandler or ChannelDuplexHandler respectively.

Omnibus’ channel builder type checks each handler, so that the InboundOut of one handler must match the InboundIn of the next. Likewise, it checks if the OutboundIn and OutboundOut match up as well.

When writing channels using this system, or NIO in general, the input (read data) comes in at the first handler. However, outbound data comes in at the bottom of the chain, and works its way back to the front.

Example

let myPrivateKey: Insecure.RSA.PrivateKey = ..

// Create a TCP Server that accepts clients on localhost:8082
let server = try await TCPServer.buildServer(host: "127.0.0.1", port: 8082) {
  // InboundHandler that maps NIO's IOData to ByteBuffer
  IODataInboundDecoder()
  // OutboundHandler that encodes HTTPServerResponsePart to IOData
  HTTPResponseEncoder()
  // InboundHandler that decodes ByteBuffer
  ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .dropBytes))
  // InboundHandler that maps HTTPServerRequestPart to HTTPClientRequestPart
  HTTPProxyServerRequestMapper()
  // OutboundHandler that maps HTTPClientResponsePart to HTTPServerResponsePart
  HTTPProxyServerResponseMapper()
  
  // DuplexHandler that consumes all Inbound data
  ProxyChannelHandler { baseChannel in
    // baseChannel is the client connected to TCPServer
    // 1. Connect to an SSHServer and start an SSH tunnel
    // 2. Configure that tunnel's channel
    // 3. Return the configured (typed) channel
    return try await SSHClient.connect(
        host: "orlandos.nl",
        authenticationMethod: .rsa(username: "joannis", privateKey: myPrivateKey),
        hostKeyValidator: .acceptAnything()
    ).buildTunnel(host: "example.com", port: 80) {
      // OutboundHandler that maps IOData to ByteBuffer. SSHTunnel reads/writes ByteBuffer, not IOData.
      IODataOutboundDecoder()
      // OutboundHandler that Encodes Ooutbound HTTPClientRequestPart to IOData
      HTTPRequestEncoder()
      // InboundHandler that decodes incoming ByteBuffer into HTTPClientResponsePart
      HTTPResponseDecoder(leftOverBytesStrategy: .dropBytes)
      // InboundHandler that consumes HTTPClientResponsePart, and writes it to the TCPServer's client
      OutputToChannelHandler(channel: baseChannel, payloadType: HTTPClientResponsePart.self)
    }
  }
}

GitHub

View Github