Accumulating HTTP request body parts: is storing `ByteBuffer`s a good idea?

suppose i have an HTTP listener that accumulates a [ByteBuffer] array:

class Listener:ChannelInboundHandler, RemovableChannelHandler
    typealias InboundIn = HTTPServerRequestPart
    typealias OutboundOut = HTTPServerResponsePart
    var request:(head:HTTPRequestHead, stream:[ByteBuffer])?
    func channelRead(context:ChannelHandlerContext, data:NIOAny) 
        switch self.unwrapInboundIn(data) 
        case .body(let buffer):
            if case (let head, var body)? = self.request 
                self.request = nil 
                self.request = (head, body)

but then it occurred to me that SwiftNIO might be reusing the same ByteBuffer for multiple HTTP request fragments, and that holding onto copies of them might trigger copy-on-write. what is the best way to receive an HTTP payload, assuming we are waiting to encounter the end segment before using the payload?

I'll let someone else pipe in about the low level details, but it may be useful to look at swift-nio-extras which has a number of classes that follow this pattern, like FixedLengthFrameDecoder (and indeed some useful types that may be directly usable for your use case). I'd imagine following the same patterns, or using the provided types directly would yield reasonably good performance.

If they normally reuse the buffer, they'll have to allocate a new buffer instead. If they don't normally reuse the buffer, they'll have to allocate a new buffer anyway. So we're talking about saving a memcpy or two depending on the order of operations, which isn't nothing but which probably doesn't matter until a benchmark says it does.

1 Like

This pattern is just fine. When you're a long way down a pipeline it's very hard to know whether a ByteBuffer is going to be re-used. NIO will opportunistically attempt to re-use a ByteBuffer for a subsequent read on a socket, but we can't guarantee it'll work, so as a practical matter you're totally fine to buffer in this way.