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 <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 →