Search code examples
nioswift-nio

SwiftNIO: How "expensive" is transformation in each ChannelHandler?


Checking this tutorial: https://rderik.com/blog/understanding-swiftnio-by-building-a-text-modifying-server/

One thing I do not understand that the main point using NIO directly is to increase speed of a backend service.

But, when we has this pipe:

Client: hello
      |
      v
   Server
      |
      v
BackPressureHandler (Receives a ByteBuffer - passes a ByteBuffer)
      |
      v
UpcaseHandler(Receives a ByteBuffer - passes a [CChar])
      |
      v
VowelsHandler(Receives a [CChar] - passes a ByteBuffer)
      |
      v
ColourHandler(Receives a ByteBuffer - passes a ByteBuffer)
      |
      v
Client: receives
H*LL* (In green colour)

parameter gets transformed many times. In UpcaseHandler NIOAny -> ByteBuffer -> string -> CChar -> NIOAny

then in VowelsHandler again: NIOAny -> ByteBuffer -> string -> CChar -> NIOAny

What is the advantage to have so many independent handlers?

If server receive a 'flat' JSON, is it worth to process it with with JSONEncoder, if speed, each microseconds are critical? try JSONEncoder().encode(d2)

Or is it worth, is it common to implement own JSON processor. I.e. an event driven JSON parser?


Solution

  • I think it's useful to use things like an UppercasingHandler when trying to learn and understand SwiftNIO. In the real world however, this is too fine grained for a ChannelHandler.

    Typically, the use-case for a ChannelHandler is usually one of the following (not exhaustive):

    • a whole network protocol (example NIOSSLClientHandler which adds TLS for a client connection)
    • added value that may be useful with multiple protocols (such as the BackpressureHandler)
    • added value that may be useful for debugging (example NIOWritePCAPHandler)

    So whilst the overhead of a ChannelHandler isn't huge, it is definitely not completely free and I would recommend not overusing them. Abstraction is useful but even in a SwiftNIO-based application or library we should try to express everything as ChannelHandlers in a ChannelPipeline.

    The value-add of having something in a ChannelHandler is mostly around reusability (the HTTP/1, HTTP/2, ... implementations don't need to know about TLS), testability (we can test a network protocol without actually needing a network connection) and debuggability (if something goes wrong, we can easily log the inputs/outputs of a ChannelHandler).

    The NIOWritePCAPHandler for example is a great example: In most cases, we don't need it. But if something goes wrong, we can add it in between a TLS handler and say the HTTP/2 handler(s) and we get a plaintext .pcap file without having to touch any code apart from the code that inserts it into the ChannelPipeline which can even be done dynamically after the TCP connection is already established.

    There's absolutely nothing wrong with a very short ChannelPipeline. Many great examples have just a few handlers, for example:

    TLS handler <--> network protocol handler(s) [HTTP/1.1 for example] <--> application handler (business logic)