Specify Redirects, Missing Routes and Allowed Methods
Drafted and reviewed using AI assistance.
On this page
Before asking an agent to “fix the old URLs,” list the requests those URLs receive. A moved reading page, a POST endpoint and an unknown path need different outcomes. Give each request a status, a destination when appropriate, and a rule for its method, body and query parameters.
A redirect is part of an application contract. RFC 9110 defines the HTTP status semantics; MDN's redirection guide summarizes the method behavior. Those sources do not decide which of your query parameters should survive a move. Put that decision in the brief alongside the status code.
The following example is fictional. Its paths, request bodies and expected responses are teaching materials, not a deployed configuration or the result of HTTP testing.
Describe the route inventory first
Use https://example.invalid as the base for every request below. These are exact, case-sensitive paths. The slashless /guide is an explicitly listed alias; there is no automatic rule that adds a slash to every unknown path.
| Path | Supported methods | Successful outcome |
|---|---|---|
/guide/ |
GET, HEAD | 200; terminal guide resource |
/render/ |
POST | 200; terminal, fictional read-only rendering operation |
/result/r7/ |
GET, HEAD | 200; terminal result resource present in this fixture |
/old-guide/ |
GET, HEAD | 308 to /guide/ |
/guide |
GET, HEAD | 308 to /guide/ |
/old-render/ |
POST | 308 to /render/ |
/preview-guide/ |
GET, HEAD | 307 to /guide/ |
/send-preview/ |
POST | 303 to /result/r7/ |
Methods outside a known path's list receive 405 with an Allow header containing exactly that list. This example does not assume an additional OPTIONS handler. If your framework supplies one, include it in the actual inventory and revise Allow accordingly.
Paths outside the table receive 404. For this fictional application that means there is no matching route. In HTTP more broadly, 404 can also mean the server is unwilling to disclose that a representation exists; it does not itself establish permanent removal. These error responses have no Location, and the unknown-path response has no Allow in this contract.
Check the method before applying an alias rule. A GET to the POST-only /old-render/ therefore receives 405 rather than a redirect to another POST-only endpoint. A POST to /guide/ also receives 405; changing it to GET would hide an unsupported operation.
Choose the redirect by what the next request must do
The old guide and render aliases are intended to move permanently, so this exercise selects 308. The temporary preview alias uses 307. When following either of those redirects, a client must preserve the request method; the POST example also retains the request body. RFC 9110 §§15.4.8–15.4.9 and the MDN table describe these distinctions.
The POST to /send-preview/ has a different purpose: direct the client to retrieve the selected result resource. Its 303 response chooses a follow-up GET with no original POST body. RFC 9110 §15.4.4 permits a retrieval request using GET or HEAD; the GET shown here is the application choice for this POST case, not a claim that every 303 converts every method to GET.
Avoid choosing 301 or 302 merely because they are familiar when the requirement is to preserve POST. Their historical method-changing behavior makes the intended contract less clear. Selecting 308 here expresses both permanence and method preservation. It does not, by itself, establish that replaying an application operation is safe.
The fictional /render/ operation is read-only so the example can concentrate on routing. A real write endpoint needs a separate account of side effects, retries and duplicate submissions. A status code is not a substitute for that design.
State query handling separately
The selected redirect policy is deliberately narrow:
- A guide destination retains one
langparameter with valueenorzh. - A render destination retains one
modeparameter with valueshortorfull. - A result destination retains no query parameters.
- Other keys, including the fictional
campaignkey, are discarded when constructing the redirect destination.
The matrix uses only single, valid values. Repeated keys, unsupported values for lang or mode, malformed escaping and other encoding cases require an additional decision before implementation. Do not invent that behavior from these examples.
Canonical endpoints do not redirect merely to remove an extra query key: they ignore unlisted keys in this contract. They accept the same supported lang and mode values as their aliases. For the simple retained values shown, the exact Location is written out below. A real implementation should use its platform's URL facilities and verify the resulting encoding rather than concatenate arbitrary input.
These query rules belong to this application. A 307 or 308 does not automatically filter tracking parameters or retain a language preference for you.
Work through complete requests
All request targets and Location values in this table are relative to the stated base. Thus /guide/?lang=zh in the Location column means the exact absolute header value https://example.invalid/guide/?lang=zh. A dash means the header, body or follow-up is absent as indicated by its column. POST bodies are UTF-8 JSON strings shown literally.
| ID | Method and target | Request body | Status | Location | Allow | Follow-up method and body |
|---|---|---|---|---|---|---|
| r01 | GET /old-guide/?lang=zh&campaign=demo |
— | 308 | /guide/?lang=zh |
— | GET; no body |
| r02 | HEAD /old-guide/?lang=en |
— | 308 | /guide/?lang=en |
— | HEAD; no body |
| r03 | POST /old-render/?mode=short&campaign=demo |
{"text":"Hello"} |
308 | /render/?mode=short |
— | POST; exact same body |
| r04 | GET /preview-guide/?lang=zh&campaign=demo |
— | 307 | /guide/?lang=zh |
— | GET; no body |
| r05 | HEAD /preview-guide/ |
— | 307 | /guide/ |
— | HEAD; no body |
| r06 | POST /send-preview/?campaign=demo |
{"text":"Preview"} |
303 | /result/r7/ |
— | GET; no body |
| r07 | GET /missing/?lang=zh |
— | 404 | — | — | None |
| r08 | POST /guide/?lang=zh |
{"text":"Hello"} |
405 | — | GET, HEAD |
None |
| r09 | GET /old-render/ |
— | 405 | — | POST |
None |
| r10 | GET /guide?lang=en&campaign=demo |
— | 308 | /guide/?lang=en |
— | GET; no body |
| r11 | GET /guide/?lang=zh |
— | 200 | — | — | None |
| r12 | HEAD /guide/?lang=zh |
— | 200 | — | — | None |
| r13 | POST /render/?mode=short |
{"text":"Hello"} |
200 | — | — | None |
| r14 | GET /result/r7/ |
— | 200 | — | — | None |
| r15 | GET /send-preview/ |
— | 405 | — | POST |
None |
| r16 | POST /unlisted/ |
{"text":"Hello"} |
404 | — | — | None |
Each listed follow-up reaches a terminal 200 response. Every HEAD response, including a redirect response, contains no response content under RFC 9110 §9.3.2. Redirect responses to other methods are not required by this example to have empty content. Only the status, Location, Allow and specified follow-up behavior are fixed here; unrelated headers and error-page wording remain separate decisions.
“Follow-up” means the expected next request if the client chooses to follow. It does not assert that a particular browser, library or proxy automatically follows, or define which credentials and other headers it forwards.
Prevent loops using the same inventory
There are three terminal destinations: /guide/, /render/ and /result/r7/. None redirects a supported request in this fixture. Every alias targets one of them directly, so the listed successful flows require one redirect at most.
This small graph is easier to review than two independent rules such as “add a trailing slash everywhere” and “remove trailing slashes from canonical URLs.” Those rules could send the same request back and forth if their scopes overlap.
Before implementing the contract in a real application, inspect middleware, proxy rules and application routes together. A matrix with no loops on paper does not prove that another layer will not add one. Check the final method as well as the path: a preserved POST must arrive at a resource that supports POST.
Do not send every unknown path to the homepage to make the error count disappear. That would replace the explicit missing-route outcome with an unrelated destination and erase the distinction the matrix is meant to preserve.
Copy the investigation request
Review the supplied fictional route inventory and all 16 requests.
Do not access example.invalid or any live service. Return a proposed
routing contract; no server, proxy, DNS or production change is requested.
Use exact, case-sensitive paths. Check supported methods before aliases.
Known path + unsupported method: 405 with exactly the route's Allow list.
Unknown path: 404 with no Location; do not redirect unknown paths home.
Keep the wider HTTP meaning of 404 distinct from this route fixture.
Use 308 for the permanent guide/render aliases, 307 for the temporary
preview-guide alias, and 303 for the POST-to-result retrieval flow.
Preserve POST and its exact UTF-8 body through the 308 render redirect.
For the selected POST 303 case, show GET with no original POST body.
Preserve HEAD through 307/308; HEAD responses contain no content.
Construct every Location explicitly. Guide aliases retain lang=en|zh;
render aliases retain mode=short|full; result redirects retain no query.
Discard other keys on redirects. Canonical endpoints ignore extra keys
without redirecting. Duplicate retained keys, unsupported retained-key
values and malformed encoding need further requirements, not guesses.
For every row return status, Location or absence, Allow or absence,
follow-up method/body or none, and expected terminal status if followed.
Verify that every redirect target supports its arriving method and is
terminal in this inventory. List unknown framework/proxy/client behavior.
Separate manually specified expectations from any actual HTTP checks.
No HTTP testing has been performed for these teaching examples.
A reviewable redirect request tells an agent which old behavior to preserve, which behavior to change, and which requests should still fail. The route matrix makes those decisions visible before they become configuration.