The Layers of HTTP in the Browser: From fetch() to QUIC
- date
- category
- Frontend
- also in
- Backend · Networking
- reading
- 1 min / 243 words
In frontend code HTTP often looks like this:
const response = await fetch("/api/users/42");
const user = await response.json();
Two lines hide several different layers:
Fetch API
|
v
browser security / cache / cookies
|
v
HTTP semantics
|
v
HTTP/1.1, HTTP/2 or HTTP/3
|
v
TCP or QUIC
|
v
TLS
This series goes below fetch(), but it stays a series for the frontend.
We will not implement an HTTP parser or tune a server kernel. What matters here is what directly affects an application in the browser:
- why a
404does not reject the promise fromfetch(), - what makes
GETdifferent fromPOSTbeyond the name, - why retrying a
POSTcan be dangerous, - where the request headers we never set come from,
- why JavaScript cannot control some headers,
- when the browser sends a body, cookies and credentials,
- why HTTP/1.1 produced a different waterfall than HTTP/2,
- what multiplexing really gives you,
- why HTTP/3 changed the transport but not the semantics of
GET /api/users, - what happens between
fetch()and the first byte of the response.
One distinction runs through the whole series:
browser API
!=
HTTP semantics
!=
HTTP wire protocol
!=
transport
fetch() is a browser API.
GET, POST, status codes and headers belong to HTTP semantics.
HTTP/1.1, HTTP/2 and HTTP/3 encode and carry those semantics in different ways.
TCP and QUIC sit lower still.
That is why the same code:
fetch("/api/users")
can be carried by HTTP/1.1, HTTP/2 or HTTP/3 without changing the application.
We will go through:
request / response
|
v
methods + status codes
|
v
headers + body
|
v
browser-controlled HTTP
|
v
connections
|
v
HTTP/1.1
|
v
HTTP/2
|
v
HTTP/3 + QUIC
|
v
full fetch lifecycle
By the end the Network panel in DevTools should stop looking like a list of requests.
It should look like a record of the decisions made by the browser, the protocol and the network.