Module II · Loading

From URL to first byte

Module 1 ended with a renderer process that cannot open a network socket, handing a request up to the privileged browser process to perform. This module is what the browser process does with it. You press Enter; some milliseconds later the first byte of an HTML document lands in memory. Between those two events is the entire I/O subsystem of the web — address parsing, name resolution, connection setup, encryption, and the request itself. None of it is visible in your HTML, and all of it is happening on your behalf.

2.1 What you type is not what you mean

Start before the URL exists. You type into the address bar — the omnibox — which is not a URL field. It is a guesser. Type democompany.com and it must decide: is this an address to navigate to, or words to search for? It applies heuristics — does it look like a hostname, is there a dot, is it a known site — and either constructs a URL or hands your text to your default search engine as a query. The same keystrokes can become a navigation or a search depending on a judgment the browser makes before any request leaves your machine.

Once it commits to navigation, the browser normalizes what you gave it into a real URL: it supplies a scheme if you omitted one (increasingly https by default), lowercases the host, encodes illegal characters, and resolves the result against the URL standard. Only then does the loading machinery have something to work with.

2.2 Parsing, schemes, and DNS — covered elsewhere

2.5 Opening the connection

With an IP address in hand, the browser establishes a connection — and on the modern web that means a secure connection, because plain http is now the exception and browsers actively upgrade to and prefer https. Setting one up is a sequence of round trips, and round trips are the currency of web latency: each one is a full there-and-back across the physical distance to the server, and you cannot go faster than the speed of light lets you.

Connection setup as a sequence of round tripsAfter a DNS lookup, the browser and server complete a TCP handshake of about one round trip, then a TLS 1.3 handshake of about one round trip, then the browser sends an HTTP GET and the server returns the response. The first byte arrives after roughly two to three round trips; HTTP/3 over QUIC collapses several of these.BrowserServer(DNS has already resolved the host to an IP — often from cache)TCP~1 RTTSYNSYN-ACKACKTLS 1.3~1 RTTClientHelloServerHello, certificateFinishedHTTPGET /index.html200 OK + first bytesFirst byte after ~2–3 round trips. HTTP/3 (QUIC) collapsesthe transport and TLS handshakes into roughly one.
Figure 2.2 — The handshakes before the document. TCP establishes a reliable channel, TLS negotiates encryption and verifies the server's certificate, and only then does the actual HTTP request go out. Every arrow is latency the distant user pays in full.

Three things deserve emphasis. First, the TLS handshake is where the browser authenticates the server — it checks that the certificate is valid, unexpired, and issued for this host by a certificate authority it trusts. That check is what the padlock means, and it is the difference between talking to Demo Company and talking to someone impersonating it. Second, connections are expensive to build and so are reused: HTTP/2 multiplexes many requests over a single connection, which is why Module 4's flood of subresources does not pay this cost again per file. Third, HTTP/3 changes the picture by running over QUIC on top of UDP, folding the transport and cryptographic handshakes together and eliminating a class of stalling that plagued earlier versions. The diagram is the worst case; the modern web works hard to do better.

1997
HTTP/1.1 standardized: persistent connections, but one request at a time per connection, in order.
2015
HTTP/2: many requests multiplexed over one connection, headers compressed. The per-file connection tax largely disappears.
2022
HTTP/3 standardized over QUIC/UDP: handshakes merged, head-of-line blocking at the transport removed, faster recovery on lossy networks — the mobile and distant user case.

2.6 The request

Now the browser sends the request. For Demo Company's home page it is, in essence, this — a request line, then headers, then (for a navigation) an empty body:

GET /index.html HTTP/2 Host: www.democompany.com User-Agent: Mozilla/5.0 (…) Accept: text/html,application/xhtml+xml,… Accept-Language: en-US,en;q=0.9 Accept-Encoding: gzip, br, zstd Cookie: (whatever this origin has stored)

Almost none of that came from your HTML. The author wrote a link with an href; the browser supplied the method, the protocol version, the Host, an identifying User-Agent, the content types and languages it is willing to accept, the compression schemes it understands, and any cookies this origin previously set. This is the user agent acting as an agent — representing you and your preferences to the server, including preferences you never consciously expressed. The Accept-Language header is how a site can greet the distant user in their own language without asking; the Cookie header is how it recognizes a returning visitor, and the subject of Module 8.

2.7 The response, and the first byte

The server answers with a status line, its own headers, and the body — the bytes we have been waiting for:

HTTP/2 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 1024 Cache-Control: max-age=3600 … <!doctype html> <html lang="en"> … the document begins here …

The status code is the server's verdict, and the browser branches on it. 200 means here is your document. A 3xx is a redirect — the browser transparently issues a fresh request to the new location, which is why one navigation can become two or three round trips before any content arrives, and why redirect chains are a real performance cost. 404 means the server has no such resource (you will meet it deliberately in Module 7). 5xx means the server failed. The browser handles each without bothering the user with the distinction unless it must.

One header outranks the others for what happens next: Content-Type. It tells the browser what kind of thing the body is, and therefore which subsystem to hand it to. text/html goes to the HTML parser of Module 3. text/css, image/webp, application/javascript each route elsewhere. The browser will sniff the bytes if the type is missing or implausible, which is a security hazard — a server can send X-Content-Type-Options: nosniff to forbid the guessing. The type, not the file extension, is the authority. A file called data.txt served as text/html is parsed as HTML.

The browser does not trust the URL to say what a resource is. It trusts the server's declared content type, and then only cautiously.

2.8 The cheapest request is the one you skip

Before any of the above runs, the browser asks a prior question: do I already have this? The HTTP cache (Module 8) can satisfy a request with zero network at all, or with a cheap conditional request — “send the body only if it changed since the version I have,” to which the server can answer 304 Not Modified with no body. The entire round-trip apparatus of this module is something the browser spends real effort to avoid. That is the first lesson of web performance and the reason Module 1 asked you to toggle “Disable cache” and watch the waterfall change: you were turning the I/O subsystem's most important optimization off and on.

2.9 The lab: Demo Company's minimal request set

Everything in this module happens, in miniature, when you load Demo Company's home page. The page tells you to expect exactly two requests:

/index.html ← the navigation: scheme, host, path, the whole handshake /favicon.ico ← the browser asks for this on its own, by convention

Trace the first one against this module: the omnibox decided it was a navigation, the URL parsed into scheme and host and path, DNS resolved www.democompany.com, a secure connection opened across a few round trips, a GET /index.html went out carrying headers you never wrote, and a 200 OK came back marked text/html — which is the cue for Module 3. The second request, the favicon, is the one to dwell on: no element in the document requested it. The user agent did, because fetching a site's icon is part of its behavior, not the author's instructions. The I/O subsystem has a will of its own.

2.10 What you now have

The path from a keystroke to the first byte: the omnibox's navigate-or-search decision, normalization into a parsed URL, scheme dispatch choosing which machinery runs, DNS turning a name into an address, a TCP/TLS or QUIC handshake opening an authenticated encrypted channel across costly round trips, a request carrying headers the browser supplied on your behalf, and a response whose status code and content type decide what happens next. You also have the framing that ties it to Module 1: a renderer asked, the browser process did all of this, and the result is bytes — not yet a page.

Those bytes are text/html, which means they go to the parser. Module 3 is where a stream of bytes becomes the DOM: the tree that every other subsystem in the series reads from and writes to. The I/O subsystem delivered the program. Now the browser has to load it into memory and make sense of it.

The network gives the browser a stream of bytes. Everything after this is the browser deciding what those bytes mean.