That's great to hear! I searched deeper earlier today and found this json rpc client demo, and figured out how to implement what I want for now, although I'm really not familiar with NIO, but here's what I got:
I just need to make my handler implement not only ChannelInboundHandler but also ChannelOutboundHandler, thus I can customize my writing data, aka pass write data to writeAndFlush with a newly created promise:
let promise = channel.eventLoop.makePromise(of: ByteBuffer.self)
try await channel.writeAndFlush(RequestWrapper(request: command, promise: promise))
Now in my handler I can unwrap the wrapper and get the promise, which can be stored and wait for use when channelReadComplete or whenever I like:
public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
let wrapper = unwrapOutboundIn(data)
queue.append(wrapper)
let buffer = context.channel.allocator.buffer(bytes: wrapper.request.magic)
context.writeAndFlush(wrapOutboundOut(buffer), promise: promise)
}
The function above lives in my handler, it can "intercept" data I'm about to send, which is a wrapper with a promise, the function get the promise and store it for future use, in my case is channelRead.
However, my approach introduce another problem:
NIO reads data in an event loop, that means it calls channelReadComplete every time a loop ends. But in my case I have some requests that replies with data larger than which the loop can read in one loop. In this case I can't know when the handler finishes reading the whole reply data.
I know I can solve this by defining a data protocol, like first 4 bytes are data length.But I just wonder if there're other ways to achieve this, elegantly. A callback function for handler protocols triggered when there's no more data to read, for example.