Projects/Build an HTTP Server

Build an HTTP Server

An HTTP server is a socket that speaks a text protocol. You build the accept loop and the parser against concrete specs, so each layer is solid before the next goes on top.

36 lessonsMedium~20 min / lessonSocketsHTTP/1.1
The project

What you'll build over the next 36 lessons

Over 36 short lessons you build a working HTTP/1.1 server from a bare TCP socket up. You start with an accept loop that echoes bytes, then write a request parser (request line, headers, Content-Length bodies), a response serializer (status lines, headers, auto Content-Length), a router, and a static-file handler with content types.

The last chapter makes it robust and concurrent: 400/404/405 error responses, HEAD, query parameters, one goroutine per connection, keep-alive, Connection: close, chunked request bodies, and an access log.

The end result is a real, teaching-grade server you run and hit with curl or a browser — it serves static files and dynamic routes over persistent connections. It deliberately stops short of TLS, HTTP/2, and the parts of RFC 9110/9112 (caching, ranges, cookies, content negotiation) a production server adds on top.

build-an-http-server / lesson-01.md
Lesson 01 · Sockets

Listen and accept a connection

An HTTP server is a program that waits on a port for clients to connect. Today you open a listening socket and accept one connection, the raw foundation every later layer sits on.

The goal

Open a TCP listener on a port and accept a single incoming connection without error.

Start here - the target
TO DO
Scenario: A client can connect to the server
Givena server listening on an address the operating system assigns (port 0)
Whena client dials that same address
Thenthe server accepts the connection and reports no error
Background

Before any HTTP exists, there must be a socket: an operating-system handle for one end of a network connection. A server listens on an address, and each time a client connects, Accept hands back a fresh connection you can read from and write to.

Binding to port 0 asks the OS for any free port, which keeps tests from colliding on a fixed number. Read the real port back from the listener’s address so your test client knows where to dial. That is the whole handshake — bytes come next.

Make it work
ln, err := net.Listen("tcp", "127.0.0.1:0") // :0 = OS picks a free port
// ln.Addr() tells you which port you actually got
conn, err := ln.Accept() // blocks until a client connects
_ = conn
CheckpointDONE
Your server binds a port and accepts a connection. Commit.
Scope & extensions

Where this project stops - and where to go next

The finished server runs and correctly handles routing, static files, persistent connections, HEAD, and chunked request bodies end to end, but still lacks production hardening like connection timeouts, request size limits, and graceful shutdown.

Extend it next
  • Add read/write deadlines per connection so a slow or silent client can't hold a connection open forever
  • Cap the header block and body size to reject oversized requests instead of allocating unboundedly
  • Handle SIGINT/SIGTERM for graceful shutdown instead of dropping connections mid-flight
  • Support trailer headers after the final chunk of a chunked request body
  • Serve a directory listing when a directory is requested and has no index.html
  • Add TLS so the server can speak HTTPS
Recommended reading

Books & references that go deeper