Back to blog
Write your own HTTP server from scratch using C
c
2025-02-14
25 min read

Write your own HTTP server from scratch using C

CNetworkingHTTPSystems Programming

Write your own HTTP server from scratch using C

Ever wondered how websites actually work under the hood? You type a URL, hit enter, and boom—a webpage appears. But what's really happening behind the scenes? Well, at the heart of it all is something called an HTTP server, and today, we're going to build one from scratch using C.

Now, before you panic and think, "C? Isn't that the scary low-level language from the 70s?" - relax. I promise it's not that bad.

What Even is an HTTP Server?

HTTP (HyperText Transfer Protocol) server is a program that listens for requests from clients (like your browser), processes those requests, and sends back the requested data. Websites like Google, Facebook, and even this very blog are all powered by HTTP servers.

How Does an HTTP Server Work?

Here's a simple breakdown:

  1. A client (browser) sends an HTTP request
  2. The server reads the request and figures out what the client wants
  3. The server processes the request and prepares a response (like an HTML page or an image)
  4. The server sends the response back to the client
  5. The browser renders the response, and you see a webpage!

What's a Socket? Why Are We Using It?

Sockets are how computers talk to each other over a network. They allow a client and a server to send and receive data, just like a phone call.

When you connect to a website, your browser opens a socket to the server. The server then listens for incoming requests through its socket and responds accordingly.

What's a Thread? Why Do We Need It?

A thread is like an extra worker. Instead of handling one request at a time, our HTTP server will use threads to handle multiple requests simultaneously.

Headers Files

dev@gcc:~/c
dev@gcc:~/cgcc> [object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],
,[object Object],

,[object Object],
,[object Object],

Understanding main()

dev@gcc:~/c
dev@gcc:~/cgcc> [object Object], ,[object Object],[object Object], {
  ,[object Object], server_fd;
  ,[object Object],

  ,[object Object], ((server_fd = socket(AF_INET, SOCK_STREAM, ,[object Object],)) == ,[object Object],) {
    perror(,[object Object],);
    ,[object Object],(EXIT_FAILURE);
  }

  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(PORT);

  ,[object Object], (bind(server_fd, (,[object Object], sockaddr *)&server_addr, ,[object Object],(server_addr)) < ,[object Object],) {
    perror(,[object Object],);
    ,[object Object],(EXIT_FAILURE);
  }

  ,[object Object], (listen(server_fd, ,[object Object],) < ,[object Object],) {
    perror(,[object Object],);
    ,[object Object],(EXIT_FAILURE);
  }

  ,[object Object],(,[object Object],, PORT);
  
  ,[object Object], (,[object Object],) {
    ,[object Object],
    ,[object Object], client_addr_len = ,[object Object],(client_addr);
    ,[object Object], *client_fd = ,[object Object],(,[object Object],(,[object Object],));

    ,[object Object], ((*client_fd = accept(server_fd, (,[object Object], sockaddr *)&client_addr, &client_addr_len)) < ,[object Object],) {
      perror(,[object Object],);
      ,[object Object],;
    }

    ,[object Object], thread_id;
    pthread_create(&thread_id, ,[object Object],, handle_client, (,[object Object], *)client_fd);
    pthread_detach(thread_id);
  }
}

Understanding handle_client()

dev@gcc:~/c
dev@gcc:~/cgcc> [object Object], *,[object Object],[object Object], {
    ,[object Object], client_fd = *((,[object Object], *)arg);
    ,[object Object], *buffer = (,[object Object], *),[object Object],(BUFFER_SIZE * ,[object Object],(,[object Object],));

    ,[object Object], bytes_received = recv(client_fd, buffer, BUFFER_SIZE, ,[object Object],);
    ,[object Object], (bytes_received > ,[object Object],) {
        ,[object Object], regex;
        regcomp(&regex, ,[object Object],, REG_EXTENDED);
        ,[object Object], matches[,[object Object],];

        ,[object Object], (regexec(&regex, buffer, ,[object Object],, matches, ,[object Object],) == ,[object Object],) {
            buffer[matches[,[object Object],].rm_eo] = ,[object Object],;
            ,[object Object], ,[object Object], *url_encoded_file_name = buffer + matches[,[object Object],].rm_so;
            ,[object Object], *file_name = url_decode(url_encoded_file_name);

            ,[object Object], file_ext[,[object Object],];
            ,[object Object],(file_ext, get_file_extension(file_name));

            ,[object Object], *response = (,[object Object], *),[object Object],(BUFFER_SIZE * ,[object Object], * ,[object Object],(,[object Object],));
            ,[object Object], response_len;
            build_http_response(file_name, file_ext, response, &response_len);

            send(client_fd, response, response_len, ,[object Object],);

            ,[object Object],(response);
            ,[object Object],(file_name);
        }
        regfree(&regex);
    }
    close(client_fd);
    ,[object Object],(arg);
    ,[object Object],(buffer);
    ,[object Object], ,[object Object],;
}

Understanding build_http_response()

dev@gcc:~/c
dev@gcc:~/cgcc> [object Object], ,[object Object],[object Object], {
    ,[object Object], ,[object Object], *mime_type = get_mime_type(file_ext);
    ,[object Object], *header = (,[object Object], *),[object Object],(BUFFER_SIZE * ,[object Object],(,[object Object],));
    
    ,[object Object],(header, BUFFER_SIZE,
             ,[object Object],
             ,[object Object],
             ,[object Object],,
             mime_type);

    ,[object Object], file_fd = open(file_name, O_RDONLY);
    ,[object Object], (file_fd == ,[object Object],) {
        ,[object Object],(response, BUFFER_SIZE,
                 ,[object Object],
                 ,[object Object],
                 ,[object Object],
                 ,[object Object],);
        *response_len = ,[object Object],(response);
        ,[object Object],(header);
        ,[object Object],;
    }

    ,[object Object],
    fstat(file_fd, &file_stat);

    *response_len = ,[object Object],;
    ,[object Object],(response, header, ,[object Object],(header));
    *response_len += ,[object Object],(header);

    ,[object Object], bytes_read;
    ,[object Object], ((bytes_read = read(file_fd,
                            response + *response_len,
                            BUFFER_SIZE - *response_len)) > ,[object Object],) {
        *response_len += bytes_read;
    }

    ,[object Object],(header);
    close(file_fd);
}

Helper Functions

url_decode()

dev@gcc:~/c
dev@gcc:~/cgcc> [object Object], *,[object Object],[object Object], {
    ,[object Object], src_len = ,[object Object],(src);
    ,[object Object], *decoded = ,[object Object],(src_len + ,[object Object],);
    ,[object Object], decoded_len = ,[object Object],;

    ,[object Object], (,[object Object], i = ,[object Object],; i < src_len; i++) {
        ,[object Object], (src[i] == ,[object Object], && i + ,[object Object], < src_len) {
            ,[object Object], hex_val;
            ,[object Object],(src + i + ,[object Object],, ,[object Object],, &hex_val);
            decoded[decoded_len++] = hex_val;
            i += ,[object Object],;
        } ,[object Object], {
            decoded[decoded_len++] = src[i];
        }
    }

    decoded[decoded_len] = ,[object Object],;
    ,[object Object], decoded;
}

get_mime_type()

dev@gcc:~/c
dev@gcc:~/cgcc> [object Object], ,[object Object], *,[object Object],[object Object], {
    ,[object Object], (strcasecmp(file_ext, ,[object Object],) == ,[object Object], || strcasecmp(file_ext, ,[object Object],) == ,[object Object],) {
        ,[object Object], ,[object Object],;
    } ,[object Object], ,[object Object], (strcasecmp(file_ext, ,[object Object],) == ,[object Object],) {
        ,[object Object], ,[object Object],;
    } ,[object Object], ,[object Object], (strcasecmp(file_ext, ,[object Object],) == ,[object Object], || strcasecmp(file_ext, ,[object Object],) == ,[object Object],) {
        ,[object Object], ,[object Object],;
    } ,[object Object], ,[object Object], (strcasecmp(file_ext, ,[object Object],) == ,[object Object],) {
        ,[object Object], ,[object Object],;
    } ,[object Object], {
        ,[object Object], ,[object Object],;
    }
}

get_file_extension()

dev@gcc:~/c
dev@gcc:~/cgcc> [object Object], ,[object Object], *,[object Object],[object Object], {
    ,[object Object], ,[object Object], *dot = ,[object Object],(file_name, ,[object Object],);
    ,[object Object], (!dot || dot == file_name) {
        ,[object Object], ,[object Object],;
    }
    ,[object Object], dot + ,[object Object],;
}

Testing

Compile and run:

💻user@linux:~/bash
user@linux:~/bash$ gcc -o http_server http_server.c -lpthread
./http_server

Visit http://localhost:8080/index.html in your browser!

Conclusion

You've just built a fully functional HTTP server from scratch! This demonstrates the fundamentals of network programming, socket handling, and multithreading in C.

N

Nishant Gaurav

Full Stack Developer

Let Down (Choir Version) - Radiohead

0:00
0:00
nishant gaurav