Hi đź‘‹
My name is Eric, and I am a full-stack software engineer at ALX. I have extensive experience in DevOps, Front-end development, and Socket/Network programming while specializing in Back-end development.
My favorite programming language is C ✨ (I really enjoy coding in C). Perhaps it's because C was the first language I learned as a programmer, though I also code in other languages like Python and JavaScript.
The main thing
At ALX, where I study, we reached a point where we needed to create impressive projects to enhance our resumes as aspiring software developers.
The main challenge was deciding on a project idea; otherwise, I would have to work on the default project (a 2D Maze game built in C).
After days of contemplation, I decided to build an HTTP web server in C, leveraging its power and efficiency. Recognizing that this would be a challenging project, requiring extensive technical expertise and a logical workflow, I embraced the challenge. I thrive on tackling projects that push my boundaries.
Here are the workings of my web server:
With the goal of building a minimalist web server that I could enhance with additional features over time, I began with the basics. I created a TCP socket, bound it to my server's address, and set it to listen for incoming connections on a specific port. Initially, I used a buffer filled with static HTML content to respond to any requests from web clients (whether from a browser or curl), regardless of the request specifics. This approach allowed me to test the server's functionality.
Here was the first time my web server worked:
Seeing this, I was filled with excitement and proceeded to implement a configuration settings feature. This allowed me to configure which port the server listens on, what folder it serves web content from, and more. The challenge was creating a format for the configuration file. To keep it simple, easy to understand, and straightforward to write, I developed a very simple format similar to that of nginx.
server <SERVER_NAME>:
listen <PORT>
root <ROOT_FOLDER>
index index.html
location / :
check <URI> FOUND?200:404
During this project, I encountered a challenging bug: an invalid pointer! While developing the configuration engine—a tool that parses and processes the configuration file—I kept seeing a free(): invalid pointer
error. I searched my code thoroughly to ensure all allocated memory was properly freed, but couldn't identify the issue.
After an hour of frustration, I decided to use printf()
with the address-of-format specifier for debugging. By placing printf("Address of buff: %p\n", &MALLOCD_BUFF)
at various points, I tracked the addresses of buffers at different stages. This helped me discover that the buffer's address in memory was slightly higher when freed than when declared. The issue arose because, within the configuration processor, the pointer was incremented by one to skip initial tab characters in each line of the file.
Here's a diagram to better illustrate the problem:
# This is the configuration file
# The escape sequence <\t> signifies a tab
server example.com:
<\t>listen 80
<\t>root /var/www/html
<\t>index index.html index.eric.html
<\t><\t>location / :
<\t><\t><\t>check $uri $uri/ FOUND?200:404
/* Inside the configuration file processor */
char *line = NULL;
size_t linesize = 0;
getline(&line, &linesize, config_file); /* address of the first char pointed to by @line is #7ADF5431 */
/* Skip as many tabs found beginning the line */
while (*line == '\t')
line++; /* This statement moves the address #7ADF5431 to + 1 (next char) */
free(line); /* When freeing the line buffer, the wrong address is being freed */
/* It's freeing address #7ADF5431 + 1 + ... instead of address #7ADF5431 */
To solve this problem, I used a variable to count the number of addresses in memory moved forward by the statement line++
. and decrements them as required before freeing them, and that was it! I no longer get the error Invalid pointer
.
/* Inside the configuration file processor */
char *line = NULL;
size_t linesize = 0;
int memcount = 0; /* Memory address counter */
while (getline(&line, &linesize, config_file) != -1) /* Read each line in the file */
{
/* Cuts off all the starting tab characters */
while (*line == '\t')
{
line++; /* Move to the next character in memory */
memcount++; /* Count the number of characters moved to */
}
/* Skip commented lines */
if (*line == '#')
{
while (memcount > 0)
{
line--; /* Go back to the first address allocated in memory */
memcount--;
}
free(line); /* Now free the right address */
continue; /* Go to the next line in file. There is nothing to do here */
}
/* Process line */
...
/* Free line */
while (memcount > 0)
{
line--; /* Go back to the first address allocated in memory */
memcount--;
}
free(line); /* Now free the right address */
}
Now, I faced the trickiest part of the project—the workflow!
If you’re a socket programmer or a Linux enthusiast, you know that multi-tasking is crucial for web servers and any socket-related software/hardware. Through my research, I learned about race conditions, where multiple processes compete for a resource in a multiprocessing/multithreaded environment. One process might alter a resource, affecting the outcome for others.
To ensure a functional server, high levels of isolation and independence are essential. How did I achieve this? I avoided having any single function do too much, as overburdened functions become dependent on many things, causing conflicts between processes calling them. Instead, I created numerous functions and handlers, each responsible for a specific task. This approach maximized independence and ensured the overall functionality of the server.
With these strategies and more, I successfully built a fully functional web server in C.
Hello Dev Community,
I'm thrilled to be part of this amazing group. Does anyone know where I can find funding for my project to help expand it? If so, please reach out to me.
Connect with me:
Check out the source code:
GitHub - Don't forget to leave a star if you like it!
Top comments (0)