A streaming HTTP shim that translates browser-issued S3 POST Object uploads into SigV4-signed PUT requests against Cloudflare R2.
The full protocol spec lives in spec.md.
Built in Gleam on BEAM/OTP.
File bytes flow from the browser through mist.stream, the multipart parser, and ssl:send to R2 with no disk hop and no in-RAM accumulation past one chunk in flight (~64 KiB).
All configuration is loaded from environment variables at startup. The process exits with a precise error if any required variable is missing.
| Variable | Required | Default | Description |
|---|---|---|---|
SHIM_ACCESS_KEY_ID |
✓ | The "access key" the application uses against the shim. Not a real AWS key. | |
SHIM_SECRET_ACCESS_KEY |
✓ | Shared secret used to verify POST policy signatures. | |
SHIM_REGION |
✓ | Region string used in SigV4 verification (typically auto). |
|
R2_ACCOUNT_ID |
✓ | Cloudflare account ID. | |
R2_ACCESS_KEY_ID |
✓ | R2 API token key ID. | |
R2_SECRET_ACCESS_KEY |
✓ | R2 API token secret. | |
R2_BUCKET |
✓ | Target R2 bucket name. | |
ALLOWED_ORIGINS |
✓ | Comma-separated list of allowed CORS origins. | |
MAX_UPLOAD_BYTES |
26214400 |
Maximum file size in bytes (25 MiB default). | |
SHIM_BASE_HOST |
(empty) | Bare hostname (e.g. s3-shim.example.com). Enables virtual-host routing. |
|
BIND_INTERFACE |
0.0.0.0 |
Interface to bind on. | |
PORT |
4000 |
TCP port. |
gleam run # foreground, with env vars set in the shell
gleam test # 98 unit + integration + e2e testsdocker build -t repost .
docker run --rm -p 4000:4000 \
-e SHIM_ACCESS_KEY_ID=shim-app-key \
-e SHIM_SECRET_ACCESS_KEY=... \
-e SHIM_REGION=auto \
-e R2_ACCOUNT_ID=... \
-e R2_ACCESS_KEY_ID=... \
-e R2_SECRET_ACCESS_KEY=... \
-e R2_BUCKET=outline-uploads \
-e ALLOWED_ORIGINS=https://outline.example.com \
repostrepost/streaming_handleris the mist entry point: it routes the request, dispatches uploads to the pump, and forcesConnection: closeon every error so mid-stream aborts (spec §10.2.3) are observable on the wire.repost/streaming/pumpruns the multipart event loop, accumulates text fields, validates them against the POST policy, opens the R2 connection, and forwards file chunks one at a time.repost/multipart_streamis an incremental multipart parser built ongleam_http's continuations.repost/r2_stream(with therepost_stream_ffi.erlFFI) is a chunked HTTP/1.1 PUT client overgen_tcp/ssl.repost/sigv4derives signing keys, verifies the browser's POST policy signature, and signs the outgoing PUT.repost/pipeline,repost/policy,repost/validatorcover the spec §7 + §10 validation pipeline.repost/routerextracts the bucket from path-style or virtual-host requests.repost/corsandrepost/errorscover CORS allow-list logic and S3-style XML error responses.
gleam testSuite includes:
- SigV4 vectors cross-validated against an independent Python
hmacreference (signing-key derivation, POST-policy signature, PUT signature). validatorover every §10.5 condition shape (eq, starts-with, content-length-range, coverage check, exempt list).multipart_streamproves chunks of the file part arrive incrementally even when the parser is fed 17-byte transport chunks.r2_streamruns against a live mist server playing the role of R2, exercisinggen_tcp/sslsend + chunked response decoding.streaming_e2e_testboots the shim and a fake R2, sends a real chunked HTTP/1.1 multipart POST, and verifies bytes round-trip plus mid-streamcontent-length-rangeabort behaviour (spec §10.2.3).
Apache-2.0.