Skip to content

An origin's redirect to itself keeps the reader on the address they asked for #1330

Open

Description

@jwrosewell

Stacks on the publisher origin in a folder of object storage (#1329).

What changes

When the origin answers a request with a redirect whose Location names the origin, the reader is sent to the same path, query and fragment on the scheme and host they asked for, so their browser stays on the deployment. There is no setting.

With origin_url = "https://origin.example" and a reader who asked for https://publisher.example/news:

The origin answers The reader receives
301 with Location: https://origin.example/news/ Location: https://publisher.example/news/
302 with Location: https://origin.example/news/?page=2#latest Location: https://publisher.example/news/?page=2#latest
301 with Location: https://www.origin.example/news/ The header as the origin wrote it
301 with Location: /news/ The header as the origin wrote it
200 or 201 with Location: https://origin.example/news/ The header as the origin wrote it

How it works

  • Which responses. A status from 300 to 399 with a Location header. A Location on any other status is left as written.
  • What names the origin. The host of the Location, with its port, compared with the Host the origin was sent, which is origin_host_header_override where one is set and otherwise the host of origin_url with its port, ignoring letter case. The scheme is not compared, and a Location that leaves the scheme off is read on the scheme of the request.
  • The origin path. Where origin_url has a path, a Location at or beneath that path comes back without it, and one outside it is left as written, because no address on the reader's host leads there.
  • What is left alone. A Location naming any other host, a host beneath the origin's, one that names no host, which the reader's browser resolves against the address it asked for, a value that cannot be read as an address, and any Location when the request names no host.
  • Where it happens. In one place, where handle_publisher_request receives the origin's response, so the streamed page, the buffered page, a HEAD and every other method carry the rewritten header.

Why

An origin redirects to itself for ordinary reasons, a request path without its trailing slash being enough, and it names itself in the Location because that is the name it was asked for. Relayed as written, the header takes the reader's browser to the origin, and from then on each page is the origin's own answer with none of the publisher's middleware run on it, at an address the publisher never gave the reader. No deployment asks for that by naming an origin, so there is no setting. The rule uses only the origin's own name and path and the request's own scheme and host, names no vendor and reads no module's settings, so it is core's.

What breaks

Nothing for a deployment whose origin sends no redirect to itself. One whose origin does now keeps its readers, where before they left for the origin.

Draft on the fork, jwrosewell#32, on top of the publisher origin in a folder of object storage (fork pull request 31). The design page is docs/superpowers/specs/2026-10-13-origin-redirect-design.md on that branch.

How it was verified

CI on the fork. The tests in core cover a Location naming the origin coming back on the request's scheme and host with its path, query and fragment, for an origin on https and on plain HTTP, with and without a port, in any letter case and with the scheme left off, the override compared where one is set, another host, a host beneath the origin's and the origin's name on a different port left as written, the origin path removed where the Location is at or beneath it and left where it is not, and through handle_publisher_request with a scripted origin, the rewritten header on a buffered answer and on a streamed page for a GET, a HEAD, a POST, a PUT and a DELETE across 301, 302, 303, 307 and 308, with a redirect to another host, one naming no host, a Location on a 200, a 201, a 404 and a 503, and a redirect with no Location each reaching the reader as the origin sent them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions