Extension Guide
This chapter shows the smallest path for adding codecs, handlers, and examples.
Add A TCP Codec
Implement Decoder and Encoder<T>. If decode output and encode input are different, implement Decoder and Encoder<T> on the same type, then use an outbound stage to convert application responses into T.
#![allow(unused)]
fn main() {
use bytes::{Buf, BufMut, Bytes, BytesMut};
use rs_netty::{codec::{Decoder, Encoder}, Error, Result};
struct LengthCodec;
impl Decoder for LengthCodec {
type Item = Bytes;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>> {
if src.len() < 4 {
return Ok(None);
}
let len = u32::from_be_bytes([src[0], src[1], src[2], src[3]]) as usize;
if src.len() < 4 + len {
return Ok(None);
}
src.advance(4);
Ok(Some(src.split_to(len).freeze()))
}
}
impl Encoder<Bytes> for LengthCodec {
fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<()> {
let len = u32::try_from(item.len()).map_err(|err| Error::Encode(err.to_string()))?;
dst.put_u32(len);
dst.extend_from_slice(&item);
Ok(())
}
}
}
The benchmark LengthCodec in this repository uses a more reusable approach: it composes LengthFieldBasedFrameDecoder and LengthFieldPrepender internally.
Add A UDP Codec
Implement DatagramDecoder and DatagramEncoder<T>. Each decode input is one datagram payload:
#![allow(unused)]
fn main() {
use bytes::{Bytes, BytesMut};
use rs_netty::{codec::{DatagramDecoder, DatagramEncoder}, Result};
struct RawDatagram;
impl DatagramDecoder for RawDatagram {
type Item = Bytes;
fn decode_datagram(&mut self, src: &[u8]) -> Result<Self::Item> {
Ok(Bytes::copy_from_slice(src))
}
}
impl DatagramEncoder<Bytes> for RawDatagram {
fn encode_datagram(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<()> {
dst.extend_from_slice(&item);
Ok(())
}
}
}
This is very close to the built-in BytesDatagramCodec.
Add An Inbound Handler
Implement Inbound<I> and return Flow<Out>:
#![allow(unused)]
fn main() {
struct ParseRequest;
impl rs_netty::Inbound<String> for ParseRequest {
type Out = Request;
async fn read(
&mut self,
_ctx: &mut rs_netty::InboundContext,
msg: String,
) -> rs_netty::Result<rs_netty::Flow<Self::Out>> {
Ok(rs_netty::Flow::Next(Request { body: msg }))
}
}
}
Return Flow::Stop when you want to filter a message instead of forwarding it.
Add An Outbound Handler
Implement Outbound<I> to convert an application response type into the next outbound type:
#![allow(unused)]
fn main() {
struct RenderResponse;
impl rs_netty::Outbound<Response> for RenderResponse {
type Out = String;
async fn write(
&mut self,
_ctx: &mut rs_netty::OutboundContext,
msg: Response,
) -> rs_netty::Result<rs_netty::Flow<Self::Out>> {
Ok(rs_netty::Flow::Next(msg.body))
}
}
}
Place it after the final handler:
#![allow(unused)]
fn main() {
pipeline()
.codec(LineCodec::new())
.inbound(ParseRequest)
.handler(Router)
.outbound(RenderResponse);
}
Add A Complete Example
Recommended steps:
- Add a small complete
.rsfile underexamples/. - Add a
[[example]]entry to the rootCargo.toml, withrequired-featuresif needed. - Prefer existing codecs and public traits; avoid relying on
pub(crate)runtime details. - Use
pipeline()for TCP anddatagram_pipeline()for UDP. - Use
#[handler]for simple handlers; write a manual impl when you need manual flushes, multiple writes, or connection close. - Add a trybuild pass case to protect the public API shape.
- Run
cargo testand relevant feature tests.
For a complete TCP typed chain, see examples/tcp_typed_chain.rs. For a complete UDP typed chain, see examples/udp_typed_chain.rs.