Expand description
Chunked calls: messages larger than one frame (issue #294).
A frame is capped (DEFAULT_FRAME_LIMIT, 16 MiB) so one peer can
never make the other buffer an unbounded line. That cap stays. A message above it travels as
several frames, and the host assembles it under a second, explicit bound
(HostOptions::max_message). Chunking is off by default
(max_message 0, issue #323): a service opts in with a size, and a host that has not refuses
rk.upload.
Request (Client::call_large): the params are serialised to
JSON text and cut at character boundaries into pieces of chunk_bytes (the host advertises it
in hello). Each piece is an ordinary request to the reserved method METHOD_UPLOAD
({id, seq, last, text}, and method on seq 0). The host appends the pieces in order (one
upload at a time per connection; a gap, a repeat or a changed id drops it), and on last parses
the assembled text and runs the real method through the normal pipeline: allowlist, handler,
events, response. The response to the final piece is the response to the call. A piece is
plain JSON text, so no encoding crate is needed; escaping at most doubles its wire size.
Response: a client that opts in per request ("chunked_ok": true, which call_large
sets) gets a result that exceeds the frame limit as {ok: true, result: null, chunked: {id, bytes, count}} instead of response_too_large; the client then reads the pieces with the
reserved method METHOD_FETCH ({id, seq} answered {seq, text}) and parses the join. The
host keeps one such result per connection (a new one replaces it; closing the connection drops
it). A client that does not opt in still gets the typed response_too_large.
Events keep the frame limit: an event larger than it is dropped with
ServiceEvent::FrameTooLarge as before.
The reserved methods are rk.upload and rk.fetch; no service method may start with rk..
They need no allowlist entry (any app that passed hello may use them), because the call they
carry is authorised on its own method id: when the first piece arrives, before a byte is
buffered (an unauthorised peer is refused there with not_allowed), and again when the call is
dispatched. The assembled text is checked for nesting and value count before it is parsed.
Constantsยง
- CHUNK_
TEXT_ BYTES - Largest piece of text in one chunk frame (1 MiB; escaping at most doubles it on the wire).
- DEFAULT_
MAX_ MESSAGE - Default for
HostOptions::max_message: 0, chunking off (it was 256 MiB earlier in the unpublished 0.2.7; see issue #323). A service that takes large calls opts in. - MAX_
ASSEMBLIES_ PER_ CONNECTION - Assemblies one connection may hold at once. A new first piece replaces the one in progress, so
the bytes a connection holds are at most one assembly, itself at most
max_message. - MAX_
JSON_ DEPTH - Deepest array/object nesting an assembled request may have (serde_json itself stops at 128).
- MAX_
JSON_ NODES - Most values (elements, members and containers) an assembled request may contain. A parsed
Valuecosts 16 to 32 times the text of a dense array, so the count, not only the byte length, bounds the memory a parse can take: 1 Mi values is about 32 to 64 MiB. - METHOD_
FETCH - Reserved method: one piece of a result too large for a frame.
- METHOD_
UPLOAD - Reserved method: one piece of a request too large for a frame.
- RESERVED_
PREFIX - Prefix no service method may use.
- SUGGESTED_
MAX_ MESSAGE - A reasonable opt-in value for a service that does take large calls: 64 MiB.
- UPLOAD_
IDLE_ TIMEOUT - An assembly that has not received a piece for this long is dropped (checked on every frame the connection sends, and the whole state goes with the connection).