The Application Layer & HTTP

September 27, 2026 • 11 min read

The Application Layer & HTTP
Table of contents

Part of the series:Networks Explained

The transport layer post left you holding a stream of bytes you can trust. Trustworthy bytes are still meaningless noise until someone agrees what they say. That agreement is the application layer: the top floor, the only one your program sees, and the place where the internet turned into something people actually use. This post opens it: who talks to whom, how names become addresses, and how one protocol designed in the 1990s grew into the most-deployed application on earth.

Client and server

The simplest division in computing: one program waits, the other asks.

A server opens a port and waits for incoming connections. A client opens a connection and starts the conversation. The roles belong to the endpoints, not to the machines: your browser is a server when your colleague loads a page from the little app you wrote.

client                                         server
   │  "send me the page"        ───────────────▶ │
   │                                              │
   │  "here it is"              ◀─────────────── │
   │                                              │
   ▼  connection closed                          ▼  waits for the next

This request-response shape is called client-server, and it is one of two patterns in wide use. The other is peer-to-peer, where every machine both asks and offers, which is what file sharing and some video calling use.

The word “application” covers a wide range of things, including two things that are not really applications at all: DNS, which turns names into addresses, and TLS, which encrypts the conversation. Neither shows up in your code as a function call, and that is exactly the end-to-end idea in practice: the network moves bytes, and everything meaningful happens at the ends.

URLs: naming a thing on the web

Before any request, you need to name the thing. The URL1 is that name, and its anatomy explains more about the web than any design document.

https://example.com:443/docs/page?q=networking#requests
│     │             │   │        │        │        │
│     │             │   │        │        │        └ fragment: never sent
│     │             │   │        │        └────────── query: parameters
│     │             │   │        └─────────────────── path: which resource
│     │             │   └──────────────────────────── port: usually implicit
│     │             └──────────────────────────────── host: which machine
│     └────────────────────────────────────────────── scheme: which protocol
└─────────────────────────────────────────────────── everything

Each part answers a question at a different layer, which is why a URL feels layered even though it is one string. The scheme says which application protocol to speak, the host says which machine, the port says which program on it, and the path says which resource that program should produce. The fragment is a detail for your browser only; it is not sent to the server at all.

Real example: a browser given https://example.com:443/docs sends GET /docs to port 443, because the scheme already implies HTTPS and 443 is its default port. A URL carries more information than it appears to, and the parts it omits are the parts it assumes.

DNS: names into addresses

Machines have IP addresses. Humans have names. DNS2 is the system that connects them, and it is one of the oldest, strangest, and most consequential pieces of the internet.

It is distributed on purpose. The whole system answers one question, what IP address does this name have?, and it answers it by asking strangers in a fixed order, caching aggressively so nobody has to ask often.

your machine does not know the answer
   │
   ▼ asks the resolver (your provider, usually a small program near you)
   │
   ├─ cached? → answer immediately             (this is most of the time)
   │
   ▼ not cached, ask the root server: "where does .com live?"
   │  → "ask the .com server"
   ▼ ask the .com server: "where does example.com live?"
   │  → "ask this server"
   ▼ ask the authoritative server: "192.0.2.1, and here are its records"

The hierarchy mirrors how knowledge is distributed: root knows the top-level domains, the top-level domain knows the authoritative servers, the authoritative server knows one zone and is the only one allowed to give the final answer. Everyone above can point downward without knowing the details, which is what makes the system scale to a few hundred million names.

Caching is what makes it usable. Every answer comes with a TTL3, a lifetime in seconds. The resolver, the browser, and the operating system each keep answers until their TTL expires, so a popular name is asked about almost never again. This is also the property that causes pain: for up to a few hours after a record changes, part of the internet is still answering with the old address.

DNS answers more than addresses. Records4 come in types, and the type says what the answer is: an A record is an IPv4 address, AAAA is IPv6, a CNAME is another name, an MX record says where email for this domain should be delivered, an NS record says which server is authoritative. That is why changing an email provider and changing a website are both DNS edits, and why the two feel unrelated to users.

Real example: dig and nslookup ask DNS questions directly, so when people say “DNS is broken” it usually means either a name does not resolve or it resolves to the wrong place. The last mile of the web is famously a DNS problem more often than a server problem.

HTTP: the request and the response

With a name and an address, two programs can talk. HTTP5 is the protocol that decides what to say, and it is a text protocol: readable lines, human-inspectable, about as simple as a protocol can be.

GET /docs/page?q=networking HTTP/1.1     ← the request

Host: example.com
User-Agent: Mozilla/5.0 ...
Accept: text/html
Connection: keep-alive

HTTP/1.1 200 OK                          ← the response

Content-Type: text/html; charset=utf-8
Content-Length: 4821
Cache-Control: max-age=3600

<html>...

A request has a method, a path, and headers, which carry control information rather than content. A response has a status code, headers, and a body. The methods are the whole vocabulary of the web in five words:

  • GET: fetch something. Should not change anything on the server.
  • POST: submit something, or ask for an action.
  • PUT and PATCH: replace or modify something that exists.
  • DELETE: remove something.

The rule underneath is that GET should be safe to repeat. Your browser retries failed GETs, prefetches links, and crawls pages. If GET could charge a card, the web could not work.

Status codes are grouped by their first digit, which is enough to know what kind of problem you have: 2xx worked, 3xx look elsewhere, 4xx you asked wrong, 5xx the server failed. Distinguishing them is a real skill, because “404” means the resource is missing while “502” means a gateway in front of your server is broken. Both look like failure to a user and mean completely different things to whoever fixes it.

Real example: curl -v https://example.com prints the exact conversation above, headers and all, in order. Reading HTTP with curl is the fastest way to see that a web framework is just a library that builds this text and parses the reply.

Stateless, and where state comes from

HTTP is stateless6: the server does not remember your previous request. Each request stands alone, and the client carries everything needed to identify itself.

That is a deliberate design, and it is the reason the web scaled to enormous traffic. Any server in a pool can answer any request. A machine can crash and another takes over instantly. There is no shared session, no sticky routing, no single point of failure.

The cost is that servers cannot remember anything by themselves, so state has to travel with every request. Cookies7 are that mechanism: small pieces of data the server sets, the browser stores, and sends back on every subsequent request to that domain.

server → Set-Cookie: session=abc123
client → stores it
client → GET /cart      Cookie: session=abc123
client → GET /checkout  Cookie: session=abc123

The server looks up abc123 in its own store, so the reference travels while the state stays put. When you clear cookies, the server loses its pointer to you. That is statelessness working exactly as designed, with the state living somewhere else.

Three versions of HTTP

The protocol is old and the transport under it changed under its feet, so HTTP evolved in steps, each fixing the previous version’s worst problem.

HTTP/1.0 opened a fresh connection per request. Simple, and expensive: every request paid for a full handshake.

HTTP/1.1 added keep-alive, so one connection carries many requests. It also allowed pipelining: send request 2 before request 1’s response arrives. Both sound like wins and were both nightmares, for the same reason. HTTP/1.1 processes responses strictly in order, so if request 1 is slow, every response behind it waits, even if their data is already sitting there. This is head-of-line blocking again, this time at the application layer, and it is why real HTTP/1.1 sites open six connections per browser tab and shard their domain across hostnames.

HTTP/28 fixed that by changing the format while keeping the semantics:

  • Multiplexing: many requests in flight on one connection, each with its own id, so a slow response no longer blocks the others.
  • Binary framing: a compact binary header instead of text, which is faster to parse and smaller on the wire.
  • Header compression: HPACK9 removes headers you already sent on the same connection, since browsers repeat the same ones constantly.
  • Server push, later deprecated, for sending resources before they were asked for.

HTTP/310 moved HTTP onto QUIC11, which runs on UDP and does its own reliability. The gain is not speed, it is independence: HTTP/3 no longer depends on the transport protocol that lives in the operating system kernel. A connection can be migrated to a new network path, several requests lose packets without stalling each other, and encryption is not an optional add-on bolted on top but part of how the connection is established.

HTTP/1.1   many connections · requests in order · blocking
HTTP/2     one connection · requests multiplexed · still TCP underneath
HTTP/3     one connection per origin · multiplexed · QUIC · built-in encryption

Each version of HTTP was waiting for the transport layer to catch up. HTTP/2 waited years for the operating system’s TCP. HTTP/3 stopped waiting and built its own.

Beyond request and response

Two extensions are worth knowing because they explain how modern web applications feel.

WebSockets12 start as an ordinary HTTP request and then keep the connection open, turning it into a full-duplex channel. Both sides can push messages at any time, with no polling and no request per update. That is what makes a chat app live, a dashboard update, or a collaborative document work without the server knowing in advance what you will ask for.

REST13 is not a protocol feature but a style of using HTTP that the web settled on: address your resources with URLs, let the method say the action, keep responses cacheable, and let the server store nothing about the conversation. Most of the design guidance for HTTP APIs is a restatement of these ideas.

The big picture

Layer four got bytes to a program. The top layer decides what those bytes mean. A server waits, a client asks, and a URL names the thing being asked for across all the layers at once. DNS turns the human name into an address through a distributed hierarchy that caches aggressively enough to work at internet scale. HTTP is a text request and response with five methods, a handful of status classes, and no memory between requests, which is exactly why it scales and why state has to travel in cookies. HTTP/2 multiplexed to escape head-of-line blocking; HTTP/3 built its own transport to stop depending on the kernel’s. WebSockets turn a request into a live channel, and REST is the shape most of the web settled on.

a URL names it:  scheme · host · port · path
DNS resolves it: name → address, cached until the TTL
HTTP asks for it: method · path · headers → status · body
state travels:    cookies carry a reference, the server holds the state

One layer left, and it is the one that keeps being skipped. The next post opens network security: what happens when the network is shared with strangers, why every byte above is currently readable by anyone on the path, and how a handshake nobody can fake turns a public road into a private conversation.

Footnotes

  1. URL - Wikipedia

  2. Domain Name System - Wikipedia

  3. Time to live - Wikipedia

  4. Domain Name System record types - Wikipedia

  5. Hypertext Transfer Protocol - Wikipedia

  6. Stateless protocol - Wikipedia

  7. HTTP cookie - Wikipedia

  8. HTTP/2 - Wikipedia

  9. HPACK - Wikipedia

  10. HTTP/3 - Wikipedia

  11. QUIC - Wikipedia

  12. WebSocket - Wikipedia

  13. Representational state transfer - Wikipedia