Request Bodies
Bodies that stream through your feature set cost nothing. Bodies you actually read become values — and values have a size limit.
Streams don't have a size. Values do.
A route that only moves its request body never builds it in memory.
Write it to a file, Send it on, Return
it, iterate it with for each — the body's size is bounded by the
sink, and no limit applies. A four-gigabyte upload is fine:
(uploadDocument: Files API) {
Extract the <upload> from the <request: body>. (* binds — reads nothing *)
Write the <upload> to the <file: "./uploads/doc.bin">. (* streams *)
Return a <Created: status> with <upload>.
}
A route that reads its body turns it into a value, and that is what
is bounded. A field access, a Compute, a Store, a
Log, or a when guard all count as reading:
(createNote: Files API) {
Extract the <body> from the <request: body>.
Extract the <title> from the <body: title>. (* reads — now it is a value *)
Store the <note> into the <note-repository> with <body>.
Return a <Created: status> with <title>.
}
The limit
The default is 1 MB. A route raises or lowers it in the contract, and the route's own declaration always wins:
paths:
/notes:
post:
operationId: createNote
x-aro-max-body: 256KB
Change the default for routes that declare nothing, from ARO or the environment:
Configure the <http-server: max-body> with "1MB".
ARO_MAX_BODY=512KB aro run ./MyApp
A body over the limit is answered 413 before it is read —
from Content-Length where the client declared one, incrementally
otherwise.
aro check tells you which kind each route is
Which of the two a route is gets computed from the source before the server binds a port, so you find out at check time rather than in production:
$ aro check ./FilesAPI
Request bodies:
streams POST /docs — no limit applies, nothing is buffered
note: x-aro-max-body is unused here; this route never builds the body
holds POST /notes — up to 256KB in memory (Extract the <title> from the <body: title>, line 14)
Anything the analysis cannot see through — a plugin action, an unknown verb — counts as reading. Erring toward the bounded answer is the safe direction.
One arrival, one consumption
A body arrives once and can be consumed once. Emit or
Publish of a body anchors it: it is drained to a
temporary file a chunk at a time, so handlers that outlive the request can each
read it, and the file is deleted when the last reference goes.
Folding qualifiers consume the body chunk by chunk, so hashing a 4 GB upload costs a chunk of memory too — and it hashes the upload's bytes, not a rendering of its parsed form:
Extract the <upload> from the <request: body>.
Compute the <digest: sha256> from <upload>.
Compute the <size: length> from <upload>.
aro run and aro build agree
The analysis is baked into the binary at build time, so a compiled server enforces the same limits and streams the same bodies as the interpreter.
Learn More
The full specification is
ARO-0090:
Streaming I/O and Materialization Limits, and
Examples/FileUpload is a runnable application. For how the
same machinery pipelines collections, see
Streaming Execution and
Concurrency.