Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ reduce this boilerplate through layered abstractions:
These layers correspond to the architecture outlined in the design
document【F:docs/rust-binary-router-library-design.md†L292-L344】.

## API Overview
## API overview

Applications are configured using a builder pattern similar to Actix Web. A
`WireframeApp` defines routes and middleware, while `WireframeServer` manages
Expand Down Expand Up @@ -86,7 +86,7 @@ binary protocol server. See the <!-- markdownlint-disable-next-line MD013 -->
[full example](docs/rust-binary-router-library-design.md#5-6-illustrative-api-usage-examples)
in the design document for further details.

## Custom Envelopes
## Custom envelopes

`WireframeApp` defaults to a simple `Envelope` containing a message ID and raw
payload bytes. Applications can supply their own envelope type by calling
Expand Down Expand Up @@ -126,22 +126,21 @@ Use `None` rather than `Some(0)` when a frame lacks a correlation ID. See
This allows integration with existing packet formats without modifying
`handle_frame`.

## Response Serialization and Framing
## Response serialization and framing

Handlers can return types implementing the `Responder` trait. These values are
encoded using the application's configured serializer and written back through
the `FrameProcessor`【F:docs/rust-binary-router-library-design.md†L724-L730】.
encoded using the application's configured serializer and framed by a
length‑delimited codec【F:docs/rust-binary-router-library-design.md†L724-L730】.

The included `LengthPrefixedProcessor` illustrates a simple framing strategy
that prefixes each frame with its length. The format is configurable (prefix
size and endianness) and defaults to a 4‑byte big‑endian length
prefix【F:docs/rust-binary-router-library-design.md†L1082-L1123】.
Frames are length prefixed using `tokio_util::codec::LengthDelimitedCodec`. The
prefix length and byte order are configurable and default to a 4‑byte
big‑endian header【F:docs/rust-binary-router-library-design.md†L1082-L1123】.

```rust
let app = WireframeApp::new()?;
```

## Connection Lifecycle
## Connection lifecycle

Protocol callbacks are consolidated under the `WireframeProtocol` trait,
replacing the individual `on_connection_setup`/`on_connection_teardown`
Expand Down Expand Up @@ -195,7 +194,7 @@ impl WireframeProtocol for MySqlProtocolImpl {
let app = WireframeApp::new().with_protocol(MySqlProtocolImpl);
```

## Session Registry
## Session registry

The \[`SessionRegistry`\] stores weak references to \[`PushHandle`\]s for
active connections. Background tasks can look up a handle by \[`ConnectionId`\]
Expand All @@ -221,7 +220,7 @@ fn on_connection_setup(&self, handle: PushHandle<MyFrame>, _ctx: &mut Connection
}
```

## Custom Extractors
## Custom extractors

Extractors are types that implement `FromMessageRequest`. When a handler lists
an extractor as a parameter, `wireframe` automatically constructs it using the
Expand Down Expand Up @@ -312,7 +311,7 @@ $ cargo run --example ping_pong
$ printf '\x00\x00\x00\x08\x01\x00\x00\x00\x2a\x00\x00\x00' | nc 127.0.0.1 7878 | xxd
```

## Current Limitations
## Current limitations

Connection handling now processes frames and routes messages. Although the
server is still experimental, it now compiles in release mode for evaluation or
Expand Down
58 changes: 51 additions & 7 deletions docs/asynchronous-outbound-messaging-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ struct PushHandleInner<F> {

// The public, cloneable handle.
#[derive(Clone)]
pub struct PushHandle<F>(Arc<PushHandleInner<F>>);
pub struct PushHandle<F>(Arc<PushHandleInner<F>>);

pub enum PushPolicy {
ReturnErrorIfFull,
Expand All @@ -317,8 +317,39 @@ impl<F: FrameLike> PushHandle<F> {
frame: F,
priority: PushPriority,
policy: PushPolicy,
) -> Result<(), PushError>;
}
) -> Result<(), PushError>;
}
```

The example below demonstrates pushing frames and returning a streamed
response. The [`async-stream`](https://docs.rs/async-stream) crate is the
canonical way to build dynamic `Response::Stream` values.

```rust,no_run
use async_stream::try_stream;
use std::sync::Arc;
use wireframe::{app::{Envelope, WireframeApp}, Response};

#[tokio::main]
async fn main() -> std::io::Result<()> {
let app = WireframeApp::new()?
.route(1, Arc::new(|_: &Envelope| Box::pin(async {
Response::Stream(Box::pin(try_stream! {
yield b"ack".to_vec();
}))
})))?;

let (push, mut conn) = wireframe_testing::connect(app).await?;
tokio::spawn(async move {
push.push_high_priority(b"urgent".to_vec()).await.unwrap();
push.push_low_priority(b"stats".to_vec()).await.unwrap();
});

while let Some(frame) = conn.next().await {
println!("{:?}", frame);
}
Ok(())
}
```

```mermaid
Expand Down Expand Up @@ -707,10 +738,23 @@ features of the 1.0 release.
that urgent pushes can interrupt a long-running data stream.

- **Message Fragmentation:** Pushes occur at the *logical frame* level. The
`FragmentAdapter` will operate at a lower layer in the `FrameProcessor`
stack, transparently splitting any large pushed frames before they are
written to the socket. The `PushHandle` and the application code that uses it
remain completely unaware of fragmentation.
`FragmentAdapter` will operate at a lower layer in the codec stack,
transparently splitting any large pushed frames before they are written to
the socket. The `PushHandle` and the application code that uses it remain
completely unaware of fragmentation.
Comment thread
leynos marked this conversation as resolved.

```rust
// Codec stack with explicit frame-size limits and fragmentation.
use tokio_util::codec::LengthDelimitedCodec;
const MAX_FRAME: usize = 64 * 1024;
let codec = LengthDelimitedCodec::builder()
.max_frame_length(MAX_FRAME) // 64 KiB cap to prevent OOM
.new_codec();

// Wrap the length-delimited frames with the fragmentation adapter.
// Pseudocode pending actual adapter API naming:
// let codec = FragmentAdapter::new(FragmentConfig::default()).wrap(codec);
```

## 7. Use Cases

Expand Down
143 changes: 0 additions & 143 deletions docs/efficiency-report.md

This file was deleted.

31 changes: 20 additions & 11 deletions docs/frame-metadata.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
# Parsing Frame Metadata
# Parsing frame metadata

`FrameMetadata` allows a protocol to inspect frame headers before decoding the
entire payload. This can be useful for routing decisions when the message type
is encoded in a header field.

Implement the trait for your serializer or decoder that knows how to read the
Implement the trait for your serialiser or decoder that knows how to read the
header bytes. Only the minimal header portion should be read, returning the
full frame and the number of bytes consumed from the input.
frame envelope and the number of bytes consumed from the input.

```rust
use wireframe::frame::{FrameMetadata, FrameProcessor};
use wireframe::frame::FrameMetadata;
use wireframe::app::Envelope;
use bytes::BytesMut;
use tokio_util::codec::{Decoder, Encoder};
use bytes::{Bytes, BytesMut};

struct MyCodec;

impl FrameProcessor for MyCodec {
type Frame = Vec<u8>;
// Example only: implement the minimal Decoder/Encoder surface for docs.
impl Decoder for MyCodec {
// Using BytesMut here mirrors LengthDelimitedCodec’s default Item.
type Item = BytesMut;
type Error = std::io::Error;

fn decode(&self, src: &mut BytesMut) -> Result<Option<Self::Frame>, Self::Error> {
fn decode(
&mut self,
_src: &mut BytesMut,
) -> Result<Option<Self::Item>, Self::Error> {
todo!()
}
}

impl Encoder<Bytes> for MyCodec {
type Error = std::io::Error;

fn encode(&self, frame: &Self::Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {
fn encode(&mut self, _item: Bytes, _dst: &mut BytesMut) -> Result<(), Self::Error> {
todo!()
}
}
Expand All @@ -39,5 +48,5 @@ impl FrameMetadata for MyCodec {
}
```

In `WireframeApp` the metadata parser is used before deserialisation so routes
In `WireframeApp` the metadata parser is used before deserialization so routes
can be selected as soon as the header is available.
Loading
Loading