[wrangler][miniflare] Serve local R2 bucket objects publicly via /cdn-cgi/local/r2/public#14119
Conversation
🦋 Changeset detectedLatest commit: 59d440f The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Codeowners approval required for this PR:
Show detailed file reviewers
|
5de38c1 to
3bd48b7
Compare
|
Thank you for this PR @tahmid-23. |
|
@NuroDev makes sense. |
Feel free to implement that yourself here, and if we can help with anything let us know 😄 |
3bd48b7 to
12cdc58
Compare
|
Codeowners approval required for this PR:
Show detailed file reviewers
|
cea5c3f to
eca9313
Compare
Accept and validate the experimental_local_public boolean on R2 bucket bindings in wrangler config. Gated as experimental. No runtime behavior attached yet — wiring lands in the next commit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the use of the shared `namespaceEntries` helper in the R2 plugin with a local `r2BucketEntries` typed against `R2OptionsSchema`. No behavior change — the new helper produces the same shape — but a local helper lets future R2-specific fields be threaded through without expanding the shared helper used by other namespace-style plugins. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-cgi/mf/r2 Exposes opted-in local R2 buckets at <dev-server>/cdn-cgi/mf/r2/<binding>/<key> on the existing user-facing dev server, dispatched through the entry worker (same pattern as the stream binding). Subject to the existing /cdn-cgi/* Host/Origin allowlist. Enabled per-binding via experimental_local_public: true on R2 bindings in wrangler config. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
eca9313 to
85bbe76
Compare
|
@NuroDev done |
Per review feedback: improves readability when skimming the function. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per review feedback: now that the route lives under /cdn-cgi/..., it can't collide with user routes, so the opt-in flag is no longer needed. Every local R2 binding is exposed by default (remote-proxied bindings remain excluded). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per review feedback: /cdn-cgi/mf/... is reserved for Miniflare internals. /cdn-cgi/local/r2/public leaves /cdn-cgi/local/r2/... open for future local R2-related endpoints. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per review feedback: matches the local-explorer worker's style and makes routing easier to maintain. Behavior is unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prod R2 includes Last-Modified (sourced from the object's upload time) on public-bucket responses. R2Object.writeHttpMetadata() does not emit it, so set it manually from object.uploaded. Without it, clients cannot construct correct If-Modified-Since conditional requests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
"public" is a more appropriate name for the worker than "local", since Miniflare is inherently local. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@NuroDev I also added a missing header that the R2 URL sends and renamed the worker to public.worker.ts. |
The R2 head operation cannot evaluate conditional headers (the wire request only carries the object key), so HEAD requests previously ignored If-None-Match/If-Match/If-Modified-Since/If-Unmodified-Since and always returned 200. Prod public buckets return 304/412 for HEAD just like GET. Route HEAD through bucket.get() with onlyIf and discard the body. Conditional tests are now parameterized over both methods. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Good point @tahmid-23! Yes let's add some CORS headers to allow all origins 👍 |
The new r2:public service previously copied the 2023-07-24 date from the neighboring bucket object worker for parity, but new embedded workers should use a recent compatibility date (matching the local explorer worker). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Serves cross-origin requests from frontend dev servers running on other localhost ports (the entry worker already rejects non-localhost origins for /cdn-cgi/* paths before they reach this worker). Preflight OPTIONS requests are answered with 204, and all response headers are exposed so clients can read ETag, Content-Range, etc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
done @NuroDev |
The local helper existed only to thread experimentalLocalPublic through the bucket entries. Now that the flag is gone, it is identical to the shared namespaceEntries helper, so use that again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codeowners approval required for this PR:
Show detailed file reviewers |
…blic routes Previously a failed conditional GET/HEAD returned 412 whenever the request carried an If-Match or If-Unmodified-Since header, even when that header's condition passed and the failure actually came from If-None-Match or If-Modified-Since — which RFC 9110 §13.2.2 defines as a 304. `bucket.get()` reports a failed conditional only as a body-less result, without naming the failed header. On failure, the worker now re-runs the lookup with just the If-Match/If-Unmodified-Since pair: if those preconditions fail on their own the response is 412, otherwise the failure came from a cache validator and the response is 304. Reusing the bucket's conditional evaluation keeps etag parsing and date granularity consistent rather than reimplementing them in the worker, and successful requests still cost a single `get()`. Adds tests covering the RFC 9110 §13.2.2 decision table, including both within-family skip rules and precondition precedence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
199dd3d to
59d440f
Compare
NuroDev
left a comment
There was a problem hiding this comment.
Overall LGTM! Thanks @tahmid-23!
Giving CI a run now to make sure all tests, formatting, linting, etc looks good
workers-devprod
left a comment
There was a problem hiding this comment.
Codeowners reviews satisfied
create-cloudflare
@cloudflare/deploy-helpers
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-auth
@cloudflare/workers-editor-shared
@cloudflare/workers-utils
wrangler
@cloudflare/wrangler-bundler
commit: |
This implements the idea discussed in #13325.
R2 exposes a public endpoint for accessing buckets remotely, but no such analogue exists in local development.
This adds a configuration parameter,
experimental_local_port, that sets up a socket for a given R2 binding. This is sent to a custom worker, which translates aGET/HEADrequest into an R2 access. The worker provides full support for range and condition headers.As a brief aside, I'd like to implement S3-compatible pre-signed uploads in the newly created worker in a follow-up PR.
A picture of a cute animal (not mandatory, but encouraged)