Extension Guide
这一章给出新增 codec、handler 和 example 的最小路径。
Add A TCP Codec
实现 Decoder 和 Encoder<T>。如果 decode 输出和 encode 输入不同,也可以实现 Decoder 在一个类型上、Encoder<T> 在同一个类型上,并通过 outbound stage 把应用响应转成 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(())
}
}
}
仓库 benchmark 中的 LengthCodec 使用更复用的方式:内部组合 LengthFieldBasedFrameDecoder 和 LengthFieldPrepender。
Add A UDP Codec
实现 DatagramDecoder 和 DatagramEncoder<T>。每次 decode 输入就是一个 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(())
}
}
}
这和内置 BytesDatagramCodec 非常接近。
Add An Inbound Handler
实现 Inbound<I>,返回 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 }))
}
}
}
如果想过滤消息而不是继续传递,返回 Flow::Stop。
Add An Outbound Handler
实现 Outbound<I>,把应用响应类型转换为下游类型:
#![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))
}
}
}
在 pipeline 中放到 final handler 后面:
#![allow(unused)]
fn main() {
pipeline()
.codec(LineCodec::new())
.inbound(ParseRequest)
.handler(Router)
.outbound(RenderResponse);
}
Add A Complete Example
建议步骤:
- 在
examples/下新增一个小而完整的.rs文件。 - 在根
Cargo.toml增加[[example]]条目,按需设置required-features。 - example 中优先使用现有 codec 和公开 traits,避免依赖
pub(crate)runtime。 - 如果是 TCP,使用
pipeline();如果是 UDP,使用datagram_pipeline()。 - 如果 handler 简单,使用
#[handler];如果要手动 flush、多次写或关闭连接,手写 impl。 - 加一个 trybuild pass 用例,确保 public API 编译形状不回退。
- 运行
cargo test和相关 feature test。
完整 TCP typed chain 可以参考 examples/tcp_typed_chain.rs;完整 UDP typed chain 可以参考 examples/udp_typed_chain.rs。