Konrad Kowalski (rootsher)Principal Platform & Reliability Architect101111011111010101001111011001100001110010010100

Request and Response: What Does the Browser Actually Send?

date
category
Frontend
also in
Backend
reading
2 min / 307 words

The frontend writes:

js
await fetch("/api/users/42");

At the HTTP level the intent looks roughly like this:

http
GET /api/users/42

But a request is not a URL.

Semantically it consists of:

text
method
target
headers
optional content

And a response, correspondingly:

text
status
headers
optional content

The request

An example simplified to HTTP/1.1 notation:

http
GET /api/users/42 HTTP/1.1
Host: example.com
Accept: */*
Cookie: session=...

The browser can add some of the headers on its own.

JavaScript does not construct a raw network packet.

It calls a browser API:

text
JavaScript
   |
   v
Fetch
   |
   v
browser builds HTTP request

That distinction keeps coming back throughout the series.

The response

The backend can answer:

http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 24

{"id":42,"name":"Alice"}

In fetch() we first get a Response object:

js
const response = await fetch(url);

response.status;
response.headers;

And we consume the body separately:

js
const user = await response.json();

That is not an accident.

Headers and body have separate lifecycles.

A resolved fetch() does not mean 200

If the backend answers:

http
HTTP/1.1 404 Not Found

the browser received a valid HTTP response.

So:

js
const response = await fetch("/missing");

console.log(response.status); // 404
console.log(response.ok);     // false

The promise does not have to be rejected.

For the Fetch API:

text
HTTP error response
!=
network failure

That is a very important boundary.

A 404 and a 500 are information inside HTTP.

Having no response to hand over at all is a different class of problem.

A body does not always exist

The frontend often assumes:

text
response = JSON

But an HTTP response may have no content.

For example:

http
HTTP/1.1 204 No Content

This code:

js
await response.json();

then has nothing to parse.

So an API client should understand the contract of the endpoint instead of treating every success as JSON automatically.

An HTTP message is an abstraction

In HTTP/1.1 a request looks on the wire roughly like the text shown above.

In HTTP/2 a request is encoded as binary frames.

In HTTP/3 it is not sent as a textual line either:

text
GET /api/users/42 HTTP/1.1

Yet semantically it is still:

text
GET
/api/users/42
headers

That is why DevTools shows us the logical HTTP request, not necessarily the bytes travelling over the network.

Which leads to the next question.

If GET, POST, 404 and 500 are part of HTTP semantics, what exactly do they mean, and why can the browser and the infrastructure behave differently depending on the method and the status?