4.1 The file service: streamed reads and writes
file is a streamed file store. It implements the store pattern (3.3), so its
verbs and listings are exactly the contract you already know — this section
covers what it adds on top.
Mounting it
{ "path": "/files", "service": "file",
"config": { "access": "open" } }
That's all the service needs. (access is explicit because mounts fail closed.)
Storage is host-scoped per tenant: each tenant writes under its own prefix of
the node's fileRoot, with no way to reach another tenant's files.
Writing streams end-to-end
A PUT streams the request body straight to storage — nothing is buffered in
full — and the write is atomic (a temp file is written, then renamed), so a
reader never sees a half-written file.
curl.exe -X PUT http://127.0.0.1:3100/files/img/logo.png `
-H "Content-Type: image/png" --data-binary "@logo.png"
You get 201/200, an empty body, and an ETag. Reads stream back the same
way, so large files cost little memory in either direction.
Media types come from the extension
The response Content-Type is derived from the file's extension via a fixed
map — content is never sniffed. An unknown extension is served as
application/octet-stream. This is predictable by design: what you name a file
determines how it's served.
Keyless POST names by content type
A POST to a container (keyless create) names the new file
<uuid><ext>, inferring the extension from the request Content-Type:
POST /files/uploads/ Content-Type: image/png
→ 201 Location: /files/uploads/3f9a…d2.png
Use this when you want the server to assign a unique name (uploads), and PUT
when you control the key.
HEAD
HEAD <child> returns the metadata headers (Content-Type, ETag,
Last-Modified, length) without the body — handy for existence and version
checks.
MOVE — rename within the store
The MOVE method renames a file, declared as the move facet (3.4). The target
goes in a Destination header, addressed like any path within the same mount:
MOVE /files/uploads/3f9a.png Destination: /files/img/logo.png
→ 201 (created) / 200 (overwrote)
Constraints: the source must be a file (not a directory), the destination must be a file path, and cross-mount moves aren't supported — the destination is mapped into this mount's store space.
A note on deployed code
Custom-service bundles live in the same tenant file store under
.rs2-code/<name>/<version>.{wasm,js}. If you mount file at the tenant root
you'll see that prefix in listings. It's harmless — but don't delete it casually;
it's the instruction plane (3.6).
The next sections cover the store extensions file adds: byte ranges and
conditional requests (4.2), then static-site mode (4.3).
← Part 3 — Core Concepts · Manual home · Next: 4.2 Ranges, ETags, conditional GETs →