Building a Web Server


Computing 101.

Now that you know how to write and debug assembly, it is time to do something real! In this module, you will develop the skills needed to build a web server from scratch, starting with a simple program and progressing to handling multiple HTTP GET and POST requests. Good luck!


As you proceed in your journey, remember your system call table.


Lectures and Reading


Challenges

Your first task is to create the simplest possible program—one that immediately terminates when run. In this challenge, you will use the exit syscall, which is responsible for ending a process and returning an exit status to the operating system. This syscall takes a single argument: the exit status (with 0 typically indicating success). Understanding how to cleanly exit a program is crucial because it ensures your process communicates its completion state properly.

In this challenge, you’ll begin your journey into networking by creating a socket using the socket syscall. A socket is the basic building block for network communication; it serves as an endpoint for sending and receiving data. When you invoke socket, you provide three key arguments: the domain (for example, AF_INET for IPv4), the type (such as SOCK_STREAM for TCP), and the protocol (usually set to 0 to choose the default). Mastering this syscall is important because it lays the foundation for all subsequent network interactions.

After creating a socket, the next step is to assign it a network identity. In this challenge, you will use the bind syscall to connect your socket to a specific IP address and port number. The call requires you to provide the socket file descriptor, a pointer to a struct sockaddr (specifically a struct sockaddr_in for IPv4 that holds fields like the address family, port, and IP address), and the size of that structure. Binding is essential because it ensures your server listens on a known address, making it reachable by clients.

With your socket bound to an address, you now need to prepare it to accept incoming connections. The listen syscall transforms your socket into a passive one that awaits client connection requests. It requires the socket’s file descriptor and a backlog parameter, which sets the maximum number of queued connections. This step is vital because without marking the socket as listening, your server wouldn’t be able to receive any connection attempts.

Once your socket is listening, it’s time to actively accept incoming connections. In this challenge, you will use the accept syscall, which waits for a client to connect. When a connection is established, it returns a new socket file descriptor dedicated to communication with that client and fills in a provided address structure (such as a struct sockaddr_in) with the client’s details. This process is a critical step in transforming your server from a passive listener into an active communicator.

Now that your server can establish connections, it’s time to learn how to send data. In this challenge, your goal is to send a fixed HTTP response (HTTP/1.0 200 OK\r\n\r\n) to any client that connects. You will use the write syscall, which requires a file descriptor, a pointer to a data buffer, and the number of bytes to write. This exercise is important because it teaches you how to format and deliver data over the network.

In this challenge, your server evolves to handle dynamic content based on HTTP GET requests. You will first use the read syscall to receive the incoming HTTP request from the client socket. By examining the request line--particularly, in this case, the URL path--you can determine what the client is asking for. Next, use the open syscall to open the requested file and read to read its contents. Send the file contents back to the client using the write syscall. This marks a significant step toward interactivity, as your server begins tailoring its output rather than simply echoing a static message.

Previously, your server served just one GET request before terminating. Now, you will modify it so that it can handle multiple GET requests sequentially. This involves wrapping the accept-read-write-close sequence in a loop. Each time a client connects, your server will accept the connection, process the GET request, and then cleanly close the client session while remaining active for the next request. This iterative approach is essential for building a persistent server.

To enable your server to handle several clients at once, you will introduce concurrency using the fork syscall. When a client connects, fork creates a child process dedicated to handling that connection. Meanwhile, the parent process immediately returns to accept additional connections. With this design, the child uses read and write to interact with the client, while the parent continues to listen. This concurrent model is a key concept in building scalable, real-world servers.

Expanding your server’s capabilities further, this challenge focuses on handling HTTP POST requests concurrently. POST requests are more complex because they include both headers and a message body. You will once again use fork to manage multiple connections, while using read to capture the entire request. Again, you will parse the URL path to determine the specified file, but this time instead of reading from that file, you will instead write to it with the incoming POST data. In order to do so, you must determine the length of the incoming POST data. The obvious way to do this is to parse the Content-Length header, which specifies exactly that. Alternatively, consider using the return value of read to determine the total length of the request, parsing the request to find the total length of the headers (which end with \r\n\r\n), and using that difference to determine the length of the body--this seemingly more complicated algorithm may actually be easier to implement. Finally, return just a 200 OK response to the client to indicate that the POST request was successful.

In the final challenge, your server must seamlessly support both GET and POST requests within a single program. After reading the incoming request using read, your server will inspect the first few characters to determine whether it is dealing with a GET or a POST. Depending on the request type, it will process the data accordingly and then send back an appropriate response using write. Throughout this process, fork is employed to handle each connection concurrently, ensuring that your server can manage multiple requests at the same time. After completing this, you will have built a simple, but fully functional, web server capable of handling different types of HTTP requests.


30-Day Scoreboard:

This scoreboard reflects solves for challenges in this module after the module launched in this dojo.

Rank Hacker Badges Score