Understanding Web Servers from Scratch
/ 4 min read
Updated:Table of Contents
I wanted to understand what actually happens between clicking a button in the browser and my controller method executing.
To explore this, I built a simple HTTP server in Java using raw sockets and implemented parts of the HTTP protocol from scratch.
Understanding the request lifecycle
- The request first reaches the operating system, not the application. The OS kernel handles the TCP handshake and temporarily stores the connection.
- Once the TCP handshake is completed, the connection does not immediately reach the application. Instead, the operating system places it in a backlog queue associated with the listening socket.
- Java provides ServerSocket API to abstract away the low-level details of TCP. Along with backlog size.
int port = 8080;ServerSocket serverSocket = new ServerSocket(port, 20);- The application thread blocks on
accept()waiting for connections.
Socket clientSocket = serverSocket.accept();This is the exact point where the kernel hands over a fully established TCP connection to the application.
- Once
accept()returns a socket, the connection is now fully inside the application layer.
At this point, the server must handle the request lifecycle:
- read HTTP request
- parse request line and headers
- execute business logic
- send response back
- Instead of processing the request inside the accept loop, the socket is handed off to a worker thread. This allows the accept loop to immediately go back and wait for new connections.
executorService.execute(() -> handleClient(clientSocket));- Now the worker thread takes over the connection. And handleClient method represents the full lifecycle of request processing for one client.
Now let’s put everything together and visualize the complete lifecycle of an HTTP request in a blocking server.
sequenceDiagram participant Client as Client (Browser) participant Kernel as OS Kernel (TCP/IP) participant Acceptor as Acceptor Thread (accept()) participant Pool as Worker Thread Pool participant Worker as Worker Thread participant Controller as Controller/Business Logic Client->>Kernel: TCP Handshake + Send HTTP Request Kernel->>Acceptor: Backlog → accept() unblocks Acceptor->>Pool: Hand off new Socket Pool->>Worker: Assign worker thread Worker->>Kernel: read() blocks until request bytes ready Kernel-->>Worker: Deliver HTTP request Worker->>Controller: Parse + Route request Controller-->>Worker: Execute logic, return response Worker->>Kernel: write() response to send buffer Kernel-->>Client: Send HTTP Response
What looks like a simple controller execution is actually a long chain of OS-level and application-level operations involving queues, threads, and the TCP stack.
Here is another depth diagram showing the same lifecycle but with more details on the OS and application interactions.
sequenceDiagram participant Client as Client (Browser/Frontend) participant Kernel as OS Kernel (TCP/IP Stack) participant ServerSocket as ServerSocket.accept() participant Executor as Thread Pool / Virtual Threads participant Handler as Controller/Handler Client->>Kernel: TCP SYN (open connection) Kernel-->>Client: TCP SYN-ACK Client->>Kernel: TCP ACK (connection established) Client->>Kernel: Send HTTP Request (GET /hello) Kernel->>ServerSocket: Place in backlog queue alt Backlog full Kernel-->>Client: Connection refused / dropped else Backlog accepted ServerSocket->>Executor: Hand off Socket task alt Thread pool saturated Executor-->>Client: Request waits in queue<br/>or times out else Thread available Executor->>Handler: Read request line alt Slow client or socket buffer delay Handler-->>Handler: Blocked on socket read end Handler->>Handler: Parse request & map to controller Handler->>Handler: Execute business logic Handler->>Executor: Write response Executor->>Kernel: Flush to TCP buffer alt Buffer full / client slow Kernel-->>Executor: Write blocks / delayed flush end Kernel-->>Client: Send HTTP Response end end
Source Code of Raw HTTP Server
The complete implementation of this raw HTTP server is available here:
https://github.com/sats17/under-the-hood-webserver/blob/master/src/thread_per_request/RawServer.java