DEV Community

Cover image for How to Build a Simple HTTP Server in C: A Beginner's Guide
Stella Achar Oiro for Zone01 Kisumu

Posted on

4 2 1

How to Build a Simple HTTP Server in C: A Beginner's Guide

Building practical applications remains the best way to master C programming. A web server project offers an excellent opportunity to learn systems programming, networking fundamentals, and HTTP protocols simultaneously.

The C HTTP server project described below emerged from collaboration with Antony Oduor, a fullstack developer at Zone 01 Kisumu who leads the development efforts, while I've partnered to lead the documentation initiatives.

Note: The project currently exists as a work in progress. Both codebase and documentation undergo continuous improvement and expansion.

Why Build a Web Server in C?

According to Antony Oduor:

"C gives you the control that modern frameworks hide away. Understanding low-level implementation makes you a more effective developer across all languages."

The benefits of developing a web server in C include:

  • Deep protocol understanding: Gain intimate knowledge of how HTTP actually works
  • Networking insights: Learn socket programming fundamentals applicable across platforms
  • Memory management mastery: Practice proper allocation, tracking, and freeing of resources
  • Performance optimization: Create highly efficient server code without framework overhead
  • Architectural knowledge: Understand how larger systems get built from smaller components

Project Architecture

The server follows a modular design with clear separation of concerns:

c-http-server/
├── src/
│   ├── main.c            # Program entry point
│   └── lib/              # Component libraries
│       ├── env/          # Environment configuration
│       ├── http/         # Protocol implementation
│       └── net/          # Socket programming
└── Makefile              # Build automation
Enter fullscreen mode Exit fullscreen mode

Component Flow Diagram

The server architecture diagram illustrates the relationships between components:

Server Architecture

Each component serves a specific purpose:

  1. main.c: Initializes the server, configures settings, and defines routes
  2. net library: Handles all socket operations and client connection management
  3. http library: Processes raw requests, manages routing, and generates responses
  4. env library: Provides configuration through environment variables

HTTP Request Lifecycle

The request lifecycle diagram demonstrates how data flows through the system:

HTTP Request Flown

A detailed examination of each step reveals:

  1. Client Connection: Browser connects to the server socket
  2. Request Reading: Server accepts the connection and reads raw HTTP data
  3. Parsing: The raw string gets converted into a structured Request object
  4. Route Matching: The URL path determines which handler function executes
  5. Response Generation: The handler produces HTML or other content
  6. Response Transmission: Data flows back to the client
  7. Connection Closure: The socket closes to free resources

Network Layer Deep Dive

The networking component abstracts socket operations into clear functions:

int listener(char* host, int port) {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    // Socket configuration code...
}
Enter fullscreen mode Exit fullscreen mode

Key network layer aspects include:

  • Socket creation: Establishes an endpoint for communication
  • Address binding: Associates the socket with a specific port and address
  • Connection listening: Prepares for incoming client connections
  • Client acceptance: Creates individual connections for each request
  • Data transmission: Sends and receives bytes efficiently

HTTP Parser Implementation

The HTTP parser transforms raw strings into structured data:

Request* parse_http_request(const char* raw_request) {
    // Parsing implementation using state machine...
}
Enter fullscreen mode Exit fullscreen mode

The parser uses a state machine approach to process HTTP data:

  1. Parse the request line (GET /path HTTP/1.1)
  2. Extract headers (Name: Value)
  3. Identify request body if present
  4. Populate a structured Request object

Router System

The routing mechanism maps URL paths to handler functions:

Router router = {{"/","/about",NULL}, {Index,About,NULL}};
Enter fullscreen mode Exit fullscreen mode

Under the hood, the router:

  1. Compares the requested path against registered patterns
  2. Invokes the appropriate handler function when matched
  3. Generates a 404 response when no match exists

Running the Server

Starting the server requires minimal setup:

# Clone the repository
git clone https://github.com/oduortoni/c-http-server.git
cd c-http-server

# Build and launch
make

# Visit in browser: http://127.0.0.1:9000
Enter fullscreen mode Exit fullscreen mode

Configuration through environment variables allows customization:

# Set custom port
export PORT=8080

# Set custom host
export HOST=0.0.0.0

# Build and run with custom settings
make
Enter fullscreen mode Exit fullscreen mode

Creating Custom Routes

Adding new functionality requires three steps:

  1. Define a handler function:
int ContactPage(ResponseWriter w, Request r) {
    // Generate HTML content...
    return 0;
}
Enter fullscreen mode Exit fullscreen mode
  1. Register the route in main.c:
http.HandleFunc("/contact", ContactPage);
Enter fullscreen mode Exit fullscreen mode
  1. Update the router configuration:
Router router = {{"/", "/about", "/contact", NULL}, 
                 {Index, About, ContactPage, NULL}};
Enter fullscreen mode Exit fullscreen mode

Form Processing

The server includes built-in form handling capabilities:

  • Parses form submissions from POST requests
  • Extracts individual form fields from the request body
  • URL-decodes field values for proper character representation
  • Generates appropriate responses based on submitted data

Advanced Features Under Development

The project continues to evolve with several features in active development:

  1. Static file serving: Deliver images, stylesheets, and client-side scripts
  2. Enhanced error handling: More detailed error responses and logging
  3. Response status codes: Full implementation of HTTP status responses
  4. Memory optimization: Improved buffer management for large requests
  5. Concurrency: Multi-threaded request handling

Key Learning Opportunities

Studying the implementation offers valuable lessons in several fundamental programming concepts:

1. C Programming Patterns

Structures

The code uses structures to organize related data, creating clean abstractions:

struct Request {
    char method[MAX_METHOD_LEN];
    char path[MAX_PATH_LEN];
    char version[MAX_VERSION_LEN];
    struct Header headers[MAX_HEADERS];
    int header_count;
    char body[MAX_BODY_LEN];
    size_t body_length;
};
Enter fullscreen mode Exit fullscreen mode

Function Pointers

Function pointers enable flexible behavior and callback patterns:

typedef int(*HandlerFunc)(ResponseWriter w, Request r);
struct Router {
    char* patterns[50];
    HandlerFunc handlers[50];
};
Enter fullscreen mode Exit fullscreen mode

Memory Management

Proper allocation and freeing prevents memory leaks:

Request* req = parse_http_request(buffer);
// Use request...
free_request(req);  // Clean up when done
Enter fullscreen mode Exit fullscreen mode

2. Network Programming

Socket Creation

Creating communication endpoints:

int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0) {
    perror("socket() could not create a socket");
    exit(1);
}
Enter fullscreen mode Exit fullscreen mode

Connection Handling

Accepting and processing client connections:

int client_conn = accept(server_socket, (struct sockaddr*)&client_addr, &client_addrlen);
printf("Accepted connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
Enter fullscreen mode Exit fullscreen mode

3. Protocol Implementation

HTTP Request Parsing

Breaking down HTTP protocol messages:

case PARSE_METHOD: {
    char* method_ptr = req->method;
    while (*p && !isspace(*p)) {
        *method_ptr++ = *p++;
    }
    *method_ptr = '\0';
    state = PARSE_PATH;
}
Enter fullscreen mode Exit fullscreen mode

Header Processing

Extracting and storing HTTP headers:

for (int i = 0; i < req->header_count; i++) {
    if (strcasecmp(req->headers[i].name, "Content-Length") == 0) {
        content_length = atoi(req->headers[i].value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Response Formation

Constructing properly formatted HTTP responses:

snprintf(response, sizeof(response),
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html\r\n"
    "\r\n%s", html_content);
Enter fullscreen mode Exit fullscreen mode

4. State Machines

Parser States

Using enumerated states to track parsing progress:

enum ParseState {
    PARSE_METHOD, PARSE_PATH, PARSE_VERSION,
    PARSE_HEADER_NAME, PARSE_HEADER_VALUE,
    PARSE_BODY, PARSE_COMPLETE, PARSE_ERROR
};
Enter fullscreen mode Exit fullscreen mode

State Transitions

Transitioning between states based on input:

switch (state) {
    case PARSE_METHOD:
        // Process method...
        state = PARSE_PATH;
        break;
    case PARSE_PATH:
        // Process path...
        state = PARSE_VERSION;
        break;
}
Enter fullscreen mode Exit fullscreen mode

Error Handling

Detecting and handling error conditions in the state machine:

if (state == PARSE_ERROR) {
    free(req);
    return NULL;
}
Enter fullscreen mode Exit fullscreen mode

5. Modular Design

Component Separation

Organizing code into logical directories and files:

src/lib/net/listener.c  // Network functions
src/lib/http/handle.c   // HTTP processing
src/lib/env/get_env_variable.c  // Configuration
Enter fullscreen mode Exit fullscreen mode

Interface Definitions

Creating clear interfaces between components:

// In header.h
int serve(char *host, Processor processor);
int listener(char* host, int port);

// Implementation in separate files
Enter fullscreen mode Exit fullscreen mode

Composition

Building complex behavior from simple components:

// Composing components together
Processor processor = {handle_connection, &router};
serve(host, processor);
Enter fullscreen mode Exit fullscreen mode

Each of these concepts builds essential programming skills that apply across multiple domains, not just web servers. Understanding these patterns helps in building robust, maintainable software systems regardless of the specific application domain.

Building an HTTP server in C provides fundamental knowledge applicable across all web development. The complete project with documentation reveals how seemingly complex systems can be built through well-designed components working together.

The skills gained from exploring low-level server implementation remain valuable regardless of which languages or frameworks become popular in the future. Understanding what happens "under the hood" makes for more effective development at all levels.

Explore the complete project on GitHub to deepen your understanding of both C programming and web servers.

Antony Oduor is a Fullstack Developer at Zone 01 Kisumu, who leads the development of the project.

Quadratic AI

Quadratic AI – The Spreadsheet with AI, Code, and Connections

  • AI-Powered Insights: Ask questions in plain English and get instant visualizations
  • Multi-Language Support: Seamlessly switch between Python, SQL, and JavaScript in one workspace
  • Zero Setup Required: Connect to databases or drag-and-drop files straight from your browser
  • Live Collaboration: Work together in real-time, no matter where your team is located
  • Beyond Formulas: Tackle complex analysis that traditional spreadsheets can't handle

Get started for free.

Watch The Demo 📊✨

Top comments (1)

Collapse
 
rayjay profile image
Ray Jones

I like this, is there a part 2?

Image of Stellar post

How a Hackathon Win Led to My Startup Getting Funded

In this episode, you'll see:

  • The hackathon wins that sparked the journey.
  • The moment José and Joseph decided to go all-in.
  • Building a working prototype on Stellar.
  • Using the PassKeys feature of Soroban.
  • Getting funded via the Stellar Community Fund.

Watch the video

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️