BYTESTACKDIY
Build Your Own HTTP ServerBuild Your Own HTTP Server
Build / 01PythonStep 2 of 10

How a Browser Reaches Your Server

Trace a request through DNS, TCP and HTTP before writing any networking code.

Previously, you ran a command python3 -m http.server 8000 --bind 127.0.0.1 and then you typed http://localhost:8000/index.html in your browser's address bar, pressed Enter, and 'Hi from the server' appeard. Your browser started with nothing but a line of text, and your server was a process quietly waiting on a port.

It felt instant, but a lot happend in between and questions arise:

  • How did that text turn into a connection?
  • How did that connection turn into a page?
  • How does the browser reach your python server?

Several systems cooperates before a server sees a request. Keeping their jobs separate will make the journey in later chapters easier to reason about. Let's follow the journey of a request.

Start with a URL

When you enter a URL in the browser and press Enter, your browser try to figure out two things:

  • Which machine should it connect to?
  • Which service on that machine should receive the request?

An address is not one thing. It is four small pieces glued together:

http://localhost:8000/index.html
└─┬─┘  └───┬───┘└─┬─┘└────┬────┘
scheme    host  port    path
  • scheme (http) tells the browser which protocol to speak.
  • hostname (localhost) says which machine to talk to.
  • port (8000) says which service on that machine.
  • path (/index.html) says what we want from that service.

DNS finds an address

Networks route by number, not by name. Before anything can be sent, the browser needs to turn a public hostname into an IP address.

Let's take google.com as an example.

When your browser needs the IP address for google.com. The browser first checks its own cache, then the operating system's. If neither knows the answer, the system asks a DNS resolver, usually run by your ISP or a public service like Google Public DNS. The resolver looks the name up through the DNS hierarchy, remembers the answer for next time, and hands it back.

For localhost no lookup is needed. It is a reserved name that always means "this computer", and it points to the loopback address 127.0.0.1. Traffic sent there never leaves your machine and DNS resolution is unnecessary.

TCP creates a byte stream

Now that the browser has an IP address and a port. We can name one exact address: 127.0.0.1:8000.

Your server is already running. The python3 -m http.server command created a TCP Socket, attached it to port 8000 and told the operating system to hold incoming callers until the program is ready to answer them. The browser knocks, the two sides exchange a short greeting and thats what we call the TCP handshake and a connection is established between the browser and your server.

TCP gives HTTP two things it depends on:

  • Reliable: lost pieces are sent again, so nothing goes missing.
  • Ordered: bytes arrive in the order they were sent.

The operating system handles the TCP connection process. TCP does not know about HTTP methods, headers, JSON or HTML. It does not even provides structure. It carries a plain stream of bytes with no built-in "message starts here" or "message ends here". HTTP has to supply that itself, and we will see how in a moment.

HTTP gives the bytes meaning

Now that the connection is established. The browser sends a request over that connection. It is plain text. This is the part where the path, which we set aside earlier, finally shows up. The example below was captured from curl and a browser also sends the same shape with more headers:

GET /index.html HTTP/1.1
Host: localhost:8000
User-Agent: curl/8.22.0
Accept: */*

Read it line by line:

  • The request line: a method (GET, meaning "give me this"), a target (/index.html) and a protocol version.
  • Headers: one Name: value pair per line, with extra information such as which host the request is for.
  • A blank line: this ends the headers. It is how the server knows the request is complete.

Every line ends with the two characters \r\n (carriage return and line feed), so the end of the headers is the four bytes \r\n\r\n. Finding those four bytes is the core job of the server we will write.

The response will follow the same broad pattern. The server reads the request, finds index.html in its folder, and writes back a response with the same shape: a status line, headers, a blank line, then the content.

HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.14.7
Date: Sun, 11 Oct 2026 08:08:58 GMT
Content-type: text/html
Content-Length: 240
Last-Modified: Sat, 10 Oct 2026 21:22:47 GMT

<!DOCTYPE html>
<html lang="en">
  ...
</html>

(The Server, Date and Last-Modified lines will differ on your machine.)

  • 200 OK is the status: it worked.
  • Content-Type: text/html tells the browser what the bytes are, so it knows to render them as a page rather than show them as text.
  • Content-Length: 240 tells the browser how many bytes of content follow. That is how the browser knows when the response is finished.
  • After the blank line comes the body: the 240 bytes of your index.html.

The browser reads that Content-Type, renders the HTML, and "Hi from the server" appears on screen. You may notice the response says HTTP/1.0: Python's built-in server answers in the older dialect. Ours will speak HTTP/1.1.

Observe a real request

Let's try a real server on the internet. example.com is served over plain HTTP, so curl's verbose mode shows everything:

curl -v http://example.com

Identify the connection, request headers, status line and response headers.

Now that we know how a client reaches your server, we can finally start building. Next, we open a TCP socket. You can close the python's http.server service now if you haven't yet.