A URL, and finding the server

Read a URL the way the browser reads it, and know what happens before any request is sent.

01of 18
≈ 11Min read
4Parts

Every part of a URL does something

The origin — scheme, host and port together — is the unit of security on the web. Two pages share an origin only if all three match; http and https on the same host are different origins, and so are ports 443 and 8443. Almost every rule in this course is phrased in terms of origins.

and the browser gives you all of them
https://shop.doors.test:8443/catalogue/pivot?colour=steel&sort=price#specs
└─┬─┘   └───────┬───────┘└─┬─┘└──────┬───────┘└────────┬─────────┘└─┬─┘
scheme       host        port      path             query       fragment

const url = new URL(location.href);
url.protocol   // "https:"
url.hostname   // "shop.doors.test"
url.port       // "8443"   (empty string when it is the default)
url.pathname   // "/catalogue/pivot"
url.searchParams.get("colour")   // "steel"
url.hash       // "#specs"
url.origin     // "https://shop.doors.test:8443"
The fragment never leaves the browser

Everything after # is for the page, not the server: it is not sent in the request and never appears in a server log. Which also means it is the wrong place to hide anything, and the right place for a scroll target or a client-side route.

Finding the server: DNS

A host name is not an address. Before anything can be sent, the browser has to turn shop.doors.test into an IP address, and it asks in a chain: its own cache, the operating system, then a resolver — usually your ISP's or one like 1.1.1.1 — which walks the root servers, the .test servers and finally the name servers for doors.test.

Each answer carries a TTL, and everyone along the way caches it for that long. That is why a DNS change "takes a while": you are waiting for other people's caches to expire, and nothing you do can shorten it.

the record types worth knowing
A      doors.test       →  93.184.216.34       (IPv4)
AAAA   doors.test       →  2606:2800:220:1::   (IPv6)
CNAME  www.doors.test   →  doors.test          (an alias)
MX     doors.test       →  mail server         (email)
TXT    doors.test       →  verification, SPF, DKIM

What the first request really costs

A first visit to a new host pays all four before a single byte of HTML arrives — which is the whole argument against loading fonts, analytics and widgets from five different domains. Every extra host is another DNS lookup and another handshake.

StepTypical costHappens again?
DNS lookup20–120 msno, until the TTL expires
TCP handshake1 round tripno, the connection is reused
TLS handshake1–2 round tripsno (and resumption makes it cheaper)
The request itself1 round tripevery request
Telling the browser early

<link rel="preconnect" href="https://fonts.example"> starts the DNS and the handshakes before the request is needed; dns-prefetch does the cheapest part only. Use preconnect for the one or two origins that matter, not for ten — each open connection costs something too.

Encoding, and why URLs are so fussy

Concatenating strings to build a URL is how & in a product name becomes an extra parameter. URL and URLSearchParams do the encoding correctly, and they are in every browser and in Node.

two different jobs, two different functions
encodeURIComponent("steel & glass")   // "steel%20%26%20glass"  — for one value
encodeURI("https://a.test/a b")       // keeps :/?#&=  — for a whole URL

// building a query string properly
const url = new URL("https://api.doors.test/search");
url.searchParams.set("q", "steel & glass");
url.searchParams.set("page", "2");
url.toString();   // …/search?q=steel+%26+glass&page=2

Practice