Frame and payload codecs
Extend the wire envelope and payload conversion separately, and know which sending paths use the selected codec.
The default LilyEnvelopeCodec implements both frame and payload duties. When writing a custom codec, separate the envelope needed for route lookup from conversion of DTO content.
Selection order#
Frame: controller override → app default
Payload: action override → controller override → app defaultThere is no action-level frame codec because frame decoding must happen before the action is found. Repeating an inherited concrete codec at another level causes a duplicate-component build error. Do not redundantly add the default LilyEnvelopeCodec to a controller.
Payload codec example#
The example delegates to the existing codec; it does not add CBOR, MessagePack, or gzip support. It demonstrates the public trait and factory connection:
use std::sync::Arc;
use lilyrs::websocket::{DecodedWebSocketPayload, EncodedWebSocketPayload, Extensions,
LilyEnvelopeCodec, WebSocketCodecError, WebSocketCodecFactory,
WebSocketCodecInitError, WebSocketPayloadCodec, WsAppBuilder, async_trait};
pub struct ApplicationPayloadCodec;
#[async_trait]
impl WebSocketCodecFactory for ApplicationPayloadCodec {
async fn new(_: Arc<Extensions>) -> Result<Self, WebSocketCodecInitError> { Ok(Self) }
}
impl WebSocketPayloadCodec for ApplicationPayloadCodec {
fn decode_payload(&self, payload: EncodedWebSocketPayload)
-> Result<DecodedWebSocketPayload, WebSocketCodecError>
{ LilyEnvelopeCodec.decode_payload(payload) }
fn encode_payload(&self, payload: DecodedWebSocketPayload)
-> Result<EncodedWebSocketPayload, WebSocketCodecError>
{ LilyEnvelopeCodec.encode_payload(payload) }
}
pub fn with_codec(builder: WsAppBuilder) -> WsAppBuilder {
builder.payload_codec::<ApplicationPayloadCodec>()
}Download the complete example.
Build the result of with_codec(builder). Controller/action payload overrides use #[payload_codec(ApplicationPayloadCodec)]; controller frame overrides use #[frame_codec(MyFrameCodec)]. Select the necessary codec at only one level; do not add redundant overrides for the same type.
Constructors and synchronous work#
WebSocketCodecFactory::new(Arc<Extensions>) is async and fallible. Actual custom dependency I/O must use its own bounded policy. Since decode/encode methods are synchronous, keep them fast and non-blocking. The framework can contain panics; it cannot preempt blocking code midway.
A payload codec implements the contract between EncodedWebSocketPayload and DecodedWebSocketPayload in both directions. RawEnvelope accesses the original frame with the single consumption right; it is not a shortcut to reading data twice with two payload extractors.
Sending-path boundary#
Typed action Ack/Emit/error terminal outputs pass through the selected codecs. context.clients().send/send_binary uses the canonical WsMessageBody broadcast path. A custom action payload codec does not automatically change every broadcast.
The Rust client's inbound path also expects canonical Lily v2. When choosing a custom wire format, test client, action-response, and fan-out compatibility together. Message protocol covers the default envelope, and testing covers the required validation boundaries.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.