Building a HTTP Server in C
I decided it would be a great idea to build a HTTP server in C to learn more about the language. C always felt like the final boss of a language when I first learnt how to program as a teenager. And somehow, I managed to get a computer science degree without touching the language. And as a Pentester, I rarely program on the job which is unfortunate.
Why learn C all of the sudden? Well C is a simple but also an unforgiving language, letting you do almost anything. Want to access an index outside an array? C will happily let you do that. It therefore requires a solid grasp of memory management, data types and pointers to avoid memory errors and security vulnerabilities. I think the best way to learn a programming language is to build projects. Books and tutorials can only get you so far in becoming proficient in any language as they unfortunately lack one key aspect, problem solving. When I started this project, problems naturally kept appearing, but I was resilient enough to solve them. Although initially frustrated, I slowly started enjoying the process as I discovered new solutions to problems.
The Beginning
It was surprisingly quick to get a server running as importing the sys/socket.h
header file did most of the heavy lifting. I then followed a basic HTTP server tutorial to get the server to send a basic message. Although the server did not conform to the HTTP RFC Specification, I was happy enough to receive a simple message.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
char serverMessage[256] = "You have reached the server";
int serverSocket;
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(1337);
serverAddress.sin_addr.s_addr = INADDR_ANY;
if(bind(serverSocket, (struct sockaddr*) &serverAddress, sizeof(serverAddress)) == -1) {
printf("Bind not successful");
return 1;
}
listen(serverSocket, 5);
int clientSocket;
clientSocket = accept(serverSocket, NULL, NULL);
send(clientSocket, serverMessage, sizeof(serverMessage), 0);
close(serverSocket);
return 0;
}
Hello World
The next step was to send a hello world message, a ritual that every programmer performs. There were two requirements: the first was sending the correct status-line
and the second was sending message-body
. Reading the RFC shows how to craft a HTTP response.
Response = Status-Line
*(( general-header
| request-header
| entity-header ) CRLF)
CRLF
[ message-body ]
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
For a standard success response, the status line would be HTTP/1.1 200 OK\r\n
. The message-body
can be anything but for this project, we want it to be HTML. Instead of writing the HTML content as a string in C, I thought it would be better to create an index.html
file within the project directory and then make the server read its content. I also wanted the server to only send the Hello World message if the user requested the correct page; otherwise, return a Page not Found error.
The diagram below encapsulates my design process:
| Client | | Server |
Send Request - - > GET /<page> HTTP/1.1 - - > Parse Request
| [Request Heders] |
| |
| v
| <page>
| |
| |
| V
| ReadFile ./pages/<page>
| |
| Page | Page Does
| Exist V Not Exist
| |- - - - - - - - - -|
| | |
| | |
| < - - - - HTTP/1.1 200 OK\r\n\r\n < - + |
| [Page Content] |
| |
| < - - - - HTTP/1.1 404 NOT FOUND\r\n\r\n <- - - - - - - - +
GIFs were not GIFFING
I wanted to add a few GIFs to my project to add some spice to the website. I thought it would be a simple process where I just need to upload the GIF files to the project and then link them within the HTML. It was not as simple. When the server sends HTTP data, the header and body can only be sent in a single buffer, so they have to be combined before being sent. The solution I came up with used the strcat
function to append the body to the HTTP header. Reading the strcat
manual revealed the mistake I made:
char *strcat(char *dest, const char *src); The strcat() function appends the src string to the dest string, overwriting the terminating null byte (’\0’) at the end of dest, and then adds a terminating null byte.
Strings are represented as an array of characters with a null-byte at the end. GIFs are plagued with null bytes, so the program would only send a portion of the GIF, specifically its content up until the first null-byte. Instead of using strcat
, I used memcpy
and provided header and body length in bytes as additional function arguments.
int sendResponse(int socket, const char* header, int headerLen, const char* body, int bodyLen) {
char* response = (char*) calloc(headerLen+bodyLen, sizeof(char));
memcpy(response, header, headerLen);
memcpy(response+headerLen, body, bodyLen);
send(socket, response, headerLen+bodyLen, 0);
free(response);
return 0;
}
IT’S SHOWTIME
With some refactoring, a bit of CSS magic and GIFs, I present to you my finest piece of work:
Here’s a link to the repository and I’m well aware that I’ve probably committed several C programming war crimes, but I’m proud to showcase what I’ve built.
Surviving in the Age of AI
I could have easily used ChatGPT to help me implement the server then write the blogpost within a few hours. But I would have missed the various learning opportunities by taking the easy route. Through hours of debugging, tinkering with the code and reading manual pages, it’s now cemented in my head some core C programming concepts. Even writing this blogpost has been a huge challenge as writing does not come naturally to me; I could have evaded the writing blocks by simply asking the AI to write for me. But I was determined enough to not give in and hopefully my future self will appreciate it.
I’m not all anti AI as I do think AI can be a very useful tool in certain situations, but there’s no shortcut in becoming competent in a craft. Learning is not supposed to be easy otherwise everyone would be a genius.
With my first blog post written up, this is the first year that registering a domain has not gone to waste!
References