Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Add a small complete .rs file under examples/.
  2. Add a [[example]] entry to the root Cargo.toml, with required-features if needed.
  3. Prefer existing codecs and public traits; avoid relying on pub(crate) runtime details.
  4. Use pipeline() for TCP and datagram_pipeline() for UDP.
  5. Use #[handler] for simple handlers; write a manual impl when you need manual flushes, multiple writes, or connection close.
  6. Add a trybuild pass case to protect the public API shape.
  7. Run cargo test and 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.