Request routing

Request routing decides what answers a URL: a platform feature such as sign-in, your edge worker, an exported file or your page. The order matters, because the first match wins and no parent route is ever searched. This article explains that order on RTEdge.net, how a URL finds its route, and how the playground output differs.

Overview

A request to a deployed host passes through these steps in order. A step either lets the request through or answers it, and the first step that answers ends the request. Step 1 only rewrites the URL, and step 15 adjusts whatever answered. Steps 1 to 4 and 15 belong to the platform around your deployment, and steps 5 to 14 to the edge router that serves it.

StepLayerWhat happensExamples
1PlatformThe URL is normalized.//nav.js, /x/../nav.js and /%6eav.js all become /nav.js.
2PlatformRedirects rewrite the URL into its canonical form./docs to /docs/, /docs: to /docs/:, /docs/* to /docs*.
3PlatformReserved names are refused, and a visitor who sends too many requests gets a 429 page./wp-admin/, index.php, and a 429 page for too many requests.
4PlatformPlatform suffixes and files are answered.::si, ::so, /favicon.ico, /robots.txt, and /::edge, ::qr and ::clear Experimental
5RouterThe route is chosen from the host and the path before the first :.name.rt.ht/docs/x.js is the route name/docs.
6RouterThe ownership check and the KV viewer answer. Experimental/::owner, /::kv
7RouterAny other : in the path goes straight to the route's worker./:hello, /docs/:api
8RouterThe index and the versions redirect answer./*, /??
9RouterThe deployment is read. A route that is not deployed answers 404, "Code does not exist."?m=uo Experimental
10RouterThe source view opens the source in RTCode.io, if the visitor may open it./?, /docs/?
11RouterThe edge:user check decides who may view the rest.A 401 or 403 page.
12RouterThe digest answers. Experimental/::meta
13RouterAn exported file answers, for any method./nav.js, /.css
14RouterThe root document answers GET. Anything else goes to the worker, or answers 404, "File does not exist."/, /missing.js, POST /
15PlatformResponse headers and compression are applied to the answer.See Response headers.

Routes are directories

For the route strings you enter in the playground, see Routes. This section explains how incoming URLs select those deployments.

The router builds the route from the host and the part of the path before the first :. It removes the last path segment and any trailing slashes. That exact route is read and nothing else: parent routes are never searched. For name.rt.ht the host part is just name, and for a custom domain it is the whole hostname.

URL on name.rt.htRoute
/name
/x.jsname
/docs/name/docs
/docs/x.jsname/docs
/docs/sub/x.jsname/docs/sub

So https://name.rt.ht/docs/sub/ answers 404, "Code does not exist.", unless name/docs/sub is itself deployed, even when name/docs has a worker. Path routes are independent deployments, each with its own files, worker, versions and screenshots. They share one browser origin, though: all the path routes of a host share its cookies, storage, service worker scopes and sign-in session. Domains and subdomains explains hosts and paths.

Canonical URLs

Before a request is routed, the platform rewrites some URLs into one canonical form. All of these are 307 redirects:

Because of the first rule, https://name.rt.ht/api/users is redirected to /api/users/, which is the route name/api/users, not an API of the route name. Reach a worker with a worker path instead, /:api/users. Write the URLs of directories with a trailing slash. File names stay case-sensitive: /NAV.JS does not find nav.js.

Worker paths

The first : in a path splits it. The text before it chooses the route, as above. The text after it becomes the worker's pathname, and the query string is kept.

URL on name.rt.htRouteThe worker sees
/:name/
/:helloname/hello
/:/api/usersname/api/users
/docs/:hello?x=1name/docs/hello?x=1

The playground output splits : paths the same way, so the same code works in both places. See The /: suffix.

Try a worker path

The iframe asks the worker for :hello. The worker answers with the pathname it sees and its route. In the playground the route is the output host. Deployed to name/docs, the same code answers {"path":"/hello","route":"name/docs"}.

html
<iframe src=:hello></iframe>
<script type=worker edge>
  export default { fetch: r => Response.json({ path: new URL(r.url).pathname, route: r.rt.route }) }
</script>

Platform suffixes come first

Suffixes such as ::si for sign-in are answered by the platform, before the router and before your deployment is read. They apply to every URL of every platform host, deployed or not, and your code cannot override them. Most of them are matched as a piece of the path, wherever it is, and not only at the end. A path such as /:api::save contains ::s, so the sign-in handler answers it and it never reaches your worker.

Keep :: out of file names, route paths and worker paths. Reserved paths lists the patterns, and URL suffixes describes each suffix.

Source, versions and the index

The playground output has none of these. They belong to the router.

Access checks

After the source view, the edge:user check decides who may view the deployment. It protects the root document, exported files and fallback calls to the worker, but not : worker paths. A visitor who fails it gets the 401 or 403 page from Error pages and status codes.

Files, page and worker

An exported file answers when the last segment of the path equals its name. Matching is case-sensitive and ignores the query, so /nav.js?v=2 serves nav.js. Files answer for every method, and an export wins over the worker, so a worker never sees a request for a path that matches an export.

The root document answers GET only. Everything else that has no answer goes to the worker, with the original URL, when one of these holds:

Without a worker, the answer is 404, "File does not exist." That makes an API-only route possible: leave the page empty and let the worker answer GET of the root.

Routing in the playground

In the playground, your page is the document of an output origin, and a service worker answers its requests. In short, it answers ::su first, then an exported file whose last segment contains a dot (your own files, then those of sibling routes), then the worker for every other request except GET /. GET / always returns your page, and anything left goes to the network. Hosts that you list in io:host reach the worker too. The playground has no router, so the platform steps above do not exist there.

RequestPlaygroundRTEdge.net
GET /Your page, even when a worker exists.Your page. The worker answers only when the page is empty.
An exported fileThe file, for any method.The file, for any method.
/aboutThe worker.A redirect to /about/, which is the route name/about.
/app.js, not exportedThe worker.The worker, as a fallback.
/:helloThe worker, with /hello.The worker, with /hello.
::suAnswered with the signed-in editor account.Answered by the platform.
::si, ?, ??, *Not emulated.Answered by the platform or the router.
The worker returns nothingA non-GET request to / gets the page, and any other request goes to the network.The 501 page, "Worker response is undefined."

Because every non-root path reaches the worker in the playground, test clean paths on a deployment before you rely on them. Playground and edge differences lists the other differences.

Guarantees and limits

Anatomy of a deployment

What a route holds: the page, files, worker, metadata, version and screenshot.

Playground and edge differences

Everything that behaves differently in the playground and on RTEdge.net, in one table.

Security and access model

Sites, sessions and permits: who can view a page, and who can open its source.

Architecture

How the editor, the output and the edge fit together.

See also