The labs on this site need JavaScript to run. The same content is written out below.
Learn › Networking › HTTP
Redirection
When a server says "go to another address", how does the next request go out?
Instead of handling a request itself, a server can use a 3xx status code and the Location header to tell the client "send the request again to this address". This is called redirection. The codes 301, 302, 303, 307, and 308 all look like the same instruction, but they differ in whether the move is permanent or temporary and in whether the method of the next request is kept.
Checked against real execution · Ran 30 combinations against a test server (127.0.0.1) with fetch in Node 24 and curl 8.21 (-L) and checked them against the model. The permanent and temporary distinction and the wording of the standard follow RFC 9110 section 15.4
Concepts in this lab
- Redirection and the Location header
- A redirection is a response in which the server, instead of handling the request itself, tells the client to send the request again to another address. Its status code is 3xx, and the next address to request is written in the Location header of the response. It is the client, not the server, that sends the request to that address again, and the client also decides whether to follow it. 301 and 308 mean a permanent redirect, 302 and 307 mean a temporary redirect, and 307 and 308 do not allow the method and the body of the next request to change. If two addresses point to each other, the redirection never ends, so a client reports an error and stops when it goes past a limit it has set.
- Post-Redirect-Get pattern
- This is an approach in which a server that receives a form with POST does not return the result page right away, but redirects to the address where the result can be seen. The browser requests that address with GET, so the last request that remains on the screen is a GET. Because of this, if the user presses refresh, the POST is not sent again, which prevents an order or a payment from being processed twice. The right status code for this redirect is 303 See Other, which means "retrieve the result from another address with GET", and 302 is also widely used by convention.
- HTTP status codes
- A three-digit number that shows the result of the server's handling of a request. 2xx means success, 3xx means a move to another location (redirection), 4xx means a problem with the client's request, and 5xx means an error on the server side. Common ones are 200 (success), 201 (created), 400 (bad request), 401 (authentication required), 403 (no permission), 404 (not found), and 500 (server error).
- HTTP methods
- The part of an HTTP request that indicates the action the request wants to perform on a resource. GET means read, POST means create or a processing request, PUT means replace the whole resource, PATCH means modify part of it, and DELETE means delete. Use each one according to its meaning so that HTTP behavior such as caching and retries works as expected.
- Safe methods and idempotent methods
- Safe methods are read-only methods that do not change the state of the server: GET, HEAD, OPTIONS, and TRACE. Idempotent methods are methods for which sending the same request several times has the same effect on the server as sending it once: all the safe methods plus PUT and DELETE. POST and PATCH are not guaranteed to be idempotent, so sending one twice can cause it to be processed twice. This is the basis for deciding whether a request may be sent again when no response arrives.
Try these
- Default settings — The server responds to a POST with 302. The next request becomes a GET and the body is lost.
- With 307, the POST stays a POST — The request is sent again to the new address without changing the method or the body.
- 303: get the result with GET — This is the approach used to send the user to a result page after a form is submitted.
- Sending a PUT with 301 — In the clients checked here (fetch and curl), only a POST is changed to GET. A PUT goes on unchanged.
- Permanent redirect 308 — It is a permanent move like 301, but it keeps the method.
- Redirects that point to each other — There is no end, so the client stops by itself at a limit it has set.
Running with the default settings
- The client sends the request POST /order + body. The body holds the content of the order (item=keyboard).
- The server responds with 302 Found. The Location header of the response holds the next address to request, /orders. A 302 response is a temporary redirect. It means "go to /orders just this time", so the client must still send the next request to the original address, /order, first.
- The client reads Location and builds the next request. The standard (RFC 9110) allows changing POST to GET on a 301 or 302. It is not required, but fetch and curl really do change it to GET and drop the body.
- The client sends the request GET /orders (no body). The user did not ask for this second request. The client sends it by itself after reading Location. The body of the first request is not carried in this request.
- The server for /orders responds with 200 OK. What it received is a GET, so it returns the list of orders. The body sent in the first request never reached this server. Only this last response is passed to the code that sent the request.
Result of this run
- Changing POST to GET on a 301 or 302 is something the standard permits ("it is allowed"), and most clients, including browsers, do it. The body is lost when this happens. To pass the POST on to the new address as it is, use 307 (temporary) or 308 (permanent).
Code
// Server (Node.js)
http.createServer((req, res) => {
if (req.url === '/order') {
res.writeHead(302, { Location: '/orders' }); // 302 Found
return res.end(); // only gives directions, with no body
}
res.writeHead(200);
res.end('Order list');
});
// Client 1: fetch follows Location unless you tell it otherwise
await fetch('http://shop.example/order', {
method: 'POST',
body: 'item=keyboard'
});
// Client 2: curl follows it only when you add -L
// curl -L -d 'item=keyboard' http://shop.example/order
Assumptions of this model
30 combinations (5 status codes × 3 methods × 2 destinations) were run against a test server started on 127.0.0.1 with two clients (fetch in Node.js 24 and curl 8.21 with the -L option), and the method and body of the second request, the number of requests that reached the server, and how it ended were confirmed to match the model. The second request of the two clients was the same in every combination, and only the number of redirects after which they stop differed (20 for fetch, 50 for curl). This number differs from client to client, and curl can change it with --max-redirs. The limit and the error page of browsers were not verified by execution. The "Kind" and "Method handling set by the standard" columns are not execution results; they restate the wording of the HTTP standard (RFC 9110, section 15.4). The rule that only a POST is changed to GET on 301 and 302, and that every method other than GET and HEAD is changed to GET on 303, is written in the Fetch standard (HTTP-redirect fetch). The explanation that browsers remember a permanent redirect follows from the standard defining 301 and 308 responses as responses that can be stored even when no explicit instruction says so, and it was not verified by execution. Whether to follow a redirect is up to the client. It was confirmed on the same test server that the http module of Node, curl without -L, and fetch called with redirect: 'manual' do not follow it and return the 3xx response as it is. The PUT in curl was sent with -T (upload a file). When you specify the method directly with -X in curl, that method is used as it is even after a redirect, so the result differs.
Previous lab: Retries and idempotency · Next lab: Content negotiation and compression