carlos / platform Public

carlos-static-redirects

What

Static sites (--kind static) can ship a _redirects file at the top of the site directory. It uses the Netlify / Cloudflare Pages format: one source destination [status] rule per line.

  • Sources: a path, or an http(s)://host/... URL that matches only that host (for www → apex). :name captures one segment. A final * captures the rest of the path as :splat and keeps a trailing slash.
  • Statuses: 301 (the default), 302, 303, 307 and 308 redirect. 200 serves another file from the same tree at the requested URL (SPA fallback). 404 serves a file as the not-found page.
  • Precedence: a file at the requested path wins, unless the status ends in !. The first matching rule applies.
  • Query strings: the request's query carries over unless the destination has its own.

Why

Until now a static site could not send a redirect of its own. The only redirect was the hard-coded /index.html → ./. The workarounds were meta-refresh pages (a 200, not a redirect) or running the site as an app instance. _redirects is the closest thing to a standard: Netlify and Cloudflare read it, and many site generators write it. The file ships inside site.tar.gz, so the rules are versioned and roll back with the content.

How

  • internal/edge/redirects.go (new) has the parser and matcher.
  • internal/edge/static.go: - PackDir refuses a root _redirects that has any line the edge cannot use, and names each such line. carlos ship and carlos deploy therefore fail at ship time instead of silently skipping a rule. - The edge parses the file once per version swap. It keeps the rules in servedTree with that version's dir and version, so a request never pairs one version's files with another version's rules. - serveTree keeps its own lookup and runs it once more for a rewrite destination. A new serveFile serves the file, either as the 200 it always was or under a rule's 404 status. A site without _redirects takes exactly the old path.
  • Safety: - The edge never serves _redirects. - Destinations starting // or /\ are refused. - Captures are percent-encoded into the Location. - Path Locations are cleaned the way http.Redirect cleans them, so an empty capture cannot make one protocol-relative. The loop guard compares the Location that is actually sent. - A redirect to the URL being requested is skipped. - Rewrite targets are cleaned inside the tree.
  • Not supported: Netlify conditions (Country= etc.), proxying to other hosts, and netlify.toml.
  • Limits: 2000 rules, 256 KiB, 2048 characters per line.
  • Docs: a new subsection, "Redirects for a static site", under carlos deploy in docs/site/cli.md.

Testing and review

  • internal/edge/redirects_test.go: - Parser tests: the forms that are accepted, about 25 lines that must be refused, keeping the good lines around a bad one, the limits, and a BOM. - PackDir refusing a bad file. - End-to-end handler tests that ship a real site. They cover shadowing, forcing, splats and placeholders, query and fragment handling, host rules, the loop guard, SPA fallback with a working ETag and 304, the custom 404 page including HEAD, and /_redirects not being served.
  • go vet and gofmt are clean. The internal/edge and internal/docsite tests pass locally.
  • An Opus review found no security issues and four bugs, all fixed with regression tests: - The loop guard missed a /x/* /x/:splat/ 301! loop. - A rewrite was cut at a ? or # that came from a decoded capture. - Percent-encoded sources passed ship but never matched. They are now decoded. - A UTF-8 BOM made ship refuse the file.
  • internal/provision passes, census included.
  • Amadan CI passed on e6e3d2eb; rebased over main as ff350c27 (CI pending at the time of writing).

Root read census

The change touches four units in the root read census: PackDir, staticSite.serveTree, the new staticSite.serveFile and the new staticSite.loadRedirects. Overlay internal/provision/testdata/plan14-root-reads-static-redirects.tsv loads after db-region-forward-copy, and the seal moves to 65857c29…d831. Every new read stays inside the extracted, digest-verified tree. The ruling is asked of the census owner on #280; the rows carry PROPOSED-static-redirects until then.

The first two CI runs failed on this census. The internal/console failure in the first run came on top of that.

Open questions for a reviewer

  • Should ship refuse unsupported lines (current behaviour) or warn and drop them? Refusing is stricter than Netlify and Cloudflare, which ignore bad lines.
  • A 404.html convention without any rule, as Netlify, Cloudflare and GitHub Pages do, would be a natural follow-up. It is not in this branch.
Human

Static sites honour a Netlify/Cloudflare-style _redirects file: redirects, SPA rewrites, custom 404s. Census overlay proposed on #280.

Commits 0
Discussions 1 1 open
Tasks 0/0 done

Discussions 1 open

Tasks

  • No tasks yet.

Activity