Expand description
The RESP2 and RESP3 codec.
Requests come in as ranges into the connection’s own read buffer, and replies go out as the bytes that go on the socket. Nothing in between is materialised, because everything in between is what the lineage’s profiles kept finding at the top.
§Reading
Argv decodes commands. It is per connection, it remembers where it got
to when a command arrives in pieces, and after the first few commands it
stops allocating. Multibulk and inline requests both land in the same place,
so the command layer never learns which one a client used.
use yo_resp::{Argv, Limits, Step};
let buf = b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$1\r\nv\r\n";
let mut argv = Argv::new();
match argv.decode(buf, &Limits::default())? {
Step::Command { consumed } => {
assert_eq!(consumed, buf.len());
assert_eq!(argv.arg(buf, 0), Some(&b"SET"[..]));
assert_eq!(argv.arg(buf, 2), Some(&b"v"[..]));
}
Step::Incomplete => unreachable!("the whole command is here"),
}§Writing
Out is the reply buffer, and it knows which protocol the connection is
speaking. A command writes the richer form once and the RESP2 downgrade
happens here rather than in the command:
use yo_resp::{Out, Proto};
fn hgetall(out: &mut Out) {
out.map(1);
out.bulk(b"field");
out.bulk(b"value");
}
let mut two = Out::new(Proto::Resp2);
hgetall(&mut two);
assert_eq!(two.as_slice(), b"*2\r\n$5\r\nfield\r\n$5\r\nvalue\r\n");
let mut three = Out::new(Proto::Resp3);
hgetall(&mut three);
assert_eq!(three.as_slice(), b"%1\r\n$5\r\nfield\r\n$5\r\nvalue\r\n");§Reading replies
frame decodes a reply into a borrowed Frame. The server has no use
for it. The replication client, the differential harness and this crate’s
own round trip tests do.
§Running a command
dispatch is the layer above both halves. It looks a command name up,
checks its arity, and calls the same yo-kv method the embedded API calls,
which is the placement rule Y23 is about: one implementation of INCR, two
ways to reach it.
use yo_resp::{Argv, Limits, Out, Proto};
use yo_resp::dispatch::{Args, Server, Session, execute};
let mut server = Server::new();
let mut session = Session::new(1);
let mut out = Out::new(Proto::Resp2);
let wire = b"*1\r\n$4\r\nPING\r\n";
let mut argv = Argv::new();
argv.decode(wire, &Limits::default())?;
execute(&mut server, &mut session, Args::new(&argv, wire), &mut out);
assert_eq!(out.as_slice(), b"+PONG\r\n");§Driving it from the loop
engine is the piece between the two: connections, read buffers, framing
and one write per connection per batch, put on yo_reactor::Engine so the
loop can run commands without knowing what a command is. It is where a
server becomes possible, and it works over anything that implements
engine::Sink, which is a socket in production and a Vec in a test.
use yo_reactor::Reactor;
use yo_resp::engine::{Recorder, Wire, pump};
let mut r = Reactor::inline(Wire::new(Recorder::new()));
let conn = r.engine_mut().accept();
r.engine_mut().feed(conn, b"*1\r\n$4\r\nPING\r\n");
pump(&mut r, &mut Vec::new());
assert_eq!(r.engine().sink().sent(conn), b"+PONG\r\n");§What is not here
Sockets. This crate turns bytes into arguments, runs them, turns values into
bytes, and says which connection they belong to. Reading and writing the
bytes themselves is the ring’s job, and 04 section 7 owns the ring.
Re-exports§
pub use engine::Cmd;pub use engine::ConnId;pub use engine::Sink;pub use engine::Wire;pub use error::ProtocolError;pub use frame::Frame;pub use proto::Limits;pub use proto::Proto;pub use reply::Out;pub use request::Argv;pub use request::Step;
Modules§
- dispatch
- From a decoded command to a written reply.
- engine
- Connections, framing and buffers: the seam between the loop and the commands.
- error
- What the codec refuses, and the exact words it refuses it in.
- frame
- Replies in: the general decoder, for the side of the wire that reads them.
- num
- Redis’s own integer and double text, shared with the string type.
- proto
- The protocol version a connection is speaking, and the limits it is held to.
- reply
- Replies out: wire bytes, written once.
- request
- Requests in: the multibulk decoder and the inline decoder.