C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
223: Building a Basic HTTP Server in C
Think of an HTTP server like a fast-food drive-thru. First, the restaurant has to install a speaker box at a specific spot in the parking lot so customers know where to go. That's your socket and your port. Then, the employee inside just sits there waiting for the "ding" that signifies someone has pulled up. That's your listen call. When the ding happens, the employee opens a communication channel to hear the order. That's accept. Finally, the customer says, "I want a burger," the employee checks if they have burgers, and then hands a bag of food through the window. That's your read (the request) and write (the HTTP response).
In C, we don't have a high-level "Web Server" library in the standard toolkit. We have to build this plumbing manually using the Berkeley sockets API. It's a bit tedious, but once you do it, you'll realize that HTTP is really just a specific way of formatting strings sent over a TCP connection.
Setting Up the Socket Pipeline
To get this running, we need to tell the OS to reserve a port for us. I usually use port 8080 for local testing because ports below 1024 usually require root privileges, and I hate running my experimental code as root.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
// Create the socket file descriptor
server_fd = socket(AF_INET, SOCK_STREAM, 0);
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // Listen on all available interfaces
address.sin_port = htons(8080); // Convert port to network byte order
// Bind the socket to the address and port
bind(server_fd, (struct sockaddr *)&address, sizeof(address));
// Start listening for incoming connections
listen(server_fd, 3);
printf("Waiting for a connection on port 8080...\n");
}
Notice htons(). I see people forget this all the time. Computers disagree on whether the most significant byte comes first (Endianness). Network protocols demand Big-Endian, so we use "host-to-network short" to make sure the port number is readable by the network hardware.
Speaking the Language of the Browser
Once we accept() a connection, the browser sends us a block of text. It looks like GET /index.html HTTP/1.1. We don't actually need to parse the whole thing for a basic server; we just need to send back a response that the browser understands. If you send back random text, the browser will likely give you a "Malformed Response" error.
An HTTP response requires a header and a body, separated by a double newline. Here is how we'd handle a single request to serve a "System Status" page:
while(1) {
new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
char buffer[1024] = {0};
read(new_socket, buffer, 1024);
printf("Request received:\n%s\n", buffer);
// This is the "Bag of Food" - the HTTP Response
char *http_response =
"HTTP/1.1 200 OK\n"
"Content-Type: text/html\n"
"Content-Length: 52\n"
"\n"
"System Status: Online
";
write(new_socket, http_response, strlen(http_response));
close(new_socket);
}
I'm using a while loop here because a server that handles one request and then quits isn't much of a server. Just keep in mind that this specific implementation is "blocking." If one client has a slow connection, the whole server hangs until that write finishes. In a real-world production server, you'd use fork() or threads to handle each connection in parallel, but let's master the handshake first.
Closing the Connection Gracefully
In the code above, close(new_socket) is non-negotiable. If you forget to close the socket after sending the response, the browser will keep the connection open, waiting for more data, and you'll eventually leak all your available file descriptors. Your OS will eventually stop letting you open new sockets, and your server will crash. Always clean up your descriptors.
📋 Practical Task
Build a Custom Maintenance Page Server
Modify the server code from this lesson to implement a "Maintenance Mode" feature. Your server should do the following:
- Listen on port 8081.
- Instead of a "System Status: Online" message, return an HTTP 503 Service Unavailable status code.
- The HTML body should contain a heading that says "Under Maintenance" and a paragraph stating "We'll be back online at 12:00 PM UTC."
- Ensure the
Content-Lengthheader accurately reflects the size of your new HTML body.
Test your server by navigating to http://localhost:8081 in your web browser and verifying that the browser recognizes the 503 error status while displaying your custom message.
There are no comments for now.