Skip to content

Public key-addressed web seam: fetchWebKey(key, request) #162

Description

@gmoon

Proposal

Promote the internal key-addressed path to the public web API:

// Caller names the S3 key; the Request contributes method (GET/HEAD) and
// conditional/Range headers. The request URL plays no role in key selection.
fetchWebKey(key: string, request: Request): Promise<Response>

fetchWeb then becomes a thin adapter over it:

fetchWeb(request) {
  return this.fetchWebKey(keyFromUrl(request), request);
}

No new behavior — this publishes a seam that already exists internally (fetchKey is private) and sharpens the v4 layering: URL→key mapping is adapter business; the core primitive is "stream this key with these request semantics."

Motivation

Today the public web API derives the key exclusively from the request URL path. Any consumer whose URL→key mapping is not identity has to fabricate a synthetic Request to smuggle the key through the URL:

// e.g. an app mounting a bucket under /files/*
const key = url.pathname.replace(/^\/files\//, '');  // derive key
url.pathname = `/${key}`;                             // re-embed in a URL
return proxy.fetchWeb(new Request(url, c.req.raw));    // s3proxy re-parses key from URL

The key round-trips key → URL-encode → Request → URL-parse → key. Three costs, ascending:

  1. Waste — a synthetic URL + Request per request, and a duplicated parse.
  2. Split ownership — "how a path becomes a key" lives in two places that must agree.
  3. Encoding hazard (the real one) — the key is smuggled through URL encoding/decoding. For keys containing %, +, spaces, or the specialCharacters!... fixture already in the test bucket, the consumer's encoder and s3proxy's decoder must agree exactly, or the wrong object is served / a valid key 404s. Identity mapping never hits this; every rewriting consumer does.

Consumer classes that need non-identity mapping:

  • Sub-path mounts — serving a bucket under /files/*, /assets/* inside an existing app
  • Multi-tenant prefix injection — request identity resolves to a server-side key prefix (tenants/<org>/…) the client never sees; a security property, since the client shouldn't dictate the raw key
  • Key rewriting — default documents, versioned keys, vanity paths

Design notes

  • Alternative shape: an options override on the existing method — fetchWeb(request, { key }). Smaller surface; the separate method is arguably more honest (URL ignored vs. URL authoritative), maintainer's call.
  • Contract to document: key is the raw S3 key (not URL-encoded); method must be GET/HEAD; Range/If-* headers flow through unchanged.
  • Conformance case: fetchWebKey(weirdKey, req) must behave identically to a correctly-encoded fetchWeb for the special-characters fixture.

Estimated scope: ~10 lines plus tests. The cost is permanent public API surface on a deliberately minimal package — hence framing as promoting the existing internal seam, not adding a feature.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions