<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Ali Naqvi</title><description>A blog about progress, technology, epistemology, the beginning of infinity, and more, by the software developer Ali Mohsin Naqvi</description><link>https://alimnaqvi.com</link><item><title>Podcasts that I recommend</title><link>https://alimnaqvi.com/blog/podcast-recommendations</link><guid isPermaLink="true">https://alimnaqvi.com/blog/podcast-recommendations</guid><description>A regularly updated list of podcast shows and episodes that I find the most insightful.</description><pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I listen to a lot of podcasts and have learned a great deal from them. Here I will share a categorized list of the &lt;a href=&quot;#shows&quot;&gt;shows&lt;/a&gt; as well as &lt;a href=&quot;#episodes&quot;&gt;episodes&lt;/a&gt; I found the most useful. Hopefully it can help you discover and learn something useful.&lt;/p&gt;
&lt;p&gt;They are listed roughly in descending order of when I listened to them. So the ones on top of the table reflect my latest thinking, while the ones on bottom could be things that I was interested in or agreed with in the past, but possibly not anymore.&lt;/p&gt;
&lt;h2&gt;Shows&lt;/h2&gt;
&lt;h3&gt;Honorable mentions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thedeeplife.com/listen/&quot;&gt;Deep Questions&lt;/a&gt;: Cal Newport’s approach to productivity (detailed in his book &lt;a href=&quot;https://www.amazon.com/Deep-Work-Focused-Success-Distracted/dp/1455586692&quot;&gt;Deep Work&lt;/a&gt;) has been an important influence on me. On the Deep Questions podcast, Cal answers questions and shares case studies about putting these ideas into practice in the real world.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sscpodcast.libsyn.com/&quot;&gt;Astral Codex Ten Podcast&lt;/a&gt;: Audio version of one of my favorite blogs, &lt;a href=&quot;https://www.astralcodexten.com/&quot;&gt;Astral Codex Ten&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Episodes&lt;/h2&gt;
&lt;p&gt;You might also be interested in &lt;a href=&quot;./podcast-app-comparison&quot;&gt;my post about detailed testing and comparison of podcast apps&lt;/a&gt;.&lt;/p&gt;</content:encoded><h:img src="/_astro/pexels-joshsorenson-1334603.C6CC5I6y.jpg"/><enclosure url="/_astro/pexels-joshsorenson-1334603.C6CC5I6y.jpg"/></item><item><title>Learning C as the first programming language</title><link>https://alimnaqvi.com/blog/learning-c-first</link><guid isPermaLink="true">https://alimnaqvi.com/blog/learning-c-first</guid><description>Why I&apos;m glad I learned C before any other programming language even if I might not use C in the real world.</description><pubDate>Fri, 24 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The question of which programming language is the best beginner language is a hotly debated one. When I was researching which language I should learn, the best advice I came across was from &lt;a href=&quot;https://podcasts.apple.com/dk/podcast/kopec-explains-software/id1518659193&quot;&gt;David Kopec&lt;/a&gt;, who &lt;a href=&quot;https://podcasts.apple.com/dk/podcast/what-is-a-programming-language/id1518659193?i=1000489023419&quot;&gt;advises&lt;/a&gt; to think about the following three things in this order of importance:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;More important than the choice of language is to &lt;strong&gt;pick a language and stick with it&lt;/strong&gt; for a while.&lt;/li&gt;
&lt;li&gt;Choose a language that is popular enough and has a welcoming community so that it has &lt;strong&gt;a lot of resources available to learn from&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The last thing to think about is to pick a language that is &lt;strong&gt;popular in the niche that you want to eventually develop for&lt;/strong&gt; (i.e., if you already have a specialty in mind, e.g., web, mobile, ML, OS, etc.).&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Popular languages&lt;/h2&gt;
&lt;p&gt;Let’s first look at the popularity of the top programming languages according to GitHub’s &lt;a href=&quot;https://github.blog/news-insights/octoverse/octoverse-2024/&quot;&gt;Octoverse 2024&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;GitHub-Octoverse-2024-top-programming-languages.webp&quot; alt=&quot;GitHub-Octoverse-2024-top-programming-languages.webp&quot;&gt;&lt;/p&gt;
&lt;p&gt;Different measures of popularity will give different rankings, but this picture gives us a great overview of the past 10 years in programming languages. I think five of the most commonly suggested programming languages to learn when you start out are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;C&lt;/li&gt;
&lt;li&gt;C++&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Looking at the above picture, it’s no surprise that the most common suggestion these days for the best first language is &lt;strong&gt;Python&lt;/strong&gt;. It has a relatively simple syntax, can be used for a variety of different applications, and is widely and increasingly used in the industry. Python is really not a bad choice at all and I almost went for it, especially because I was starting out during the GenAI hype, which massively boosted the Python hype. Even before the latest AI boom, Python’s popularity was growing. But now it has overtaken JavaScript as the most used language on GitHub.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt; has a massive developer base and has been the most popular programming language for many years, mainly thanks to it being the language of web browsers. JavaScript also gets a fair bit of criticism due to its perceived fundamental design flaws that go back to its history of originally being developed in just 10 days. Also, having such a simple and flexible syntax, it is very easy to write bad JavaScript code, and there is in fact a lot of bad JavaScript code out there. This is another major reason it is criticized so much. It is accused of promoting bad coding practices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Java&lt;/strong&gt; was once the rising star of programming languages due to it being platform-independent and its use in the Android ecosystem. Its popularity has taken a hit in recent years, mainly because Kotlin has become the preferred language for Android app development. Java is still a very popular and very important programming language, especially because many large corporations have built their backend systems in it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt; is the oldest programming language in our list (and in most lists of programming languages these days). More than 50 years after it was first created, it continues to rank among the top 10 most popular programming languages. C still powers an enormous amount of infrastructure in the world, ranging from the largest supercomputers to the smallest microcontrollers and embedded systems. It is a relatively low-level programming language. That means C gets you as close to the hardware as possible, short of using assembly language. It also means it leaves a lot to the programmer instead of automatically taking care of basic things like memory management for you. This makes it a somewhat “difficult” language in the world of today’s higher-level programming languages. Even though the language itself is quite small, C makes it very easy to make serious mistakes with consequences like crashing the system. So one always needs to be extra-careful when programming in it. And this is probably the main reason C has recently been falling out of favor, as newer languages like Go and Rust offer the performance and power of C without its drawbacks. But there is only so much you can expect from a 50-year-old programming language. Still, the case for C as the first programming language is strong due to its unparalleled educational value. More on this in a bit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C++&lt;/strong&gt; was created to be an improvement upon C and to be its object-oriented version. When C++ was originally developed, all C code could be seamlessly run with C++, but that is no longer the case. The two languages have diverged quite a bit in the ensuing decades, even though they are still often mentioned as C/C++, as if they are almost the same. Unlike C, C++ has evolved to become a behemoth of a language, with so many features that it can be overwhelming. So you will often hear things like “C++ is a kitchen sink language” or that “nobody really is an expert in C++”. It gets a lot of flak for this and &lt;a href=&quot;https://en.wikipedia.org/wiki/Criticism_of_C%2B%2B&quot;&gt;many other reasons&lt;/a&gt;. For example, Linus Torvalds (the creator of Linux and Git) &lt;a href=&quot;https://harmful.cat-v.org/software/c++/linus&quot;&gt;famously said&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;C++ is a horrible language… the choice of C is the only sane choice… C++ leads to really really bad design choices.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In any case, C++ is one of the most popular languages today, finding applications in a variety of niches including systems development, desktop applications, video games, embedded systems, servers, and more. C++ provides a combination of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;C-like performance and low-level access to hardware, and;&lt;/li&gt;
&lt;li&gt;non-C-like higher-level features and ease of use.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;More on C&lt;/h2&gt;
&lt;p&gt;Due to both historical accidents and C’s merits, C has stood the test of time. Despite being so powerful, C is in fact a very small language and it is easy to quickly get up to speed with it (it only has 30-50 keywords, depending on which standard you are looking at).&lt;/p&gt;
&lt;p&gt;Famously, C makes it very easy to shoot yourself in the foot because it leaves all the onus of higher-level functionality on the programmer. To quote directly from the legendary book &lt;em&gt;The C Programming Language&lt;/em&gt; by Brian Kernighan and Dennis Ritchie (the latter being the creator of C):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;C provides no operations to deal directly with composite objects such as character strings, sets, lists or arrays. There are no operations that manipulate an entire array or string, although structures may be copied as a unit. The language does not define any storage allocation facility other than static definition and the stack discipline provided by the local variables of functions; there is no heap or garbage collection. All of these higher-level mechanisms must be provided by explicitly called functions. Most C implementations have included a reasonably standard collection of such functions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Although the absence of some of these features may seem like a grave deficiency, (&quot;You mean I have to call a function to compare two character strings?&quot;), keeping the language down to modest size has real benefits. Since C is relatively small, it can be described in small space, and learned quickly. A programmer can reasonably expect to know and understand and indeed regularly use the entire language.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A lack of garbage collection in C is the most infamous of these deficits. Dr. Charles Severance (better known as Dr. Chuck) &lt;a href=&quot;https://www.cc4e.com/podcast&quot;&gt;notes that&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The lack of garbage collection feature in C is both one of the great strengths of the language and at the same time is likely the reason that the average programmer will never develop or maintain a major C application during their career.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even though these things were left out deliberately, they are more than just frustrating for the modern programmer. They can actually lead to security vulnerabilities and issues with system stability if not handled correctly. So there is a good reason C is being gradually replaced by other languages that came after it.&lt;/p&gt;
&lt;h2&gt;Why C&lt;/h2&gt;
&lt;p&gt;Despite the problems mentioned above, C turned out to be the perfect language for me to learn as my first language. Its educational value is unparalleled:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Almost all modern programming languages are directly or indirectly influenced by C, so learning C gives you a huge leg up if you want to be generally good at programming.&lt;/li&gt;
&lt;li&gt;When you learn a “real life” language (that you will be using day to day) after learning C, you will be in a much better position to grasp the nuances of the said language.&lt;/li&gt;
&lt;li&gt;Learning C helps to deeply understand how computers work under the hood.&lt;/li&gt;
&lt;li&gt;Helps you learn good coding practices.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s look at each of these points in more detail.&lt;/p&gt;
&lt;h3&gt;The mother of modern programming languages&lt;/h3&gt;
&lt;p&gt;C is the de facto lingua franca of programming languages. Due to the success and ubiquity of C, most programming languages today are either directly or indirectly inspired by it. The procedural, imperative style of C, with its curly braces, semicolons, and familiar control structures (&lt;code&gt;if&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;switch&lt;/code&gt;), is the direct ancestor of the syntax used in a massive family of popular languages. The &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_C-family_programming_languages&quot;&gt;list of C-family programming languages&lt;/a&gt; contains over 70 programming languages that share significant features with it!&lt;/p&gt;
&lt;p&gt;Here are some prominent modern programming languages that borrow aspects of C:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C++&lt;/strong&gt;: A direct descendant of C (originally called &quot;C with Classes&quot;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Java&lt;/strong&gt;: Heavily borrowed C/C++ syntax to make it familiar to C programmers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C#&lt;/strong&gt;: Microsoft&apos;s answer to Java. It adopted the C-family syntax that Java popularized but is built on its own .NET framework.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt;: A spiritual successor to C, developed at Google. It aims to capture C&apos;s simplicity and performance for systems programming but modernizes it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rust&lt;/strong&gt;: A modern systems language designed to be a &quot;safer C.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;: Adopted C-style syntax (curly braces, semicolons, &lt;code&gt;for&lt;/code&gt; loops) to have a familiar feel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP&lt;/strong&gt;: A web-focused scripting language whose syntax is heavily modeled on C.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Swift&lt;/strong&gt;: Apple&apos;s successor to Objective-C (which is a strict superset of C). Swift can import any C library.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, languages like Python and Ruby, which have a very different syntax from C, have their primary implementations written in C. This allows them to easily interface with high-performance libraries written in C.&lt;/p&gt;
&lt;h3&gt;Preparation for learning a “real” language&lt;/h3&gt;
&lt;p&gt;If you want to be a language-agnostic programmer, learning most languages becomes dramatically easier if you know C, as you can focus on their unique features (like classes in C++, the garbage collector in Java, or async/await in JavaScript) instead of wrestling with basic syntax.&lt;/p&gt;
&lt;p&gt;While C is very much a &quot;real&quot; language used in critical systems, the spirit of this point is that it prepares you for the languages you&apos;ll likely use for application development (e.g., C++, Java, C#, Go, Rust, JavaScript). Learning C first is like learning Latin to understand Romance languages; you grasp the roots from which so much has grown.&lt;/p&gt;
&lt;p&gt;When you move from C to a language like Rust, you&apos;ll have a profound appreciation for its &quot;borrow checker&quot; because you&apos;ve personally experienced the memory-related bugs it prevents. When you use a garbage-collected language like Go or Java, you&apos;ll understand the trade-offs being made — convenience at the cost of some performance overhead and non-deterministic cleanup — because you&apos;ve had to manage memory manually. This deep context allows you to grasp the &lt;em&gt;why&lt;/em&gt; behind a language&apos;s design, not just the &lt;em&gt;how&lt;/em&gt; of its syntax.&lt;/p&gt;
&lt;h3&gt;Seeing under the hood&lt;/h3&gt;
&lt;p&gt;Modern programming languages and frameworks are built on layers of abstraction designed to maximize developer productivity. While this is powerful, it can leave programmers with a fragile, incomplete mental model of how a computer actually works. C tears away these layers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Direct memory management&lt;/strong&gt; is the single most important concept C teaches. You are responsible for every byte of memory you allocate on the heap using &lt;code&gt;malloc()&lt;/code&gt; and for releasing it with &lt;code&gt;free()&lt;/code&gt;. Forgetting to &lt;code&gt;free()&lt;/code&gt; memory leads to memory leaks. Using memory after it has been freed leads to crashes. This forces you to understand the fundamental difference between the &lt;strong&gt;stack&lt;/strong&gt; (for local variables, managed automatically) and the &lt;strong&gt;heap&lt;/strong&gt; (for dynamically allocated data, managed by you). This knowledge is invaluable for debugging complex performance issues in any language.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pointers&lt;/strong&gt; are arguably the most feared but also the most powerful feature in C. A pointer is simply a variable that holds a memory address. By using pointers, you are no longer working with abstract data but with the actual locations in memory where that data lives. This teaches you: how data is laid out in memory; the difference between passing data &quot;by value&quot; (a copy) and &quot;by reference&quot; (a pointer to the original); and how to build complex data structures like linked lists, trees, and hash tables from scratch, giving you a visceral understanding of how they work.&lt;/p&gt;
&lt;p&gt;C is often called a &quot;high-level assembly language.&quot; It provides very little abstraction over the underlying hardware. When you are working with text (strings) in C, you are forced to consider how letters and words are stored in memory: each character in a string occupies a contiguous byte of memory, ending with a special character (the null-terminator) that signals the end of that string. When you are working with numbers, C forces you to think about how much space (in bytes) each number takes in memory, with different implications depending on how large you want the value of the number to be. When you work with arrays, you understand they are just contiguous blocks of memory. This proximity to the metal gives you an intuition for how your code will be executed by the CPU, making you a far more effective performance tuner and low-level debugger.&lt;/p&gt;
&lt;h3&gt;Internalizing good coding practices&lt;/h3&gt;
&lt;p&gt;Starting with a language that holds your hand can instill habits that are inefficient or unsafe in the long run. C, by contrast, has no safety net. It is a strict but fair teacher that forces you to become a disciplined, mindful programmer.&lt;/p&gt;
&lt;p&gt;For example, in Python, you can create a list and &lt;code&gt;append()&lt;/code&gt; to it indefinitely. The interpreter handles all the memory allocation, resizing, and garbage collection behind the scenes. This is convenient but hides the computational cost. A new programmer might not realize that appending to a list a million times could trigger multiple expensive reallocations and copies of the entire data structure.&lt;/p&gt;
&lt;p&gt;To do the same in C, you must manage a dynamic array yourself. You have to track its &lt;code&gt;size&lt;/code&gt; and &lt;code&gt;capacity&lt;/code&gt;. When &lt;code&gt;size&lt;/code&gt; equals &lt;code&gt;capacity&lt;/code&gt;, you must allocate a new, larger block of memory with &lt;code&gt;realloc()&lt;/code&gt;, copy the old data over, and free the old block. This process forces you to be &lt;strong&gt;mindful of resources&lt;/strong&gt; from day one. You learn to think about efficiency not as a premature optimization but as a core part of writing good code.&lt;/p&gt;
&lt;p&gt;In C, many standard library functions signal errors by returning &lt;code&gt;NULL&lt;/code&gt; or &lt;code&gt;-1&lt;/code&gt;. If you try to open a file and it doesn&apos;t exist, &lt;code&gt;fopen()&lt;/code&gt; returns &lt;code&gt;NULL&lt;/code&gt;. If you don&apos;t explicitly check for this &lt;code&gt;NULL&lt;/code&gt; value and try to use the file pointer, your program will crash with a segmentation fault. This forces you into the habit of defensive programming: always check return values, anticipate failure modes, and handle errors gracefully. This is a hallmark of professional, production-ready code that is often neglected when a language&apos;s exception-handling system makes it easy to be lazy.&lt;/p&gt;
&lt;p&gt;Because nothing is done for you automatically, every line of C code is more deliberate. You choose &lt;code&gt;unsigned int&lt;/code&gt; over &lt;code&gt;int&lt;/code&gt; for a reason. You decide whether a function parameter should be a pointer or a copy. This constant need to make low-level decisions builds a powerful mental muscle, leading to code that is more precise, efficient, and intentional, regardless of the language you ultimately work in.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In conclusion, while you may not write C every day in a web or mobile development job, the lessons it imparts are universal and timeless. Learning C is an investment in your fundamental understanding of computation itself. It makes you a better problem-solver, a more insightful debugger, and a more disciplined engineer — the kind of programmer who doesn&apos;t just know how to use a tool but understands how the tool works.&lt;/p&gt;</content:encoded><h:img src="/_astro/writing-a-c-program.CCxV5tot.webp"/><enclosure url="/_astro/writing-a-c-program.CCxV5tot.webp"/></item><item><title>Writing an Nginx-like web server from scratch in C++</title><link>https://alimnaqvi.com/blog/webserv</link><guid isPermaLink="true">https://alimnaqvi.com/blog/webserv</guid><description>Understanding the HTTP protocol and low-level network programming to build a robust web server in C++17 — one of my favorite projects at 42.</description><pubDate>Thu, 21 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview of HTTP&lt;/h2&gt;
&lt;p&gt;When you enter a website name in a browser&apos;s address bar, it first gets translated into an IP address via a process called DNS resolution. This IP address denotes the public address of a computer. A public IP address is the internet equivalent of the address of a physical building — anybody can send letters to this address. The browser then sends a message to this IP address asking for a website, i.e., asking for the resource at that location (URL) — the resource is usually an &lt;a href=&quot;/blog/this-website#level-1-pure-code&quot;&gt;HTML document&lt;/a&gt;. A computer can only receive messages on an IP address if a program is &quot;listening&quot; for them. That program is the &lt;strong&gt;web server&lt;/strong&gt;. A server is simply a computer program whose job is to continuously listen for incoming requests, &quot;understand&quot; (parse) those requests, and respond accordingly. In common usage, the word &quot;server&quot; usually refers to the computer (hardware) because that computer&apos;s entire purpose in life is to respond to requests from the web. But it is the server program/application (the software) that truly deserves the name &quot;server&quot; because any computer can be a server, including your laptop and your phone, and you can still do other things on that computer while that server runs in the background.&lt;/p&gt;
&lt;p&gt;In our example of entering a website name in the browser, the server may simply send back a default file (called an index file, usually an HTML file), which we can say is the homepage of that website. This is just the beginning of the conversation between your browser and the server. When the browser parses that HTML file to display it to you, it usually finds other resources mentioned in the HTML (e.g., images, CSS files, JavaScript, etc.), so it sends individual requests to the server for each of those resources (files). Then, as the server sends the requested files one by one, the browser displays them on the page as they are received. These days, all of this (and more!) happens in a fraction of a second. Your interaction with the page will then determine the next cycle of messages being sent back and forth between the browser and the server. In most of the messages that the browser sends, it is requesting a file (e.g., an HTML file or an image), but the browser can also send data for the server to save, e.g, when you upload an image or fill out a form. In addition to simply serving files saved on the server (called static files), it is also possible for a server to generate files on the fly based on some pre-programmed logic, e.g., based on user interaction with the website. A server can also delegate some of its tasks to other programs running on the same machine. E.g., it can give a received request to a Python program and then send the user the output of the Python program.&lt;/p&gt;
&lt;p&gt;The &quot;language&quot; that the browser and the server use to communicate with each other is called &lt;strong&gt;HTTP&lt;/strong&gt;, which stands for Hypertext Transfer Protocol. Just like any other communication protocol, it defines the rules of the communication. A human example (somewhat dated) is the protocol of saying &quot;over&quot; after finishing sending a message on a radio transceiver, such as in military radio communication or on walkie-talkies, if you are old enough to remember those! The HTTP protocol was invented in the early 1990s by &lt;a href=&quot;https://en.wikipedia.org/wiki/Tim_Berners-Lee&quot;&gt;Tim Berners-Lee&lt;/a&gt;, who is the inventor of the broader World Wide Web and HTML (he did so while working at CERN, a European nuclear research agency!). Today, the Internet Engineering Task Force (IETF) is the body that is responsible for standardizing the protocol. The legendary &lt;strong&gt;HTTP/1.1&lt;/strong&gt;, the specific version of HTTP first published in 1997, remained the most widely used HTTP protocol on the web for almost two decades. HTTP/2, published in 2015, has now largely overtaken it, and HTTP/3 is quickly gaining support, thanks to the performance benefits of each subsequent generation. HTTP/1.1 used a simple plain-text format, which made it very human-readable — somewhat surprisingly, because it could easily have been more cryptic, similar to assembly language or even binary (HTTP/2 and HTTP/3 utilize binary encoding for efficiency).&lt;/p&gt;
&lt;h2&gt;A concrete example of HTTP communication&lt;/h2&gt;
&lt;p&gt;Let&apos;s take a concrete example to better understand HTTP. Let us visit &lt;a href=&quot;https://info.cern.ch&quot;&gt;info.cern.ch&lt;/a&gt;, the first website ever created!&lt;/p&gt;
&lt;p&gt;When you enter &lt;code&gt;info.cern.ch&lt;/code&gt; into the browser, a DNS lookup takes place to resolve &lt;code&gt;info.cern.ch&lt;/code&gt; into an IP address, which happens to be &lt;code&gt;188.184.67.127&lt;/code&gt; for this website (this is an IPv4 address). The browser now knows which building (as it were) to send the message to. It actually sends a message to the IP:port &lt;em&gt;combination&lt;/em&gt; &lt;code&gt;188.184.67.127:443&lt;/code&gt; — the part after the &lt;code&gt;:&lt;/code&gt; is called the &lt;strong&gt;port&lt;/strong&gt;. If the IP address were an address of a building, the port would be the specific apartment number within the building. 443 is the standard port used for HTTPS requests — the &apos;S&apos; stands for Secure and signifies that the data sent back and forth will be encrypted for added security. In the past, using the less secure version of HTTP, the browser would have used &lt;code&gt;188.184.67.127:80&lt;/code&gt;, where 80 denotes the standard HTTP port.&lt;/p&gt;
&lt;p&gt;The exact HTTP request that the browser sends depends on things like your specific browser and operating system, but it would be something like this when you visit &lt;code&gt;info.cern.ch&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET / HTTP/1.1
Host: info.cern.ch
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line is the most important: It uses one of the &lt;strong&gt;HTTP methods&lt;/strong&gt;, &lt;code&gt;GET&lt;/code&gt;, to request the resource at location &lt;code&gt;/&lt;/code&gt; (denoting the root of the domain).&lt;/p&gt;
&lt;p&gt;Here is what the server responds with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;HTTP/1.1 200 OK
Date: Tue, 15 Jul 2025 19:09:50 GMT
Server: Apache
Last-Modified: Wed, 05 Feb 2014 16:00:31 GMT
ETag: &quot;286-4f1aadb3105c0&quot;
Accept-Ranges: bytes
Content-Length: 646
Connection: close
Content-Type: text/html

&amp;#x3C;html&gt;&amp;#x3C;head&gt;&amp;#x3C;/head&gt;&amp;#x3C;body&gt;&amp;#x3C;header&gt;
&amp;#x3C;title&gt;http://info.cern.ch&amp;#x3C;/title&gt;
&amp;#x3C;/header&gt;

&amp;#x3C;h1&gt;http://info.cern.ch - home of the first website&amp;#x3C;/h1&gt;
&amp;#x3C;p&gt;From here you can:&amp;#x3C;/p&gt;
&amp;#x3C;ul&gt;
&amp;#x3C;li&gt;&amp;#x3C;a href=&quot;http://info.cern.ch/hypertext/WWW/TheProject.html&quot;&gt;Browse the first website&amp;#x3C;/a&gt;&amp;#x3C;/li&gt;
&amp;#x3C;li&gt;&amp;#x3C;a href=&quot;http://line-mode.cern.ch/www/hypertext/WWW/TheProject.html&quot;&gt;Browse the first website using the line-mode browser simulator&amp;#x3C;/a&gt;&amp;#x3C;/li&gt;
&amp;#x3C;li&gt;&amp;#x3C;a href=&quot;http://home.web.cern.ch/topics/birth-web&quot;&gt;Learn about the birth of the web&amp;#x3C;/a&gt;&amp;#x3C;/li&gt;
&amp;#x3C;li&gt;&amp;#x3C;a href=&quot;http://home.web.cern.ch/about&quot;&gt;Learn about CERN, the physics laboratory where the web was born&amp;#x3C;/a&gt;&amp;#x3C;/li&gt;
&amp;#x3C;/ul&gt;
&amp;#x3C;/body&gt;&amp;#x3C;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line contains &lt;code&gt;200 OK&lt;/code&gt;, denoting a successful response. 200 is one of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status&quot;&gt;HTTP response status codes&lt;/a&gt;, which are sent by the server to succinctly communicate what happened to each request. After the first line, all lines before the first empty line are the HTTP headers, and everything after that is the response body (or payload). The response body, in this case, is the contents of a very simple HTML file and is what your browser will render.&lt;/p&gt;
&lt;p&gt;On this page (&lt;a href=&quot;https://info.cern.ch&quot;&gt;https://info.cern.ch&lt;/a&gt;), if you click on another link such as &quot;&lt;a href=&quot;http://info.cern.ch/hypertext/WWW/TheProject.html&quot;&gt;Browse the first website&lt;/a&gt;&quot;, the browser will send another request to the server, very similar to the previous one except that the &lt;code&gt;/&lt;/code&gt; will be replaced by a different resource location (specified in the URL that we just clicked):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /hypertext/WWW/TheProject.html HTTP/1.1
Host: info.cern.ch
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server again sends the &lt;code&gt;200 OK&lt;/code&gt; response along with the contents of the file at the requested location. And so the communication between the browser and the server continues.&lt;/p&gt;
&lt;h2&gt;Nginx&lt;/h2&gt;
&lt;p&gt;So, to deploy/host a website, one needs to use a server that will serve the files related to that website. Today, the majority of the world&apos;s websites are served using one of two open-source server applications — Nginx (pronounced &quot;engine x&quot;) and Apache. According to &lt;a href=&quot;https://w3techs.com/technologies/overview/web_server&quot;&gt;W3Techs&lt;/a&gt;, as of the date of writing, Nginx is used by 33.8% of all websites, while Apache is used by 27.6%. These days, many websites are being deployed &quot;serverless&quot;, which is a misnomer because servers are, of course, still involved, just abstracted away and managed by large cloud service providers (hyperscalers) such as AWS, instead of being directly managed by the website owner.&lt;/p&gt;
&lt;p&gt;In order to understand how a server works, one of the best ways to get started is to get familiar with one of these commonly used open-source web server applications. Setting up an Nginx or Apache web server is rather straightforward. You can turn any computer (that is running a supported operating system) into a server. For the base case, Nginx and Apache don&apos;t differ much, so for simplicity, let&apos;s focus on one of them: Nginx. You need to run only a couple of commands to get a &quot;Hello World&quot; server up and running.&lt;/p&gt;
&lt;p&gt;The following should work on any Debian-based Linux distribution, such as Ubuntu. To install Nginx:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start Nginx service (runs in the background):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo service nginx start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now Nginx server is already running and serving a default HTML file.&lt;/p&gt;
&lt;p&gt;At any time, you can check the status with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo service nginx status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go to &lt;code&gt;http://localhost/&lt;/code&gt; in your browser, and you should see the &quot;Welcome to nginx!&quot; default page. This is simply the contents of this HTML file: &lt;code&gt;/var/www/html/index.nginx-debian.html&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can add an HTML file named &lt;code&gt;index.html&lt;/code&gt; in the same directory, so it will be served instead of the default file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chown -R $USER:$USER /var/www/html # Give yourself ownership of the directory to prevent &quot;permission denied&quot; messages
echo &quot;&amp;#x3C;h1&gt;Hello world&amp;#x3C;/h1&gt;&quot; &gt; /var/www/html/index.html # Create an index.html with the contents &quot;&amp;#x3C;h1&gt;Hello world&amp;#x3C;/h1&gt;&quot;
sudo service nginx restart # Restart Nginx for the changes to take effect
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;http://localhost/&lt;/code&gt; should be serving the &quot;Hello world&quot; page. You can change that to any HTML content and that will be served on the browser. &lt;code&gt;localhost&lt;/code&gt; is an alias for the &quot;loopback&quot; IP address (usually &lt;code&gt;127.0.0.1&lt;/code&gt;). When you visit &lt;code&gt;localhost&lt;/code&gt;, it is as if you were visiting your site from another computer by typing in the public IP address and port of the computer on which the server is listening (the latter requires messing with various router and firewall settings and can quickly lead to security issues).&lt;/p&gt;
&lt;p&gt;Nginx uses a configuration file to control all aspects of how the server will behave and what it will serve. By default, the configuration file being used is &lt;code&gt;/etc/nginx/sites-enabled/default&lt;/code&gt;. Here is a simplified version of it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;server {
    listen 80; # Listening on port 80 on all IP addresses of the computer

    root /var/www/html; # The directory in which all the website files will live

    index index.html index.htm index.nginx-debian.html; # The file to serve (first one available in this order) when someone requests the &quot;homepage&quot;

    server_name _; # Sets names of a virtual server such as &quot;example.com&quot;. This name is matched with the `Host` header in incoming HTTP requests
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Writing the server in C++&lt;/h2&gt;
&lt;p&gt;Now that we have an understanding of the HTTP protocol as well as the behavior of Nginx, the most popular server application, we can start writing our own server. We will implement the HTTP/1.1 protocol.&lt;/p&gt;
&lt;p&gt;The C++ program will take a configuration file as an argument (an example compatible with our program is provided &lt;a href=&quot;https://github.com/alimnaqvi/webserv/blob/master/webserv.conf&quot;&gt;here&lt;/a&gt;). This config file contains all the settings of our server.&lt;/p&gt;
&lt;p&gt;Here is approximately what our C++ program will need to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read and parse the config file and check that it is valid.&lt;/li&gt;
&lt;li&gt;Save the input configuration in suitable data structures for easy retrieval as requests arrive.&lt;/li&gt;
&lt;li&gt;Indefinitely wait for new connections to arrive on the IP:port combination(s) specified in the config file.&lt;/li&gt;
&lt;li&gt;When new connections arrive, queue them suitably and accept as many as the capacity allows.&lt;/li&gt;
&lt;li&gt;For each accepted connection that has sent a request, parse the message and prepare a suitable response.&lt;/li&gt;
&lt;li&gt;Send the response and wait for further messages and further connections.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, in our code, we aimed to follow the best practices of modern C++ (we used the C++17 standard, since that is the latest one allowed at our campus). We never performed any manual memory allocation using &lt;code&gt;new&lt;/code&gt;/&lt;code&gt;delete&lt;/code&gt;, but instead used smart pointers (&lt;a href=&quot;https://en.cppreference.com/w/cpp/memory/unique_ptr.html&quot;&gt;&lt;code&gt;std::unique_ptr&lt;/code&gt;&lt;/a&gt;). Since lower-level file operations were inevitable (modern C++ does not provide an alternative to C networking APIs yet), we had to use manual &lt;code&gt;open()&lt;/code&gt; and &lt;code&gt;close()&lt;/code&gt; for file descriptors, but we made sure to use &lt;a href=&quot;https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii&quot;&gt;RAII&lt;/a&gt; principles (the object destructor is responsible for closing file descriptors). Other modern features include the use of the Filesystem library (&lt;a href=&quot;https://en.cppreference.com/w/cpp/filesystem.html&quot;&gt;&lt;code&gt;std::filesystem&lt;/code&gt;&lt;/a&gt;) for performing file system operations such as path joining, checking the existence of a file or a directory, creating a directory, changing working directory, etc.&lt;/p&gt;
&lt;h3&gt;Config file&lt;/h3&gt;
&lt;p&gt;Our program accepts a simplified version of the Nginx config file.&lt;/p&gt;
&lt;p&gt;Various &lt;strong&gt;directives&lt;/strong&gt; are allowed in the configuration file. Directives are divided into simple directives and block directives. A simple directive consists of the name and parameters separated by spaces and ends with a semicolon (&lt;code&gt;;&lt;/code&gt;). A block directive has the same structure as a simple directive, but instead of a semicolon, it ends with a set of additional instructions surrounded by braces (&lt;code&gt;{&lt;/code&gt; and &lt;code&gt;}&lt;/code&gt;). If a block directive can have other directives inside braces, it is called a &lt;strong&gt;context&lt;/strong&gt; (examples: &lt;code&gt;server&lt;/code&gt; and &lt;code&gt;location&lt;/code&gt; contexts).&lt;/p&gt;
&lt;p&gt;Directives placed in the configuration file outside of any contexts are considered to be in the &quot;global&quot; context. Unlike Nginx, there is no &lt;code&gt;http&lt;/code&gt; context for our program. &lt;code&gt;server&lt;/code&gt; directives must be in the global context. Comments are allowed: the rest of a line after the &lt;code&gt;#&lt;/code&gt; sign is considered a comment.&lt;/p&gt;
&lt;p&gt;Here is a more detailed description of the directives allowed in each of the three allowed contexts:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. &quot;Global&quot; context:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At least one &lt;code&gt;server&lt;/code&gt; block is mandatory in the global context.&lt;/p&gt;
&lt;p&gt;Allowed directives inside global context:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directive&lt;/th&gt;
&lt;th&gt;Multiple allowed&lt;/th&gt;
&lt;th&gt;Duplicates allowed&lt;/th&gt;
&lt;th&gt;Is optional&lt;/th&gt;
&lt;th&gt;Default value&lt;/th&gt;
&lt;th&gt;Number of arguments&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes (ignore)&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;n/a (block)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;root&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or &quot;html&quot;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;index&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;index.html&lt;/td&gt;
&lt;td&gt;no limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error_page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or none&lt;/td&gt;
&lt;td&gt;no limit (at least 2, last is URI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;autoindex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or off&lt;/td&gt;
&lt;td&gt;1 (on/off)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;client_max_body_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or 1m&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;server&lt;/code&gt; context:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The first &lt;code&gt;server&lt;/code&gt; block defined for a given IP:port combination becomes the default server for that IP:port. If multiple servers have the same server_name, it is allowed, but a warning will be issued that they will be considered one server (i.e., all the subsequent duplicate servers will be ignored).&lt;/p&gt;
&lt;p&gt;Allowed directives inside &lt;code&gt;server&lt;/code&gt; context:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directive&lt;/th&gt;
&lt;th&gt;Multiple allowed&lt;/th&gt;
&lt;th&gt;Duplicates allowed&lt;/th&gt;
&lt;th&gt;Is optional&lt;/th&gt;
&lt;th&gt;Default value&lt;/th&gt;
&lt;th&gt;Number of arguments&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;listen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;INADDR_ANY:http&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;server_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&quot;&quot;&lt;/td&gt;
&lt;td&gt;no limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;root&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or &quot;html&quot;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;index&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;index.html&lt;/td&gt;
&lt;td&gt;no limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error_page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or none&lt;/td&gt;
&lt;td&gt;no limit (at least 2, last is URI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;location&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;/ {}&lt;/td&gt;
&lt;td&gt;block (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;autoindex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or off&lt;/td&gt;
&lt;td&gt;1 (on/off)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;client_max_body_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or 1m&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cgi_handler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;2 (ext and interpreter)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;location&lt;/code&gt; context:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Allowed directives inside location context:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directive&lt;/th&gt;
&lt;th&gt;Multiple allowed&lt;/th&gt;
&lt;th&gt;Duplicates allowed&lt;/th&gt;
&lt;th&gt;Is optional&lt;/th&gt;
&lt;th&gt;Default value&lt;/th&gt;
&lt;th&gt;Number of arguments&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;root&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or &quot;html&quot;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;autoindex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or off&lt;/td&gt;
&lt;td&gt;1 (on/off)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;client_max_body_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or 1m&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;index&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or index.html&lt;/td&gt;
&lt;td&gt;no limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error_page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or none&lt;/td&gt;
&lt;td&gt;no limit (at least 2, last is URI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cgi_handler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;inherited or none&lt;/td&gt;
&lt;td&gt;2 (ext and interpreter)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;limit_except&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;GET POST&lt;/td&gt;
&lt;td&gt;no limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upload_store&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;return&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1 or 2 (code URL)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In terms of C++, each of the above 3 contexts is its own class: &lt;code&gt;GlobalConfig&lt;/code&gt;, &lt;code&gt;ServerConfig&lt;/code&gt;, and &lt;code&gt;LocationConfig&lt;/code&gt;. There is only one &lt;code&gt;GlobalConfig&lt;/code&gt; object since only one config file is allowed. Each &lt;code&gt;GlobalConfig&lt;/code&gt; object can have multiple &lt;code&gt;ServerConfig&lt;/code&gt; objects, and each &lt;code&gt;ServerConfig&lt;/code&gt; object can have multiple &lt;code&gt;LocationConfig&lt;/code&gt; objects.&lt;/p&gt;
&lt;h3&gt;Socket programming&lt;/h3&gt;
&lt;p&gt;Even in modern C++, network programming requires dealing with low-level C libraries and APIs that were designed at the dawn of the Internet. This is one of the frequently cited limitations of C++. Various proposals to modernize this aspect of the language have been raised and are currently under consideration by the C++ standards committee.&lt;/p&gt;
&lt;p&gt;So, in order to write a web server in C++, if we don&apos;t want to use third-party libraries, we must get ready to do some good old C programming! One of the main concepts to grasp is that of a &lt;strong&gt;socket&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In UNIX-like systems, a socket is a software abstraction that serves as an endpoint for sending and receiving data. The &lt;a href=&quot;https://en.wikipedia.org/wiki/Berkeley_sockets&quot;&gt;Berkeley Sockets API&lt;/a&gt;, which standardized this concept, was designed to be protocol-agnostic from its inception, supporting different communication domains.&lt;/p&gt;
&lt;p&gt;Two common domains are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Internet Sockets (AF_INET/AF_INET6):&lt;/strong&gt; Used for communication over a network. These sockets are identified by an IP address and a port number. They do not have a visible representation in the file system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unix Domain Sockets (AF_UNIX):&lt;/strong&gt; Used for inter-process communication on the same host. These are more efficient than network sockets for local communication and are represented by a special file in the filesystem.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regardless of the type, when a socket is created, the kernel returns a &lt;strong&gt;file descriptor&lt;/strong&gt; — a small integer — that the process uses to read from, write to, and manage the socket. This exemplifies the &quot;everything is a file&quot; philosophy in UNIX-like systems.&lt;/p&gt;
&lt;p&gt;There are plenty of tutorials available out there that guide you through the basics of socket programming, so I won&apos;t rehash them here. For example, see &lt;a href=&quot;https://ncona.com/2019/04/building-a-simple-server-with-cpp/&quot;&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are the key C library functions that we will need, categorized by the header in which they are defined:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#include &amp;#x3C;netdb.h&gt;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man3/getaddrinfo.3.html&quot;&gt;&lt;code&gt;getaddrinfo()&lt;/code&gt;&lt;/a&gt; for converting the human-readable IP:host combinations (provided in config file) to C-ready data structures &lt;code&gt;struct addrinfo&lt;/code&gt;, to be used in the next functions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#include &amp;#x3C;sys/socket.h&gt;&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man2/socket.2.html&quot;&gt;&lt;code&gt;socket()&lt;/code&gt;&lt;/a&gt; for creating a TCP socket&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man2/bind.2.html&quot;&gt;&lt;code&gt;bind()&lt;/code&gt;&lt;/a&gt; for assigning the socket to an IP address and a port (“naming the socket”)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man2/listen.2.html&quot;&gt;&lt;code&gt;listen()&lt;/code&gt;&lt;/a&gt; for listening to incoming connections and creating a queue&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man2/accept.2.html&quot;&gt;&lt;code&gt;accept()&lt;/code&gt;&lt;/a&gt; for accepting a connection in the queue&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#include &amp;#x3C;fcntl.h&gt;&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man2/fcntl.2.html&quot;&gt;&lt;code&gt;fcntl()&lt;/code&gt;&lt;/a&gt; for setting the sockets to non-blocking mode&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#include &amp;#x3C;poll.h&gt;&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man2/poll.2.html&quot;&gt;&lt;code&gt;poll()&lt;/code&gt;&lt;/a&gt; for asking the OS kernel which fds are &quot;ready&quot; to be processed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The main server loop (&lt;code&gt;poll&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man2/poll.2.html&quot;&gt;&lt;code&gt;poll()&lt;/code&gt;&lt;/a&gt; is one of the most important functions for the web server. It is a system call that allows the program to monitor multiple file descriptors to see if they are ready for input/output operations (reading or writing) without blocking.&lt;/p&gt;
&lt;p&gt;The heart of the server is an infinite loop (the &quot;event loop&quot;) that calls &lt;code&gt;poll()&lt;/code&gt; on every iteration. By passing certain parameters to &lt;code&gt;poll()&lt;/code&gt;, we are basically telling the kernel (of the operating system) which file descriptors we are interested in, and for what &quot;events&quot; — we tell &lt;code&gt;poll()&lt;/code&gt; whether we are interested in reading from (&lt;code&gt;POLLIN&lt;/code&gt;) or writing to (&lt;code&gt;POLLOUT&lt;/code&gt;) a file descriptor, or both (e.g., &quot;tell me when file descriptor &lt;code&gt;listen_fd_1&lt;/code&gt; has a new connection,&quot; &quot;tell me when file descriptor &lt;code&gt;connection_fd_1&lt;/code&gt; has data to read,&quot; or &quot;tell me when file descriptor &lt;code&gt;connection_fd_2&lt;/code&gt; is ready to accept more data to write&quot;).&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;poll()&lt;/code&gt; is called, our server program &lt;em&gt;sleeps&lt;/em&gt; (does not consume CPU) until one or more of the registered events occur, or a specified timeout expires.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;poll()&lt;/code&gt; returns, it tells us which file descriptors (sockets) are &quot;ready&quot; and for which events. &quot;Ready&quot; means that you can now safely read from or write to that file descriptor without worrying that that read or write might &quot;block&quot;, i.e., freeze the program (e.g., while waiting for user input on &lt;code&gt;stdin&lt;/code&gt;). We then iterate through these ready file descriptors and perform the non-blocking input/output operations (like &lt;code&gt;accept()&lt;/code&gt; to accept new connections, &lt;code&gt;read()&lt;/code&gt;/&lt;code&gt;recv()&lt;/code&gt; to read incoming messages, and &lt;code&gt;write()&lt;/code&gt;/&lt;code&gt;send()&lt;/code&gt; to send back HTTP responses).&lt;/p&gt;
&lt;p&gt;This is what allows the server to constantly be &quot;listening&quot; without consuming excess resources. &lt;code&gt;poll()&lt;/code&gt; lets us pass responsibility from our program to the OS kernel. The kernel knows how to do this job better than most any code that we can write. Kernels have had this kind of capability — checking the status of files to see which ones are &quot;ready&quot; — since before the invention of the web.&lt;/p&gt;
&lt;p&gt;This event loop continues indefinitely until a UNIX signal (&lt;code&gt;SIGINT&lt;/code&gt;) is received. A signal handler ensures that the server shuts down gracefully after performing any necessary cleanup of resources (closing file descriptors, freeing memory, etc.).&lt;/p&gt;
&lt;h3&gt;HTTP messages&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Parsing of incoming request messages:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When a request arrives on our server, the first thing the server must do is to parse it and make sure that it is a valid HTTP request (follows the rules of the HTTP protocol, only version HTTP/1.1 in our case). Again, I won&apos;t go through the details here myself since there are already great guides out there. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Messages&quot;&gt;MDN&lt;/a&gt; in particular is a fantastic resource with very readable details on the HTTP protocol (and much more regarding the web). We must keep in mind things like case (in)sensitivity (e.g., header name is case-insensitive), that line separator is CRLF (&lt;code&gt;\r\n&lt;/code&gt;, not &lt;code&gt;\n&lt;/code&gt;), and that the headers are separated from the body by a blank line (&lt;code&gt;\r\n\r\n&lt;/code&gt;). Here is an image from MDN that I find very helpful:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alimnaqvi.com/_image?href=%2F_astro%2Fhttp-message.CtP249cI.png&amp;#x26;w=755&amp;#x26;h=273&amp;#x26;f=webp&quot; alt=&quot;Anatomy of an HTTP message&quot;&gt;&lt;/p&gt;
&lt;p&gt;Our web server supports three HTTP methods: &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;. HTTP methods indicate the purpose of the request and what is expected if the request is successful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET&lt;/code&gt;: Tells the server to retrieve a resource at a specified location.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST&lt;/code&gt;: Submits some data to the specified resource, often causing a change in state on the server.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DELETE&lt;/code&gt;: Deletes the specified resource.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the requested method (always in the first line of an HTTP request) is none of these three, the request is considered invalid by our server, and an error response is sent back, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;HTTP/1.1 501 Not Implemented
Content-Type: text/html
Content-Length: 158
Server: Webserv
Date: Sun, 17 Aug 2025 21:31:41 GMT

&amp;#x3C;html&gt;
  &amp;#x3C;head&gt;
    &amp;#x3C;title&gt;501 Not Implemented&amp;#x3C;/title&gt;
  &amp;#x3C;/head&gt;
  &amp;#x3C;body&gt;
    &amp;#x3C;h1&gt;501 Not Implemented&amp;#x3C;/h1&gt;
    &amp;#x3C;p&gt;The server does not support the facility required.&amp;#x3C;/p&gt;
  &amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server also checks whether the value of the &lt;code&gt;Content-Length&lt;/code&gt; header matches the actual size of the content received. The incoming request may also be &quot;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding&quot;&gt;chunked&lt;/a&gt;&quot; — chunked encoding is a mechanism in HTTP/1.1 that allows the sender to transfer a request or response body as a series of &quot;chunks&quot; without knowing the total size in advance. So the server must &quot;unchunk&quot; it. Instead of &lt;code&gt;Content-Length&lt;/code&gt;, the sender includes the header &lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt;, which acts as a signal to the receiver (our server) that the body will not be a single block of data of a known size, but will instead follow a &lt;a href=&quot;https://en.wikipedia.org/wiki/Chunked_transfer_encoding&quot;&gt;special format&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generating response messages:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After successfully parsing and validating a request, the server starts working on generating an appropriate response. In our C++ code, this is done by separate classes for &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; (in addition to a class for handling unknown/bad requests), which all inherit from a &lt;code&gt;HTTPRequest&lt;/code&gt; &lt;a href=&quot;https://www.learncpp.com/cpp-tutorial/pure-virtual-functions-abstract-base-classes-and-interface-classes/&quot;&gt;abstract base class&lt;/a&gt; with the main pure virtual function being &lt;code&gt;generateResponse()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The server must never do a &lt;code&gt;read&lt;/code&gt; or a &lt;code&gt;write&lt;/code&gt; operation without going through &lt;code&gt;poll()&lt;/code&gt; (to be sure that the operation will be non-blocking). So &lt;code&gt;generateResponse()&lt;/code&gt; acts like a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/State_machine&quot;&gt;state machine&lt;/a&gt;. Each time &lt;code&gt;generateResponse()&lt;/code&gt; needs to read from or write to a file, it does some work, saves its current working state at the point where it needs to read or write, registers the read or write file descriptor with &lt;code&gt;poll&lt;/code&gt;, and returns. When &lt;code&gt;poll()&lt;/code&gt; says the file descriptor is ready for reading/writing, the &lt;code&gt;generateResponse()&lt;/code&gt; function will be called again, recognize (based on the previously saved state) that this is not the first time it is being called, and continue where it left off last time. Every time it returns, the main server loop checks if the response is ready, so that it can be sent to the client.&lt;/p&gt;
&lt;p&gt;The requested resource path is always built by adding the URI to the root. E.g., if the first line of the request is &lt;code&gt;GET /some_file_or_dir HTTP/1.1&lt;/code&gt; and the root (set in the configuration file) is &lt;code&gt;/var/www/my_website/&lt;/code&gt;, the resource path is root + URI = &lt;code&gt;/var/www/my_website/some_file_or_dir&lt;/code&gt;. And if the first line of the request is &lt;code&gt;GET / HTTP/1.1&lt;/code&gt;, the resource path is root + URI = &lt;code&gt;/var/www/my_website/&lt;/code&gt; (same as root).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For &lt;code&gt;GET&lt;/code&gt; responses&lt;/strong&gt;, the &lt;code&gt;generateResponse()&lt;/code&gt; function checks whether the requested path (root + URI) exists or not; otherwise, it is an error (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/404&quot;&gt;404&lt;/a&gt;). If the requested path is a file (except for &lt;a href=&quot;#cgi&quot;&gt;CGI files&lt;/a&gt;), the server reads the file and sends its contents as the body of the response. If the requested resource is a directory, the server checks if an &lt;a href=&quot;https://nginx.org/en/docs/http/ngx_http_index_module.html&quot;&gt;index file&lt;/a&gt; has been set in the server configuration file. If such a file is found in the directory, then this file is chosen for the response; otherwise, an error 404 is returned.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For &lt;code&gt;POST&lt;/code&gt;&lt;/strong&gt;, the URI, instead of being a file or directory, represents an &lt;em&gt;endpoint that will process the data the client is sending&lt;/em&gt;. &lt;code&gt;POST /submit-form HTTP/1.1&lt;/code&gt; doesn&apos;t mean there&apos;s a file or directory named &lt;code&gt;submit-form&lt;/code&gt;. It means there is a piece of logic on the server, associated with the &lt;code&gt;/submit-form&lt;/code&gt; path, that knows what to do with the incoming form data. For form data, &lt;code&gt;POST&lt;/code&gt; usually hands over the task to a &lt;a href=&quot;#cgi&quot;&gt;CGI&lt;/a&gt; program. If there is no CGI program set up at the requested URI, it is considered to be a request to upload data to a file on the server. In our particular case, uploads are only allowed if a &lt;code&gt;location&lt;/code&gt; block is provided in the configuration file with an &lt;code&gt;upload_store&lt;/code&gt; directive. This will allow uploads for that particular location (URI/endpoint) only. For example,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;   location /upload { 
       upload_store /path/to/upload/directory;
   } 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will allow uploads only for requests such as &lt;code&gt;POST /upload HTTP/1.1&lt;/code&gt;, but other requests like &lt;code&gt;POST /some-uri HTTP/1.1&lt;/code&gt; will get an error. The request body for &lt;code&gt;POST&lt;/code&gt; requests is usually not plain text that can be written to a file; the body is usually encoded. Once a valid upload request is received, the &lt;code&gt;POST&lt;/code&gt; version of &lt;code&gt;generateResponse()&lt;/code&gt; must parse the encoded request body. Uploads via &lt;code&gt;POST&lt;/code&gt; requests almost always use the &lt;code&gt;multipart/form-data&lt;/code&gt; encoding, identified by the &lt;code&gt;Content-Type&lt;/code&gt; header (see &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7578&quot;&gt;RFC&lt;/a&gt; for more details on the syntax). For simple form data, &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; encoding may be used. Less commonly, for file uploads, &lt;code&gt;application/octet-stream&lt;/code&gt; might be used to send raw binary data. It is also possible to see a non-encoded media type, such as &lt;code&gt;Content-Type: text/plain&lt;/code&gt;, &lt;code&gt;Content-Type: text/html&lt;/code&gt;, or &lt;code&gt;Content-Type: application/json&lt;/code&gt;, etc., in which case the body&apos;s raw data is saved in a file with an appropriate extension. If a file upload is successful, a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/201&quot;&gt;&lt;code&gt;201 Created&lt;/code&gt;&lt;/a&gt; response is sent; otherwise, an appropriate response, such as &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/415&quot;&gt;&lt;code&gt;415 Unsupported Media Type&lt;/code&gt;&lt;/a&gt; is sent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For &lt;code&gt;DELETE&lt;/code&gt;&lt;/strong&gt;, the URI refers to the file or directory that should be deleted. In our server, DELETE is disallowed by default in all locations, and must be enabled manually for individual locations. For example, if &lt;code&gt;DELETE&lt;/code&gt; is enabled in the &lt;code&gt;/uploads&lt;/code&gt; location, for a request &lt;code&gt;DELETE /uploads/photo123.jpg HTTP/1.1&lt;/code&gt;, if the file exists, the server will delete the file and send a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/200&quot;&gt;200&lt;/a&gt; success response (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/204&quot;&gt;202&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/204&quot;&gt;204&lt;/a&gt; can also be sent). If the file does not exist, it will be a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/404&quot;&gt;404 Not Found&lt;/a&gt; response. If the resource is not inside the &lt;code&gt;upload_store&lt;/code&gt; directory (e.g., &lt;code&gt;DELETE /index.html HTTP/1.1&lt;/code&gt;), it will be a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/403&quot;&gt;403 Forbidden&lt;/a&gt; response.&lt;/p&gt;
&lt;p&gt;Regardless of the method requested, redirections in our server are configured using a &lt;code&gt;return&lt;/code&gt; directive in the config file. For example, the config file might contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;    location /redirect-demo {
        return 301 &quot;https://www.youtube.com/watch?v=dQw4w9WgXcQ&quot;;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then any request to the URI &lt;code&gt;/redirect-demo&lt;/code&gt; will be redirected to the URL &lt;a href=&quot;https://www.youtube.com/watch?v=dQw4w9WgXcQ&quot;&gt;https://www.youtube.com/watch?v=dQw4w9WgXcQ&lt;/a&gt;. A redirect response might be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;HTTP/1.1 301 Moved Permanently
Content-Type: text/html
Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ
Content-Length: 105
Server: Webserv
Date: Mon, 18 Aug 2025 14:15:11 GMT

&amp;#x3C;html&gt;
  &amp;#x3C;head&gt;
    &amp;#x3C;title&gt;301 Moved Permanently&amp;#x3C;/title&gt;
  &amp;#x3C;/head&gt;
  &amp;#x3C;body&gt;
    &amp;#x3C;h1&gt;301 Moved Permanently&amp;#x3C;/h1&gt;
  &amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;CGI&lt;/h3&gt;
&lt;p&gt;The Common Gateway Interface (CGI) is a standard protocol that allows a web server to execute external programs (CGI scripts) to process and generate content for web requests. Just like HTTP/1.1, the CGI/1.1 specification is described in an &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc3875&quot;&gt;RFC&lt;/a&gt;. Legacy CGI is not very commonly used nowadays, as much more performant and feature-rich alternatives now exist, such as FastCGI, servlets, and web frameworks. Our server uses CGI/1.1 for educational purposes.&lt;/p&gt;
&lt;p&gt;Our config file allows a &lt;code&gt;cgi_handler&lt;/code&gt; directive, which takes two arguments: an extension and the path to a handler for that extension. The interpreter can be for any language, e.g. Perl, PHP, or Python. For example, the following can be specified in the config file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;server {
    listen localhost:9743;
    server_name localhost;
    root ./assets/default_website;
    cgi_handler .py /usr/bin/python3;
    location /cgi-demo {
        index hello.py;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, the &lt;code&gt;cgi_handler&lt;/code&gt; is provided in the server context and will be inherited by any location blocks within that server block. What this means is that any request for a resource name ending with &lt;code&gt;.py&lt;/code&gt; will be considered a CGI request within this server block. In addition, requests to the &lt;code&gt;/cgi-demo&lt;/code&gt; location will also be considered CGI requests since its index file ends with &lt;code&gt;.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The general idea is that the server:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identifies a requested resource&lt;/strong&gt; as a CGI script (e.g., &lt;code&gt;/cgi-demo/hello.py&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prepares an environment&lt;/strong&gt; for the script, packing all the request details (method, headers, query string, etc.) into environment variables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Executes the script&lt;/strong&gt; using the configured interpreter (e.g., &lt;code&gt;/usr/bin/python3&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipes the request body&lt;/strong&gt; (if any, like from a POST) to the script&apos;s standard input (&lt;code&gt;stdin&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reads the script&apos;s standard output&lt;/strong&gt; (&lt;code&gt;stdout&lt;/code&gt;). This output contains the response headers and body that the server should send back to the client.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Waits for the script to terminate&lt;/strong&gt; and cleans up.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Just like network programming, modern C++ is annoyingly lacking in features to create, manage, and communicate with sub-processes (child processes). So we must again go back to C APIs. We will use &lt;a href=&quot;https://man7.org/linux/man-pages/man2/fork.2.html&quot;&gt;&lt;code&gt;fork()&lt;/code&gt;&lt;/a&gt; to create a child process, &lt;a href=&quot;https://man7.org/linux/man-pages/man2/execve.2.html&quot;&gt;&lt;code&gt;execve()&lt;/code&gt;&lt;/a&gt; to execute the CGI program, &lt;a href=&quot;https://man7.org/linux/man-pages/man2/pipe.2.html&quot;&gt;&lt;code&gt;pipe()&lt;/code&gt;&lt;/a&gt; (with the help of &lt;a href=&quot;https://man7.org/linux/man-pages/man2/dup.2.html&quot;&gt;&lt;code&gt;dup2()&lt;/code&gt;&lt;/a&gt;) to communicate with the child process. &lt;a href=&quot;https://man7.org/linux/man-pages/man3/waitpid.3p.html&quot;&gt;&lt;code&gt;waitpid()&lt;/code&gt;&lt;/a&gt; is used to check the status of the child process, and &lt;a href=&quot;https://man7.org/linux/man-pages/man2/kill.2.html&quot;&gt;&lt;code&gt;kill()&lt;/code&gt;&lt;/a&gt; can be used to terminate the process if needed.&lt;/p&gt;
&lt;p&gt;The CGI protocol has very specific rules (just like the HTTP protocol) about how a request is passed to the CGI process. The most critical part is the preparation of environment variables that must be passed to the CGI program (via the &lt;code&gt;execve&lt;/code&gt; system call). Here are the essential ones based on the CGI/1.1 specification:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;Variable&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;Example Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;GATEWAY_INTERFACE&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The CGI version.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;CGI/1.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;SERVER_PROTOCOL&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The protocol of the request.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;HTTP/1.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;REQUEST_METHOD&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The HTTP method.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;REQUEST_URI&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The full, original request URI.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/path/to/script.py?user=test&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;SCRIPT_FILENAME&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The absolute filesystem path to the script.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/var/www/html/path/to/script.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;SCRIPT_NAME&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The virtual path to the script.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/path/to/script.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;QUERY_STRING&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The part of the URI after the &lt;code&gt;?&lt;/code&gt;.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;user=test&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;CONTENT_TYPE&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The &lt;code&gt;Content-Type&lt;/code&gt; header from the client request.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;CONTENT_LENGTH&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The length of the request body. If the request was chunked, this should be the total size of the &lt;em&gt;unchunked&lt;/em&gt; body.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;15&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;SERVER_NAME&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The server&apos;s hostname or IP.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;example.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;SERVER_PORT&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The port the server received the request on.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;80&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;REMOTE_ADDR&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;The IP address of the client.&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;192.168.1.10&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All other headers from the request must also be passed as environment variables. The convention is to prefix them with &lt;code&gt;HTTP_&lt;/code&gt;, convert the header name to uppercase, and replace hyphens (&lt;code&gt;-&lt;/code&gt;) with underscores (&lt;code&gt;_&lt;/code&gt;). For example, &lt;code&gt;User-Agent&lt;/code&gt; becomes &lt;code&gt;HTTP_USER_AGENT&lt;/code&gt; and &lt;code&gt;Accept-Language&lt;/code&gt; becomes &lt;code&gt;HTTP_ACCEPT_LANGUAGE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We then use the standard UNIX way to create the child process. Two pipes are needed to communicate with it: one for our server to write the request body to the CGI&apos;s &lt;code&gt;stdin&lt;/code&gt;, and the other for our server to read the response from the CGI&apos;s &lt;code&gt;stdout&lt;/code&gt;. We use a &lt;code&gt;CGISubprocess&lt;/code&gt; class for this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;CGISubprocess::CGISubprocess()
{
    if (pipe(_pipe_to_cgi) != 0)
        throw std::runtime_error(&quot;Failed to create pipe to CGI: &quot; + std::string{strerror(errno)});
    setNonBlocking(_pipe_to_cgi[0]);
    setNonBlocking(_pipe_to_cgi[1]);
    if (pipe(_pipe_from_cgi) != 0)
    {
        close(_pipe_to_cgi[0]);
        close(_pipe_to_cgi[1]);
        throw std::runtime_error(&quot;Failed to create pipe from CGI: &quot; + std::string{strerror(errno)});
    }
    setNonBlocking(_pipe_from_cgi[0]);
    setNonBlocking(_pipe_from_cgi[1]);
}

void CGISubprocess::createSubprocess(const std::filesystem::path &amp;#x26;filePathAbs, const std::string &amp;#x26;interpreter)
{
    // fork (create a duplicate of the current process)
    _pid = fork();
    if (_pid == -1)
        throw std::runtime_error(&quot;Failed to create fork for CGI: &quot; + std::string{strerror(errno)});

    // in child
    else if (_pid == 0)
    {
        // change current working directory to script directory
        std::filesystem::current_path(filePathAbs.parent_path());

        close(_pipe_to_cgi[1]); // Close write end of the pipe (parent will write to it)
        dup2(_pipe_to_cgi[0], STDIN_FILENO); // redirect stdin to read end of pipe_to_cgi
        close(_pipe_to_cgi[0]); // close redirected fd
        close(_pipe_from_cgi[0]); // close read end of the pipe (parent will read from it)
        dup2(_pipe_from_cgi[1], STDOUT_FILENO); // redirect stdout to write end of pipe_from_cgi
        close(_pipe_from_cgi[1]); // close redirected fd

        // prepare args for execve (interpreter is the program name and script file is the argument, like running `python3 hello.py`)
        char *args[] = {const_cast&amp;#x3C;char *&gt;(interpreter.c_str()), const_cast&amp;#x3C;char *&gt;(filePathAbs.c_str()), NULL};

        // execve
        if (execve(args[0], args, _envp.data()) == -1)
        {
            // can also simply use std::exit but it won&apos;t clean up the local objects (destructor will not be called)
            throw std::runtime_error(&quot;execve failed: &quot; + std::string{strerror(errno)});
        }
    }
    // in parent
    else if (_pid &gt; 0)
    {
        _subprocessStarted = true;
        // close unneeded pipes
        close(_pipe_to_cgi[0]);
        close(_pipe_from_cgi[1]);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then register the two remaining open file descriptors in the parent process with &lt;code&gt;poll()&lt;/code&gt; (one to write to the CGI and one to read from it), which will tell us whether the file descriptors are ready for reading/writing or not. After writing to the child and reading back from it successfully, all that remains to be done is to send a response to the client. The response we read from the CGI is almost a full response, but not quite. It can e.g., contain the &lt;code&gt;Status: 200 OK&lt;/code&gt; header, which we should convert to a proper start line &lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt;. This is now the full response and can finally be sent to the client.&lt;/p&gt;
&lt;h2&gt;Closing remarks&lt;/h2&gt;
&lt;p&gt;So hopefully, this provides some insight into the inner workings of a web server, a technology that lies at the heart of the web that we all interact with every day. Admittedly, it can be frustrating to go back to understand old-school technologies and use verbose C/C++ code to implement what modern languages and frameworks can achieve in a snap of a finger. But modern frameworks can only do that because somebody else has already done the difficult work, notwithstanding the fact that a surprisingly large amount of infrastructure in the present day still runs on decades-old technologies. I, for one, learned a great deal by working on this project and expect these lessons to be useful in a wide range of development tasks.&lt;/p&gt;</content:encoded><h:img src="/_astro/pexels-brett-sayles-2881232.Bgj0P1cv.jpg"/><enclosure url="/_astro/pexels-brett-sayles-2881232.Bgj0P1cv.jpg"/></item><item><title>Understanding the universality of computation</title><link>https://alimnaqvi.com/blog/computational-universality</link><guid isPermaLink="true">https://alimnaqvi.com/blog/computational-universality</guid><description>Turing&apos;s theory of universal computation is one of our deepest theories about the universe, one of the four strands of the fabric of reality.</description><pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Turing principle is so fundamental to our understanding of reality that David Deutsch considers it to be one of the four main constituents of his &quot;theory of everything&quot; (the other three being quantum theory, neo-Darwinian theory of evolution, and Popper&apos;s theory of epistemology).&lt;/p&gt;
&lt;h2&gt;The Church-Turing Conjecture&lt;/h2&gt;
&lt;p&gt;In 1936, mathematicians Alan Turing, Alonzo Church, and Emil Post — trying to understand the precise nature of &quot;computing&quot; (or &quot;calculating&quot; or &quot;proving&quot;) — independently conjectured that what can or cannot be computed does not depend on things like the design of the computer, but is &lt;em&gt;universal&lt;/em&gt;. This is now known as the Church-Turing conjecture (or hypothesis or thesis) and can be stated as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Everything that is computable can be computed by a Turing machine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Turing machine is not a very complex object. The example that Alan Turing &lt;a href=&quot;https://www.cs.virginia.edu/~robins/Turing_Paper_1936.pdf&quot;&gt;gave&lt;/a&gt; was that of a long strip of paper with a mechanism to read, write, and erase symbols on it, and move to other parts of the strip depending on the symbols. But this is just one implementation of the Turing machine, which is an abstract concept. It&apos;s irrelevant what material the computer is made of — it just needs to satisfy the read-write-erase-move criteria (i.e., be Turing-complete). This makes the Turing machine a universal computer (an abstract one), a computer that can do what any other computer can do.&lt;/p&gt;
&lt;p&gt;The epoch-making genius of Alan Turing, and why he deserves special mention among the other mathematicians who came at this, was that his model was the closest to being physical (as opposed to abstract mathematical) and he fully understood and articulated this &lt;em&gt;universality&lt;/em&gt; of computation: the fact that there is nothing special about the hardware of a computer — the nature of computation does not depend on which physical object is performing it. He conjectured that one device as simple as the Turing machine can perform any possible computation — it just needs to be provided with the right algorithm (software) to do so. If something is computable, a Turing machine can compute it; if something is uncomputable, no other possible object can compute it.&lt;/p&gt;
&lt;p&gt;Remember that this was before the &quot;age of computing&quot;. Turing later went on to &lt;a href=&quot;https://courses.cs.umbc.edu/471/papers/turing.pdf&quot;&gt;propose&lt;/a&gt;, quite correctly, that a universal computer such as a Turing machine could even be made to &quot;think&quot;, i.e., become intelligent, if it is provided with the right &quot;programming&quot;. This is why Alan Turing is considered one of the &quot;founding fathers&quot; of the fields of computer science as well as artificial intelligence. (Interestingly, Ada Lovelace, almost a hundred years before Turing, came close to understanding this universality, &lt;a href=&quot;https://www.cs.yale.edu/homes/tap/Files/ada-lovelace-notes.html&quot;&gt;suggesting&lt;/a&gt; that computers could be made to do much more than number-crunching, such as music generation, but she erroneously ruled out the possibility of their originating autonomous thought.).&lt;/p&gt;
&lt;p&gt;It might seem like an abrupt jump to go from &quot;being able to compute everything that is computable&quot; to &quot;being able to simulate a mind&quot;. I will explain the connection shortly.&lt;/p&gt;
&lt;p&gt;But first, I hasten to add that it is now known that the above-stated Church-Turing hypothesis could at best only be true of classical computation, because quantum computers are capable of performing computations that no classical computer can (a Turing machine is a classical computer). One example of such a computation is the generation of true random numbers. But that is not a fatal blow to the hypothesis. As I mentioned, the Turing machine is just one example that Turing used to prove that it is possible for one machine to perform any possible computation. To make the Turing machine truly universal, we just need to modify its design to make it a quantum computer. So, restating the Church-Turing hypothesis to take that fact into account:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There exists an abstract universal computer that can compute everything that is computable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Stating the hypothesis this way also takes the focus off a single design of a computer — the Turing machine. I think many people learning about this focus too much on the Turing machine itself and miss the truly astonishing part: the nature of reality is such that one computer, no matter where it is in the universe and no matter what it is made of, can compute what anything else in the universe can compute.&lt;/p&gt;
&lt;h2&gt;Computation connects mathematics to physics&lt;/h2&gt;
&lt;p&gt;Turing did not explicitly state it, but it is implicit in his work that &quot;everything that is computable&quot; refers to everything that is computable &lt;em&gt;in physical reality&lt;/em&gt;, because the laws of physics determine what is and is not computable.&lt;/p&gt;
&lt;p&gt;To compute means to perform a calculation. It refers to the physical application of some mathematical theory (rules and formulae). Given a (physical) input, a computer processes it (using some physical medium) and provides a certain (physical) output. Humans have been building computing devices since prehistoric times. Using tally marks or even your fingers to do counting is like using a crude computer. The abacus was a more sophisticated early computer. The ancient Greeks built devices to predict eclipses, e.g., &lt;a href=&quot;https://en.wikipedia.org/wiki/Antikythera_mechanism&quot;&gt;the Antikythera mechanism&lt;/a&gt;. The modern word &quot;computer&quot; has come to mean a &lt;em&gt;programmable&lt;/em&gt; computer, meaning we can relatively easily change the algorithm/software it uses to convert an input into an output without changing anything about the hardware. But these are just a special case of a &quot;computer&quot; in the deeper sense.&lt;/p&gt;
&lt;p&gt;So computing is how mathematics (which is abstract) interacts with physical reality. Even if a mathematician is working on &quot;pure mathematics&quot; without using a conventional computer, she is still using some physical system, mainly her brain along with maybe pen and paper, to perform computations. Mathematically proving a theorem is exactly equivalent to performing some computation (usually using the computer known as the human brain).&lt;/p&gt;
&lt;p&gt;When we re-examine the Church-Turing conjecture while keeping this physical definition of computation in mind, it has been suggested that it be called the &quot;Turing principle&quot;, not a hypothesis or a conjecture, to make it clear that it is a physical principle, comparable to, for example, the laws of thermodynamics or the gravitational equivalence principle. So &lt;strong&gt;the Turing principle&lt;/strong&gt; can be stated as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There exists an abstract universal computer that can perform any computation that any physical object can perform.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We are still talking about an abstract universal computer. However, note that the only thing that makes the Turing machine abstract instead of real is that it requires unlimited hardware resources: unlimited memory (i.e., an infinitely long paper tape), unlimited running time (no limit to how much time it can take to perform computations), and an unlimited energy supply. This is needed to remove the limitation of computations that can be performed in principle but are not practicable because they require infeasible amounts of memory and/or time. In order to translate the abstraction of the Turing machine into a real physical device, all we need to say is that such a physically possible device can compute anything that is computable, as long as we provide it with enough paper tape, time, and energy. Similarly, the quantum equivalent of the Turing machine can also be conceived, and has been &lt;a href=&quot;https://www.daviddeutsch.org.uk/wp-content/deutsch85.pdf&quot;&gt;described&lt;/a&gt; by David Deutsch.&lt;/p&gt;
&lt;p&gt;Clearly, given the above, it must be possible to build a physical universal computer. It will only be limited by the hardware that it has access to. And we can continue to supply it with resources (energy and memory) until we run out of matter in the universe. So we can do away with talking about abstract computers and talk about physically possible computers:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is possible to build a universal computer, a physical object that can perform any computation that any other physical object can perform.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Simulating anything in the universe&lt;/h2&gt;
&lt;p&gt;Now, why should &quot;everything that is computable&quot; be of interest to anybody other than mathematicians and theoretical computer scientists? Why should the Turing principle deserve to be considered a part of any viable &quot;theory of everything&quot;?&lt;/p&gt;
&lt;p&gt;The answer: Because computers can &lt;em&gt;simulate&lt;/em&gt; physical reality. It turns out that simulating physical processes using a computer has also been common since prehistoric times. Consider a goat herder putting tally marks on a wooden stick to count his goats. As the goats leave the pen, he puts a tally mark on a stick for each goat. When the goats return, he checks whether the number of tally marks is equal to the number of goats. If they are not, either some goats are missing or he has got somebody else’s goats (assuming his calculation is accurate). So one physical system, the goats, is being simulated by a completely different physical system: tally marks on a wooden stick. Note that in order to do this, the goat herder must assume that the laws of physics are such that it is irrelevant what physical system he uses to simulate the count of goats. Of course, this example is that of a very crude simulation, and we know that we can do much better.&lt;/p&gt;
&lt;p&gt;Simulating physical processes using computers is nowadays known as generating &lt;em&gt;virtual reality&lt;/em&gt;. The hardware that exists today can already generate sounds indistinguishable from natural sounds to the human ear. Indeed, it&apos;s meaningless to ask whether a sound is &quot;real&quot; or &quot;artificial&quot; if it is causing the exact same effect on the listener&apos;s ear and brain. The technology of computer-generated graphics is rapidly improving, and those for other senses like touch, smell, and taste are also not difficult to foresee. To create a perfect simulation of reality, the hardware advances that need to be made are relatively trivial compared to the software advances. The software advances in question are no different from advances in understanding the physics of the real world. Thus, it is in principle possible to create a virtual reality rendering that is indistinguishable from actual reality, &lt;em&gt;if&lt;/em&gt; we knew the real laws of physics to arbitrary accuracy. To take the converse of that: Computers can perfectly simulate (a given part of) reality because reality is rather like a computer; the initial conditions are the input, the laws are the software/program, and the final conditions are the output. This breathtaking insight is due to David Deutsch, who has &lt;a href=&quot;https://www.daviddeutsch.org.uk/wp-content/deutsch85.pdf&quot;&gt;stated&lt;/a&gt; the strongest, all-embracing, physical form of the Turing principle, known as the &lt;strong&gt;Church–Turing–Deutsch principle&lt;/strong&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every finite physical process can be perfectly simulated by a finite universal computer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;These days we take for granted the ability of computers to simulate everything from &lt;a href=&quot;https://www.youtube.com/watch?v=VWNAZ6PZR8I&quot;&gt;the aerodynamics of a vehicle&lt;/a&gt; to &lt;a href=&quot;https://www.youtube.com/watch?v=4disyKG7XtU&amp;#x26;list=RD4disyKG7XtU&quot;&gt;the collision of galaxies&lt;/a&gt;. The Turing principle (in its strongest form that Deutsch has argued for) makes it clear that our computers can simulate &lt;em&gt;any part of reality with perfect accuracy&lt;/em&gt;, given the right program. This is a profound fact about reality that has far-reaching consequences.&lt;/p&gt;
&lt;p&gt;David Deutsch uses the term &quot;self-similarity&quot; for this remarkable property of reality:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;... physical reality is self-similar on several levels: among the stupendous complexities of the universe and multiverse, some patterns are nevertheless endlessly repeated. Earth and Jupiter are in many ways dramatically dissimilar planets, but they both move in ellipses, and they are made of the same set of a hundred or so chemical elements (albeit in different proportions), and so are their parallel-universe counterparts. The evidence that so impressed Galileo and his contemporaries also exists on other planets and in distant galaxies. The evidence being considered at this moment by physicists and astronomers would also have been available a billion years ago, and will still be available a billion years hence. The very existence of general, explanatory theories implies that disparate objects and events are physically alike in some ways. The light reaching us from distant galaxies is, after all, only light, but it looks to us like galaxies. Thus reality contains not only evidence, but also the means (such as our minds, and our artefacts) of understanding it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is the presence of self-similarity that makes the world comprehensible to beings like us. The Turing principle shows one of the ways in which reality is self-similar. There is something exceptionally computation-friendly about the laws of physics as we find them.&lt;/p&gt;
&lt;h2&gt;Virtual reality is how we understand&lt;/h2&gt;
&lt;p&gt;The human brain is a physical object, a universal computer — at least a classical universal computer since it is Turing-complete (it can mimic a Turing machine, e.g., by using the read-write-erase-move procedure mentioned earlier). Our experience of the world is literally virtual reality. We never see what is &quot;really out there&quot;. We don&apos;t even see what is in our brains: electrochemical phenomena. What we actually experience is the (clearly inaccurate) virtual reality rendering of that which is out there. The program/software that dictates how the raw sensory input is processed into the virtual reality that we end up experiencing is partly inborn and partly acquired throughout life.&lt;/p&gt;
&lt;p&gt;To improve our understanding of the world, we habitually update the program of the brain by acquiring new theories. The Turing principle implies that what makes us intelligent, and able to comprehend the world better than any other animal, could not be any magical property of the hardware of our brain. The hardware is irrelevant as long as it is a universal computer, which is a very low bar since something as simple as a Turing machine (or more accurately its quantum counterpart) is a universal computer. Our personal computers and most programming languages are also Turing-complete and have been for decades. What makes us intelligent is the programming of our brain and the fact that we are able to update that programming. Not only does that make us &quot;more intelligent than other animals&quot;, but it makes us qualitatively different from them: Creating and updating virtual reality is our ecological niche. We are the only (remaining) species whose members rely mainly on updating their virtual reality rendering (i.e., creating knowledge) to survive.&lt;/p&gt;
&lt;p&gt;We understand the world by creating explanations and updating our inborn assumptions. For example, there is nothing in our genes that tells us how to make fire. Or how to make arrows and other sharp objects to hunt or fend off animals much bigger than us. Once someone has created a theory, the same ability to generate virtual reality also allows us to pass on that information to other members of our species, so they don&apos;t have to &quot;rediscover fire&quot; or &quot;reinvent the wheel&quot; (meme replication requires that knowledge be encoded in some medium, such as speech or writing, and then decoded by another individual creatively, i.e., by creating a virtual reality rendering of the encoded message). The virtual reality renderings in the brains of other animals, insofar as there are any, are defined by their genes. They cannot update the program in any significant way in their lifetime, but we can. This fact has incidentally given us the ability to reason about &lt;em&gt;everything&lt;/em&gt; — the Turing principle necessitates that there is no limit to how accurate our rendering of reality can become. When our intuitions tell us that the Earth is flat, we can reason by creating explanations and testing them against reality. No matter how strong our intuitions are (it really, really feels like the Earth is flat), we have the ability to see past them and get &lt;em&gt;closer to reality&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I will leave you with a quote by David Deutsch that summarizes the significance of the Turing principle better than I ever can:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is the strongest form of the Turing principle. It not only tells us that various parts of reality can resemble one another. It tells us that a single physical object, buildable once and for all (apart from maintenance and a supply of additional memory when needed), can perform with unlimited accuracy the task of describing or mimicking any other part of the multiverse. The set of all behaviours and responses of that one object exactly mirrors the set of all behaviours and responses of all other physically possible objects and processes.&lt;/p&gt;
&lt;p&gt;This is just the sort of self-similarity that is necessary if ... the fabric of reality is to be truly unified and comprehensible. If the laws of physics as they apply to any physical object or process are to be comprehensible, they must be capable of being embodied in another physical object – the knower. It is also necessary that processes capable of creating such knowledge be physically possible. Such processes are called science. Science depends on experimental testing, which means physically rendering a law’s predictions and comparing it with (a rendering of) reality. It also depends on explanation, and that requires the abstract laws themselves, not merely their predictive content, to be capable of being rendered in virtual reality. This is a tall order, but reality does meet it. That is to say, the laws of physics meet it. The laws of physics, by conforming to the Turing principle, make it physically possible for those same laws to become known to physical objects. Thus, the laws of physics may be said to mandate their own comprehensibility.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><h:img src="/_astro/Alan_Turing_statue.DPFrH1uh.jpg"/><enclosure url="/_astro/Alan_Turing_statue.DPFrH1uh.jpg"/></item><item><title>My quest for the best podcast app</title><link>https://alimnaqvi.com/blog/podcast-app-comparison</link><guid isPermaLink="true">https://alimnaqvi.com/blog/podcast-app-comparison</guid><description>I decided to conduct detailed testing and comparison of virtually every podcast app available on iOS to find the best fit for my needs.</description><pubDate>Sun, 16 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;I listen to 10-20 hours of audio content per week. So, it&apos;s only reasonable that I invest time in finding a good app that makes the listening experience better and helps me be more productive.&lt;/p&gt;
&lt;p&gt;I have long been a user of &lt;strong&gt;Pocket Casts&lt;/strong&gt; (with its Plus plan). The main selling point of &lt;strong&gt;Pocket Casts&lt;/strong&gt; for me was its folders and bookmarks. Overall it is a very decent app, but it lacks some features, such as the ability to search for a keyword in all the episodes of subscribed shows. You can only search show by show, and given that I am subscribed to over 230 shows, that’s just not feasible. It provides &lt;em&gt;a&lt;/em&gt; transcript feature that is based on the Podcasting 2.0 Transcript tag, meaning it relies on the creators manually providing a transcript, which most creators do not. So the vast majority of podcasts currently don’t have transcripts available on &lt;strong&gt;Pocket Casts&lt;/strong&gt;; automatic generation of transcripts &lt;em&gt;is not&lt;/em&gt; available.&lt;/p&gt;
&lt;p&gt;Here is a list of features I wanted in my ideal podcast app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Organize subscribed shows into categories (e.g., using folders or tags).&lt;/li&gt;
&lt;li&gt;Search for episode keywords &lt;em&gt;within&lt;/em&gt; a specific show.
&lt;ul&gt;
&lt;li&gt;I assumed this was a standard feature, but &lt;strong&gt;Apple Podcasts&lt;/strong&gt; proved me wrong.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Search for episode keywords &lt;em&gt;across all&lt;/em&gt; subscribed shows.&lt;/li&gt;
&lt;li&gt;Clip-sharing with others, even if they don’t use the same app.&lt;/li&gt;
&lt;li&gt;Import podcasts easily from another player (e.g., via OPML).
&lt;ul&gt;
&lt;li&gt;Again, I was surprised that &lt;strong&gt;Apple Podcasts&lt;/strong&gt; lacks standard OPML import/export functionality.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Support for multiple queues or playlists, beyond just a single &quot;up next&quot; list.&lt;/li&gt;
&lt;li&gt;Bookmarks for marking interesting points in an episode, ideally with notes/comments.&lt;/li&gt;
&lt;li&gt;Episode transcripts (either via Podcasting 2.0 tag or auto-generated).&lt;/li&gt;
&lt;li&gt;Detailed listening statistics (e.g., listening time per week, most listened-to shows per month).&lt;/li&gt;
&lt;li&gt;(Price is, of course, always a factor, but given how much value I get from podcasts—and since most apps offer free tiers with optional premium features—this was a secondary consideration.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Clearly, this is a demanding list, and not every feature is a must-have. I also haven&apos;t listed the very basic features that virtually every podcast app includes, like custom playback speed or adding shows via RSS URL. Below, I will discuss the relative importance (weight) I assigned to each desired feature.&lt;/p&gt;
&lt;h2&gt;Testing Setup&lt;/h2&gt;
&lt;p&gt;So, in early 2025, about a month before my &lt;strong&gt;Pocket Casts&lt;/strong&gt; Plus yearly subscription was due to renew, I went on a hunt for the best podcast-listening app out there for iOS. To my surprise, there &lt;em&gt;aren&apos;t&lt;/em&gt; that many contenders.&lt;/p&gt;
&lt;p&gt;On Reddit (across different subreddits), there seems to be a strong consensus that Podcast Addict is the best podcast app overall. Unfortunately, it is Android-only, so it wasn&apos;t an option for me. Beyond that, opinions (on Reddit and elsewhere) are very mixed. I had to test the apps myself to find out.&lt;/p&gt;
&lt;p&gt;I selected various apps based on online recommendations and App Store searches. I initially downloaded and briefly evaluated (at least) the following apps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pocket Casts&lt;/strong&gt; (My baseline app)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overcast&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Castro&lt;/li&gt;
&lt;li&gt;Downcast&lt;/li&gt;
&lt;li&gt;Snipd&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apple Podcasts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Spotify&lt;/li&gt;
&lt;li&gt;Audible&lt;/li&gt;
&lt;li&gt;Amazon Music&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSSRadio&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fountain&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Podurama&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Podcat&lt;/li&gt;
&lt;li&gt;Podimo&lt;/li&gt;
&lt;li&gt;Castbox&lt;/li&gt;
&lt;li&gt;Podbean&lt;/li&gt;
&lt;li&gt;Podger&lt;/li&gt;
&lt;li&gt;Kasey Podcast&lt;/li&gt;
&lt;li&gt;Luminary&lt;/li&gt;
&lt;li&gt;Podcast Republic&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Testing Details&lt;/h2&gt;
&lt;p&gt;Some apps from the initial list were quickly eliminated due to obvious omissions of key features (a lack of folders/tags was often the easiest deal-breaker to spot). After this first pass, I shortlisted the following apps for more detailed testing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pocket Casts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overcast&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apple Podcasts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSSRadio&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fountain&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Podurama&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I used each of these six apps as my primary podcast player for at least a week. I imported all my (230+) podcast subscriptions via OPML into each app, where supported. (As mentioned, &lt;strong&gt;Apple Podcasts&lt;/strong&gt; lacks OPML import, so I manually added only my most frequently listened-to shows, not the full 230+ list).&lt;/p&gt;
&lt;p&gt;An obvious winner didn’t immediately emerge. Each app had significant strengths and weaknesses relative to my criteria. It became clear I likely wouldn&apos;t find one perfect app with every feature exactly as I wanted. Therefore, I decided to perform a quantitative comparison.&lt;/p&gt;
&lt;p&gt;For each desired feature, I rated its implementation in each app on a scale of 0-10 (where 0 means absent or unusable, and 10 means perfectly implemented for my needs). I also assigned a weight to each feature (0.0-1.0 scale) based on its importance to me. For more details on &lt;em&gt;why&lt;/em&gt; I gave a specific score, hover/tap on the numbers in the table below.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Feature importance (weight) (0.0-1.0)&lt;/th&gt;
&lt;th&gt;Pocket Casts (0-10)&lt;/th&gt;
&lt;th&gt;Fountain (0-10)&lt;/th&gt;
&lt;th&gt;Apple Podcasts (0-10)&lt;/th&gt;
&lt;th&gt;Podurama (0-10)&lt;/th&gt;
&lt;th&gt;RSS Radio (0-10)&lt;/th&gt;
&lt;th&gt;Overcast (0-10)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Folders/Organization&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playlists / Multiple Queues&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clip-sharing&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bookmarks&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transcripts&lt;/td&gt;
&lt;td&gt;0.4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search within one show&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search within all subscribed shows&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listening stats&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premium price (annual value)&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total weighted score&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;28.4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;29.9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30.4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;28.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total unweighted score&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&quot;Total unweighted score&quot; is the simple sum of the scores for each feature (maximum possible is 90 based on 9 features listed). &quot;Total weighted score&quot; is the more important metric for my decision; it&apos;s calculated by multiplying each feature’s score by its assigned importance weight, and then summing these products.&lt;/p&gt;
&lt;p&gt;While this isn&apos;t the most rigorous quantitative analysis possible, it served its purpose well by providing a structured comparison based on my priorities (high value-to-time-invested ratio). The scores and weights are subjective; I experimented with adjusting them slightly based on my perceptions, but the overall ranking didn&apos;t change significantly.&lt;/p&gt;
&lt;p&gt;Based on the weighted scores, &lt;strong&gt;Podurama&lt;/strong&gt; comes out on top for my specific needs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Podurama&lt;/strong&gt; (30.4)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fountain&lt;/strong&gt; (29.9)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overcast&lt;/strong&gt; (28.9)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pocket Casts&lt;/strong&gt; (28.4)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSS Radio&lt;/strong&gt; (24.5)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apple Podcasts&lt;/strong&gt; (14.0)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;However, the scores for the top four apps are very close to each other, indicating that the decision wasn&apos;t clear-cut and involved trade-offs.&lt;/p&gt;
&lt;h2&gt;Podurama&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Main Positives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Offers a ton of options and settings: highly customizable launch screen, ability to set different settings per show, bulk editing of episodes, etc.&lt;/li&gt;
&lt;li&gt;Provides multiple ways to learn about an episode before listening (AI-generated insights, chapters, snippets). The &quot;Popular&quot; episodes section within shows can sometimes aid discovery.&lt;/li&gt;
&lt;li&gt;Includes a web app (I haven&apos;t used it extensively, but its availability is a plus for some users).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Main Negatives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No clip-sharing functionality.&lt;/li&gt;
&lt;li&gt;Does not support searching within episodes across all subscribed shows.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Podurama&lt;/strong&gt; is big on machine learning; it offers various “AI features” such as podcast summaries, AI-generated chapters, and episode trailers/snippets. However, I personally don&apos;t find such features so useful.&lt;/p&gt;
&lt;h2&gt;Fountain&lt;/h2&gt;
&lt;p&gt;This was probably the least-known app among my shortlist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main Positives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The clip-sharing feature is outstanding (rated 10/10). It&apos;s easy to use and share, even with people not using Fountain.&lt;/li&gt;
&lt;li&gt;Transcripts are generally very good. Seem to be using an advanced ML model.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Main Negatives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Transcript text cannot be copied (a drawback for quoting or saving snippets).&lt;/li&gt;
&lt;li&gt;Search within a show is limited to episode &lt;em&gt;titles only&lt;/em&gt;; it does not search descriptions or show notes. This is a considerable deficit for my search-heavy workflow.&lt;/li&gt;
&lt;li&gt;No dedicated bookmarking feature. While creating clips serves as a &lt;em&gt;workaround&lt;/em&gt; (saved clips act like bookmarks), it&apos;s slower and less convenient than a dedicated bookmark button (hence the 3/10 score).&lt;/li&gt;
&lt;li&gt;Occasional performance issues (freezing/slowing during search), though infrequent enough not to be a deal-breaker.&lt;/li&gt;
&lt;li&gt;Timestamps in show notes are not clickable. This is a major nuisance, as many podcasts use show note timestamps for navigation instead of formal chapter markers.&lt;/li&gt;
&lt;li&gt;Lacks customization for the order of shows/episodes on the main screen (shows are sorted alphabetically, episodes by recency), rendering the launch screen less useful for prioritizing content.&lt;/li&gt;
&lt;li&gt;Incomplete show notes observed for some episodes (text gets truncated).&lt;/li&gt;
&lt;li&gt;Lacks advanced playback features like silence skipping (&quot;Smart Speed&quot; in Overcast) and volume boost.&lt;/li&gt;
&lt;li&gt;Sometimes fails to pause playback via headphone controls.&lt;/li&gt;
&lt;li&gt;No OPML export option, making it difficult to switch away from Fountain in the future.&lt;/li&gt;
&lt;li&gt;No web app.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Brief Notes on the Remaining Runners-Up&lt;/h2&gt;
&lt;p&gt;Based on my criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pocket Casts&lt;/strong&gt;: Its primary strength remains its intuitive show organization via folders. The smooth drag-and-drop interface allows full customization of show order within folders. Its bookmarking is excellent (10/10). However, its lack of global search and auto-generated transcripts were major drawbacks for me.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overcast&lt;/strong&gt;: Stands out for its minimalistic design, high performance, excellent global search (10/10), and valuable playback features like Smart Speed and Voice Boost (reflected partly in the Price/Value score, as these are free). Its lack of folder organization (0/10) and basic bookmarking were key weaknesses for me. Its playlist implementation is strong (10/10).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apple Podcasts&lt;/strong&gt;: It is, well, &lt;em&gt;Apple&lt;/em&gt; Podcasts. It is a first-party app that comes pre-installed on Apple devices. If you like Apple’s approach to doing things (privacy, etc.), this app might be for you. It offers excellent global search (10/10) and widely available transcripts (10/10). However, it severely lacks organizational features (no folders - 0/10), offers no OPML import/export, and has minimal queue/playlist management (0/10).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;After this extensive testing, I have switched to &lt;strong&gt;Podurama&lt;/strong&gt; as my primary podcast app. However, the fact that this detailed analysis was necessary, and the closeness of the scores, highlight that there is no perfect podcast app, at least for my specific needs.&lt;/p&gt;
&lt;p&gt;I&apos;ve actually settled on a multi-app &lt;em&gt;system&lt;/em&gt;, keeping several apps on my phone to leverage the strengths of each:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Podurama&lt;/strong&gt;: My main app for daily listening, chosen for its excellent organization (folders - 9/10), good bookmarking (10/10), and customization options.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fountain&lt;/strong&gt;: Used specifically when I want to create and share audio clips with others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overcast&lt;/strong&gt;: Kept primarily for its powerful keyword search across &lt;em&gt;all&lt;/em&gt; episodes of my subscribed shows (10/10), a feature Podurama lacks. Its Smart Speed is also beneficial.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apple Podcasts&lt;/strong&gt;: Used mainly for its high-quality transcript feature (10/10) or for browsing Apple&apos;s unique podcast charts and recommendations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This fragmented approach isn&apos;t ideal, but it&apos;s the best way I&apos;ve found to access the features I value most. Maybe an app will improve to combine all these strengths one day. Maybe they won’t, so I might even consider building my own podcast app at some point to finally get everything I want in one place.&lt;/p&gt;</content:encoded><h:img src="/_astro/pexels-sorjigrey-9956822.DsIg5z7B.jpg"/><enclosure url="/_astro/pexels-sorjigrey-9956822.DsIg5z7B.jpg"/></item><item><title>How I built this website starting with no web dev experience</title><link>https://alimnaqvi.com/blog/this-website</link><guid isPermaLink="true">https://alimnaqvi.com/blog/this-website</guid><description>It required getting the hang of HTML, CSS, Javascript, and TypeScript, among other things. After navigating a slew of frameworks, I settled on Astro.</description><pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The idea of having a website has long enticed me. I take a lot of notes and take great care to keep them well-organized. Writing useful ideas down has the obvious upside of reliable retrieval of information compared to human memory. I also find that writing helps me sharpen my thinking on any given topic. I largely agree with &lt;a href=&quot;https://www.cold-takes.com/learning-by-writing/&quot;&gt;Holden Karnofsky&apos;s Learning by Writing&lt;/a&gt;. It is so often the case that an idea makes perfect sense in my head, but when I start writing it down, there turn out to be glaring holes in my understanding. Or at least, it reveals various assumptions that had been implicit.&lt;/p&gt;
&lt;p&gt;So having personal notes is great. But many of them deserve to be out in the open, for anyone to discover, learn, and criticize. This is how knowledge grows.&lt;/p&gt;
&lt;p&gt;One common idea I encountered and sometimes believed was that having your own website is akin to reinventing the wheel. Why not use already established platforms like Medium or Substack? The answer is that while those platforms have their use cases, having your own website gives you extremely fine-grained control over everything. It might not be for everyone to create your own website from scratch. But there are gradations of the level of control that one may want. There are countless services available these days that can help you &quot;spin up&quot; and host a website.&lt;/p&gt;
&lt;p&gt;When one thinks of starting a blog, often the first thing that comes to mind is WordPress. It was no different for me. But the option that comes to mind first is hardly ever the best option. So I started researching my options. While my coding knowledge is rapidly growing, especially in &lt;a href=&quot;./learning-c-first&quot;&gt;lower-level programming&lt;/a&gt;, I have to be strategic about what I spend my time learning.&lt;/p&gt;
&lt;h2&gt;The full spectrum of blog-building tools from low-level to high level&lt;/h2&gt;
&lt;h3&gt;Level 1: Pure Code&lt;/h3&gt;
&lt;p&gt;At the fundamental level, all websites are HTML, which gives structure to the content you see, styled with CSS. JavaScript is used to manipulate the HTML and CSS to add interactivity and dynamic features. So HTML, CSS, and JavaScript are sent by the server and received by the browser, which renders the website that you see. Note that the JavaScript sent to the browser is called &quot;client-side JavaScript&quot; (JavaScript has applications that are far wider than just this) and this is only done in case something interactive or &quot;dynamic&quot; needs to happen on the website without reloading the page (without asking the server to send the HTML of the whole page again), e.g., the top bar of this website is dynamic (it appears on upscrolling and disappears on downscrolling). Here&apos;s some client-side JavaScript for you to play with:&lt;/p&gt;
&lt;p&gt;I am dynamic!&lt;/p&gt;
&lt;p&gt;One can theoretically build a website from scratch with just vanilla &lt;strong&gt;HTML&lt;/strong&gt;, &lt;strong&gt;CSS&lt;/strong&gt;, and &lt;strong&gt;JavaScript&lt;/strong&gt;. However, in practice, building something similar to this website would be extremely time-consuming. It would be difficult to manage content and it would be hard to scale (every post is a new HTML file or requires complex JavaScript). There are some limited use cases for this approach: Learning web fundamentals, very small personal sites where tinkering &lt;em&gt;is&lt;/em&gt; the goal, digital gardens built piece-by-piece.&lt;/p&gt;
&lt;p&gt;Building one&apos;s website from scratch with this approach would really be reinventing the wheel. In programming and computer science, layers of abstraction exist because the lower level functionality becomes standardized. Unless there is a specific need for lower-level control, we abstract away (automate) the repetitive stuff so that we can build larger, more complex things.&lt;/p&gt;
&lt;h3&gt;Level 2: Frontend Libraries/Frameworks&lt;/h3&gt;
&lt;p&gt;The next layer is to use libraries and frameworks to structure your code. With frameworks like &lt;strong&gt;React.js&lt;/strong&gt;, &lt;strong&gt;Vue.js&lt;/strong&gt;, &lt;strong&gt;Svelte&lt;/strong&gt;, and &lt;strong&gt;Angular&lt;/strong&gt;, you can create reusable components. For example, you can write HTML for a &quot;button&quot; and create a &lt;code&gt;Button&lt;/code&gt; component. This way, every time you want to insert a button in your web page, you just use the &lt;code&gt;Button&lt;/code&gt; component instead of repeating all the HTML that went into creating the button. You can even pass it some properties so that one &lt;code&gt;Button&lt;/code&gt; component can be used to refer to different kinds of buttons (e.g., colors, size, text, etc.). This provides much better code organization.&lt;/p&gt;
&lt;p&gt;Creating a component is just like writing a function in a programming language once, and then simply calling that function (with specific parameters if needed) to perform that action as many times as needed, without needing to worry about the code and inner workings of the function. While this significantly reduces the development time of the frontend, you still need to build the &quot;blog&quot; functionality yourself (routing, fetching data, rendering Markdown, etc.). Use cases for this level include building highly custom web applications where a blog is just one part, or as the foundation for tools in the next level.&lt;/p&gt;
&lt;h3&gt;Level 3: Static Site Generators (SSGs) &amp;#x26; Meta-Frameworks&lt;/h3&gt;
&lt;p&gt;These tools take your content (often Markdown files), apply templates, and generate static HTML, CSS, and JavaScript. This way, your focus shifts more to content, instead of the HTML, CSS, and JavaScript code. You still have a lot of control over how the translation from Markdown to HTML (and CSS and JavaScript) takes place. Tools in this layer include &lt;strong&gt;Next.js&lt;/strong&gt;, &lt;strong&gt;Astro&lt;/strong&gt;, &lt;strong&gt;Gatsby&lt;/strong&gt;, &lt;strong&gt;Eleventy (11ty)&lt;/strong&gt;, &lt;strong&gt;Hugo&lt;/strong&gt;, and &lt;strong&gt;Jekyll&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;These tools are mostly developer-focused but differ vastly from each other and the learning curve varies. For example, it is possible to quickly spin up a website with Hugo (using a theme that someone else built) with very little coding knowledge. Next.js, on the other hand, is a very developer-focused but extremely powerful framework built on top of React.js (previous level), and provides not only static site generation (SSG) but also server-side rendering (SSR) capabilities. Next.js has quickly become the go-to choice for building modern websites that require a range of complex features. However, it can be overkill for mostly-static blogs.&lt;/p&gt;
&lt;p&gt;In this level, you can write your content directly into text files (most often Markdown files). But since this can be inconvenient and lacking word-processing features, a headless CMS is a popular choice to manage content. These systems manage your content (posts, authors, categories) via a user-friendly interface and expose it to the site generators through an API, decoupling content management from the website&apos;s frontend. Popular choices for headless CMS are Strapi, Contentful, Sanity.io, Payload CMS, WordPress (used headlessly).&lt;/p&gt;
&lt;h3&gt;Level 4: Traditional/Monolithic CMS&lt;/h3&gt;
&lt;p&gt;All-in-one systems that provide an admin interface for content management &lt;em&gt;and&lt;/em&gt; render the frontend of the website using themes and templates. Often database-driven. Examples include &lt;strong&gt;WordPress.org&lt;/strong&gt; (self-hosted), &lt;strong&gt;Drupal&lt;/strong&gt;, &lt;strong&gt;Joomla&lt;/strong&gt;, and &lt;strong&gt;Ghost&lt;/strong&gt; (self-hosted option). Such systems require minimal coding and provide a lot of features out-of-the-box (or via plugins). Performance and level of customization can vary. Plugin/theme bloat or security issues are a risk if not managed appropriately.&lt;/p&gt;
&lt;h3&gt;Level 5: Hosted Platforms &amp;#x26; Website Builders&lt;/h3&gt;
&lt;p&gt;These are commercial services that bundle the software, hosting, maintenance, security, and often a visual drag-and-drop editor into one package. This is the easiest way to get started with minimal technical knowledge. Examples include &lt;strong&gt;WordPress.com&lt;/strong&gt; (hosted), &lt;strong&gt;Wix&lt;/strong&gt;, &lt;strong&gt;Squarespace&lt;/strong&gt;, &lt;strong&gt;Blogger&lt;/strong&gt; (Google), &lt;strong&gt;Ghost&lt;/strong&gt; (Pro-hosted), &lt;strong&gt;Medium&lt;/strong&gt;, and &lt;strong&gt;Substack&lt;/strong&gt;. The obvious downside is limited design/customization beyond the provided templates. Vendor lock-in can also be a big issue (you might not fully &quot;own&quot; your site&apos;s underlying code or have full data portability). Can also become expensive with higher tiers/features.&lt;/p&gt;
&lt;h2&gt;Learning web fundamentals&lt;/h2&gt;
&lt;p&gt;Before I could choose a tool to build my blog, one thing was obvious to me: I should get more familiar with the fundamentals of the web, both because it will help me build a better website, but also because it will be invaluable in my software engineering career, no matter which specialization I end up choosing. So I dove into learning the basics such as understanding the syntax and usage of HTML, CSS, and JavaScript. But I promised myself not to get bogged down into the fundamentals so much that it prevents me from building any higher-level things (thankfully I was already familiar with the phenomenon of &lt;a href=&quot;https://www.wbscodingschool.com/blog/what-is-tutorial-hell-how-to-get-out/&quot;&gt;Tutorial Hell&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;With that in mind, I went through a set of tutorials. Including &lt;a href=&quot;https://www.freecodecamp.org/learn/2022/responsive-web-design/&quot;&gt;freeCodeCamp&apos;s Responsive Web Design course&lt;/a&gt; to get familiar with HTML and CSS, &lt;a href=&quot;https://javascript.info/&quot;&gt;javascript.info&lt;/a&gt; to learn JavaScript, and various parts of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn_web_development/Core&quot;&gt;MDN Core Modules&lt;/a&gt; to sharpen concepts about frontend development. I went through these at varying speeds and often skipping or abandoning when I felt bogged down or the learning slowed down. The most important thing in my view is to get up to speed with enough of the basics to start building &lt;em&gt;something&lt;/em&gt;. Then building that &lt;em&gt;anything&lt;/em&gt; is a better teacher than passively consuming a large number of tutorials. And by basics, I don&apos;t necessarily mean that you have to start with the lowest-level explanation in terms of the hierarchy of complexity. After all, we don&apos;t need to know all the facts about the microelectronic structure underlying our computers in order to use the computers proficiently and productively. To &quot;understand&quot; something is very different from merely learning facts about something. &quot;Understanding is about coherence, elegance, and simplicity, as opposed to arbitrariness and complexity,&quot; as David Deutsch put it. It is about making meaningful connections between the different levels of abstraction, and being able to answer &lt;em&gt;why&lt;/em&gt; things are the way they are, and the &lt;em&gt;kinds of&lt;/em&gt; things that &lt;em&gt;must be&lt;/em&gt; happening, without necessarily being able to recite all the facts.&lt;/p&gt;
&lt;p&gt;In this case, this process consisted of understanding what websites really are (renderings of HTML, CSS, and JavaScript), how they are served (sent from the server to the browser), how they are hosted (choosing where to serve from), what kinds of tools are available to create different kinds of websites, which of those tools are most widely used and why, etc.&lt;/p&gt;
&lt;h2&gt;Choosing a website-building tool&lt;/h2&gt;
&lt;p&gt;After gaining a reasonable understanding of the fundamentals (how the web works) and thinking through the full spectrum of tools as laid down &lt;a href=&quot;./this-website#the-full-spectrum-of-blog-building-tools-from-low-level-to-high-level&quot;&gt;above&lt;/a&gt;, it was time for me to choose. The full spectrum didn&apos;t simply lay itself out in my head as it is listed here. There were naturally a lot of uncertainties and gaps in my understanding of the landscape. Not to mention that there was a lot of conflicting advice out there.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alimnaqvi.com/_image?href=%2F_astro%2Freddit-meme.DzaSsgJ1.jpg&amp;#x26;w=1164&amp;#x26;h=888&amp;#x26;f=webp&quot; alt=&quot;Reddit meme about how most advice on the internet is useless because it does not take your needs into account&quot;&gt;&lt;/p&gt;
&lt;p&gt;The best way to eliminate those uncertainties was to experiment. Actually getting hands-on, as opposed to reading people&apos;s opinions about it, is often the best way for me to learn about and understand a topic. I went through official tutorials / quick start guides of various tools/frameworks mentioned above (e.g., &lt;a href=&quot;https://react.dev/learn&quot;&gt;React&lt;/a&gt;, &lt;a href=&quot;https://nextjs.org/learn&quot;&gt;Next.js&lt;/a&gt;, &lt;a href=&quot;https://docs.astro.build/en/tutorial/0-introduction/&quot;&gt;Astro&lt;/a&gt;, &lt;a href=&quot;https://gohugo.io/getting-started/quick-start/&quot;&gt;Hugo&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I seriously considered all levels from level 2 and up since they all have their pros and cons. I wanted to have as much control (ownership of data as well as design) over my website as possible. But I wanted to balance that with how much time investment will be required to learn and manage that. Also, I know that my needs will change in the future and my knowledge will grow, so I needed scalability and portability (ability of the website to grow and transform, or even move to another framework if needed).&lt;/p&gt;
&lt;p&gt;I created a list of features that I would ideally like in my website (not necessarily immediately, but at some point in the future). I wanted the freedom to be able to implement features like customizable dynamic table of contents, progress bar that updates on scroll, system for searching and filtering posts, AI narration of articles, etc.&lt;/p&gt;
&lt;p&gt;For these reasons, the tools in level 4 and 5 were much less attractive. React and Next.js were the most powerful options (that have become the leading choice in the industry by professional web developers). If I was sure I wanted to become a web developer or if my needs were more complicated than a blog, I would certainly choose the React/Next.js stack. However, the learning curve for them is notoriously steep. For someone completely new to web development like me, at the start there would be all sorts of bugs, performance issues, and possibly even security vulnerabilities in the website. Also, Next.js shines when it comes to dynamic websites with complex features. It was designed for building full-blown web applications, not with focus on static site generation, which is where I wanted to start.&lt;/p&gt;
&lt;p&gt;So Next.js is something I will keep an eye on in the longer term. But for the time being, I turned my attention to tools that focus mostly on static site generation. Hugo and Astro are by far the most sophisticated and modern in this aspect (largely superseding the pioneering tools like Jekyll and Gatsby, although they still have their idiosyncrasies and use cases). Hugo&apos;s primary selling point is its superior &quot;build speeds&quot; (the time it takes to generate the HTML from the input Markdown files and configuration files) as it is written in &lt;a href=&quot;https://en.wikipedia.org/wiki/Go_(programming_language)&quot;&gt;Go&lt;/a&gt;. But I didn&apos;t understand why that aspect is so hyped. Maybe build speed is important when you have hundreds or thousands of pages on your website (like documentation sites). To me, it is a very minor aspect in the broader decision, when there are so many other deciding factors involved. Hugo mainly excels as building static sites, and does not make it straightforward to add client-side JavaScript for dynamic features. It does have numerous themes available to start with. But customizing them is not easy, as you&apos;re forced to write complex Go templates or hacky JavaScript workarounds. Hugo simply does not have &quot;highly customizable websites&quot; as its selling point. But that&apos;s exactly what Astro offers.&lt;/p&gt;
&lt;p&gt;Astro is a relatively new player, but one that has quickly won the hearts of many, due to the high utility-to-complexity ratio that it offers. Astro, like React/Next.js, is component-based, but unlike Next.js, it is &quot;framework-agnostic&quot; (not tied to a single framework), i.e., it allows you to use components from &lt;em&gt;any&lt;/em&gt; popular frameworks (React, Vue, Svelte, etc.) or &lt;em&gt;no framework at all&lt;/em&gt;. It also ships zero JavaScript to the client by default, but this behavior can be altered in a piecemeal way. So by default, all the &quot;input files&quot; like Markdown files and code/configuration files are used to generate purely static/HTML files, but &quot;partial hydration&quot; can be introduced, such that there are &quot;islands&quot; of dynamic content (client-side JavaScript), while the rest of the site remains static. This paradigm offers an incredible combination of performance and flexibility. The learning curve is also not as steep as Next.js. For beginners starting with any of these frameworks, it is usually a good idea to start with a theme (or &quot;starter templates&quot;) that someone has designed using that framework, and then modify that theme to suit your needs. All the mentioned frameworks have many themes available (&lt;a href=&quot;https://astro.build/themes/&quot;&gt;Astro&lt;/a&gt;, &lt;a href=&quot;https://themes.gohugo.io/&quot;&gt;Hugo&lt;/a&gt;, &lt;a href=&quot;https://vercel.com/templates/next.js&quot;&gt;Next.js&lt;/a&gt;), thanks to the generous contributions of the community members. I also found Astro themes to be much easier to heavily modify (and even mix and match) than others (especially Hugo).&lt;/p&gt;
&lt;h2&gt;Learning and navigating through Astro&lt;/h2&gt;
&lt;p&gt;Once I had chosen Astro, it was time to start building. As mentioned, choosing a theme and building upon it is usually a great way to kick-start this process. I scoured through the hundreds of themes that are available, to find something that has some of the features and aesthetic that I want, so I can build upon it. Some of the themes that fit the bill were &lt;a href=&quot;https://astro.build/themes/details/astro-micro/&quot;&gt;Astro Micro&lt;/a&gt;, &lt;a href=&quot;https://astro.build/themes/details/pure/&quot;&gt;Astro Pure&lt;/a&gt;, &lt;a href=&quot;https://astro.build/themes/details/nordlys/&quot;&gt;Nordlys&lt;/a&gt;, and &lt;a href=&quot;https://astro.build/themes/details/webtrotion/&quot;&gt;Webtrotion&lt;/a&gt;. After trying all of them out and testing and tinkering with them, I decided to use Astro Pure as the base of my website, and then heavily modify it. The generous &lt;a href=&quot;https://github.com/cworld1&quot;&gt;creator&lt;/a&gt; of the Pure theme has added lots of useful components to it (e.g., MediumZoom, QRCode, Aside, Tabs, etc.), that can be used not only in the theme, but also in other Astro projects.&lt;/p&gt;
&lt;p&gt;One important criterion for making such decisions is the breadth and depth of the available documentation. Astro itself naturally has extensive and high-quality &lt;a href=&quot;https://docs.astro.build/en/getting-started/&quot;&gt;documentation&lt;/a&gt;. But the Astro Pure theme is also reasonably &lt;a href=&quot;https://astro-pure.js.org/docs&quot;&gt;well-documented&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Just like I went about understanding the fundamentals of the web (see &lt;a href=&quot;./this-website#learning-web-fundamentals&quot;&gt;above&lt;/a&gt;), I went on to seek to understand how Astro works. I learned about the syntax of &lt;code&gt;.astro&lt;/code&gt; files, project and directory structure, configuration files, deployment options, Markdown and content collections, etc. At the same time, I kept deepening my understanding of HTML and how to style the HTML using CSS (especially learning Tailwind CSS) and to manipulate its structure using JavaScript and TypeScript.&lt;/p&gt;
&lt;p&gt;I made various changes to the Astro Pure theme. This included changing the overall aesthetic by using different colors, fonts, roundedness of edges, etc. I also created a system for &quot;topics&quot; instead of &quot;tags&quot; and made the &lt;a href=&quot;../tags&quot;&gt;topics page&lt;/a&gt; feel much more dynamic (while remaining fully static). That said, as it stands, the structure of my blog is still mostly based on the original codebase of the theme, instead of my modifications to it. The blog will naturally continue to evolve and new features will be added as time goes on.&lt;/p&gt;
&lt;h2&gt;Choosing a content management system&lt;/h2&gt;
&lt;p&gt;Once I was happy (for the time being) with the structure and look of the website, I focused my attention on thinking about the content management system that I will use to write my content. In the past, I used many note management systems such as Notion, Obsidian, OneNote, and Evernote. Notion provides a very user-friendly yet powerful interface to create complex pages, which can be easily exported as Markdown files. Notion can technically be used as a backend to generate the Markdown files. But it would add a lot of overhead and intricate setup to get it to work seamlessly, since Notion is cloud-first and all your notes are saved first and foremost on the cloud instead of in your local file system. Obsidian, on the other hand, is slightly less user-friendly, but works mainly with your local file system. It also has a thriving plugin ecosystem that provides powerful tools such as Git integration, frontmatter support, AI-powered spell &amp;#x26; grammar checking, etc. What&apos;s more, Obsidian has a great mobile app with which you can easily make and sync changes on the go.&lt;/p&gt;
&lt;p&gt;Obsidian seemed great for almost all my needs, except there was no way for me to seamlessly integrate it into my workflow. I do almost all my development on VS Code running on WSL (Windows Subsystem for Linux). So the local copy of all my Astro code (and blog posts as Markdown files) is located in the WSL file system instead of that of Windows. Obsidian installed on Windows unfortunately does not integrate well with WSL (and installing the Linux version of Obsidian inside WSL results in poor performance). So if I were to use Obsidian, I would have to set up additional syncing mechanisms (e.g., Git, Dropbox, or symbolic links) to synchronize between Obsidian on Windows and Astro on WSL. Additionally, Obsidian&apos;s support for &lt;a href=&quot;https://nextjs.org/docs/pages/guides/mdx&quot;&gt;MDX&lt;/a&gt; is limited, although this was not a big factor in my decision for my choice of content management system.&lt;/p&gt;
&lt;p&gt;By the way, I love VS Code. I think it is one of the best pieces of software ever developed. It is extremely versatile and works with so many use cases and development styles (I apologize to Neovim or JetBrains users who are clenching their fists right now). So why not try writing Markdown directly in VS Code? I decided to give this a try after I read that quite a few people are doing this. I was surprised by how powerful a Markdown editor it can become with the right setup. VS Code has some &lt;a href=&quot;https://code.visualstudio.com/docs/languages/markdown&quot;&gt;built-in support for Markdown&lt;/a&gt; and offers features like dynamic previews and document outline. But as always, there is an extension for everything in VS Code. The &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one&quot;&gt;Markdown All in One extension&lt;/a&gt; is a must-have. It adds a ton of features like keyboard shortcuts (bold, italics, lists, etc.) and convenient commands such as inserting tables, links, images, etc. &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=TakumiI.markdowntable&quot;&gt;Markdown Table&lt;/a&gt; is another key extension that makes it very easy to edit and navigate tables (&lt;code&gt;Tab&lt;/code&gt; to move around and commands for inserting and moving rows/columns, etc.). &lt;a href=&quot;https://frontmatter.codes/&quot;&gt;Front Matter CMS&lt;/a&gt; extension provides a powerful CMS without ever having to leave VS Code (I mainly use it to manage my required and optional frontmatter fields, such as tags, preview images, publish date, etc.). &lt;a href=&quot;https://marketplace.visualstudio.com/items/?itemName=streetsidesoftware.code-spell-checker&quot;&gt;Code Spell Checker&lt;/a&gt; provides basic spell checking, and this is useful generally when coding, not just with Markdown content. I am still experimenting to find the best tool for high-quality spell and grammar checking. When I need to edit some content on my iPhone, things get a &lt;em&gt;little&lt;/em&gt; less seamless. I use the extremely well-designed app &lt;a href=&quot;https://workingcopy.app/&quot;&gt;Working Copy&lt;/a&gt; as a Git client. It has a decent built-in file editor too. If at some point in the future I need to do heavy editing on the phone and need a more user-friendly interface, Obsidian for iPhone &lt;a href=&quot;https://meganesulli.com/blog/sync-obsidian-vault-iphone-ipad/&quot;&gt;can also be used&lt;/a&gt; in conjunction with Working Copy. So here is a brief summary of how Obsidian and VS Code compare as CMS for my purposes:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Obsidian&lt;/th&gt;
&lt;th&gt;VS Code&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontmatter Management&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;🟦 VS Code (slight edge)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spell &amp;#x26; Grammar Checking (AI)&lt;/td&gt;
&lt;td&gt;✅ Good&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;🟦 VS Code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MDX Support&lt;/td&gt;
&lt;td&gt;❌ Limited&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;🟦 VS Code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rich Markdown Editing &amp;#x26; Tables&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;✅ Good&lt;/td&gt;
&lt;td&gt;🟪 Obsidian&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow &amp;#x26; Syncing Ease (WSL)&lt;/td&gt;
&lt;td&gt;❌ Friction&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;🟦 VS Code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Concluding thoughts&lt;/h2&gt;
&lt;p&gt;All in all, it took me just a month or two to go from &quot;HTML and JavaScript are nothing more than words to me&quot; all the way to getting this website up and running, as I wanted it to be. I learned so much in the process and will continue to do so. It would not have been possible at all, if not for the wonderful and generous people who have developed these amazing tools (free or paid) to make others&apos; lives easier. Shout-out to the creators/developers of all the tools and tutorials that I mentioned (Astro, Pure theme, VS Code extensions, JavaScript.info, and so much more) or neglected to mention. This culture of standing with each other as we solve intellectual challenges and make collective progress is one of the main reasons I decided to pursue a career in software engineering.&lt;/p&gt;
&lt;p&gt;{/&lt;em&gt;&lt;/em&gt;/}&lt;/p&gt;</content:encoded><h:img src="/_astro/markus-spiske-MI9-PY5cyNs-unsplash.hCtqrkS5.jpg"/><enclosure url="/_astro/markus-spiske-MI9-PY5cyNs-unsplash.hCtqrkS5.jpg"/></item><item><title>Writing a bash-like shell in C</title><link>https://alimnaqvi.com/blog/minishell</link><guid isPermaLink="true">https://alimnaqvi.com/blog/minishell</guid><description>I learned and internalized a number of important software concepts by working on the “minishell” project at 42 school.</description><pubDate>Sun, 16 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Many people working in or interested in tech rely on a shell, the most popular one being bash, followed by zsh, to interact with their computer. What better way to deeply understand the shell than coding one yourself?&lt;/p&gt;
&lt;p&gt;The “minishell” project is often cited as one of the trickiest projects in the entire 42 curriculum. Here’s a list of the main things I learned while working on it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Writing a garbage collector (and understanding its pros and cons)&lt;/li&gt;
&lt;li&gt;How the shell takes input from the command line, parses it, and executes it&lt;/li&gt;
&lt;li&gt;How command pipelines and file redirections work&lt;/li&gt;
&lt;li&gt;Process creation, management, and communication&lt;/li&gt;
&lt;li&gt;The logic behind shell exit statuses&lt;/li&gt;
&lt;li&gt;How the shell handles signals, e.g., &lt;code&gt;SIGINT&lt;/code&gt; sent with &lt;code&gt;Ctrl-C&lt;/code&gt; and &lt;code&gt;SIGQUIT&lt;/code&gt; sent with &lt;code&gt;Ctrl-\&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Effective collaboration using Notion and GitHub&lt;/li&gt;
&lt;li&gt;Good documentation of code&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;This was a group project, and I was honored to partner with &lt;a href=&quot;https://github.com/MM1132&quot;&gt;Robert&lt;/a&gt;. We used Notion as our primary collaboration tool. While GitHub does offer project management features, Notion seemed better suited in this particular case. Notion was where all the relevant brainstorming notes went. We created a detailed project overview, listing step by step the overall logic of the program, what the different parts of the program would be, and what each part would achieve. Here is what the &lt;em&gt;Project Outline&lt;/em&gt; page on Notion looked like (each section is expandable in the original Notion page):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alimnaqvi.com/_image?href=%2F_astro%2Fproject-outline-notion.D5aVhx7C.png&amp;#x26;w=806&amp;#x26;h=334&amp;#x26;f=webp&quot; alt=&quot;Notion screenshot showing project outline of minishell&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using this, we came up with a task list. This is what it looked like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://alimnaqvi.com/_image?href=%2F_astro%2Ftask-list-notion.CNECkPOh.png&amp;#x26;w=657&amp;#x26;h=345&amp;#x26;f=webp&quot; alt=&quot;Notion screenshot showing a list of tasks for minishell&quot;&gt;&lt;/p&gt;
&lt;p&gt;In addition, we had a &lt;em&gt;Notes&lt;/em&gt; section on Notion where we put any other ideas and thoughts of interest.&lt;/p&gt;
&lt;p&gt;GitHub is where our code lived, so we did make use of GitHub’s collaboration features. E.g., we set up rules in the repository’s settings so that changes couldn’t be directly pushed to the &lt;code&gt;main&lt;/code&gt; branch; all work had to be done on separate branches, and then a pull request created to merge with &lt;code&gt;main&lt;/code&gt;. Since we decided to look over each other’s work before it was merged, we also enabled the rule to require approval before merging, ensuring the person who created the pull request couldn&apos;t merge it directly. And, of course, it&apos;s important to require branches to be up-to-date before merging.&lt;/p&gt;
&lt;p&gt;Below I will explain the key features of our work. The code can be seen in full &lt;a href=&quot;https://github.com/alimnaqvi/minishell&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The program&lt;/h2&gt;
&lt;h3&gt;Overall logic&lt;/h3&gt;
&lt;p&gt;We implemented the core parts of bash, without recreating its entire functionality. For example, we implemented the handling of file redirections (&lt;code&gt;&amp;#x3C;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;&amp;#x3C;&lt;/code&gt;, &lt;code&gt;&gt;&lt;/code&gt;, &lt;code&gt;&gt;&gt;&lt;/code&gt;), environment variables (accessed with &lt;code&gt;$&lt;/code&gt; and the &lt;code&gt;env&lt;/code&gt; command), and any number of pipes (&lt;code&gt;|&lt;/code&gt;). But we did not implement background processes (&lt;code&gt;&amp;#x26;&lt;/code&gt;), wildcards (&lt;code&gt;*&lt;/code&gt;), or other command chaining and grouping functionality (e.g., &lt;code&gt;;&lt;/code&gt;, &lt;code&gt;&amp;#x26;&amp;#x26;&lt;/code&gt;, &lt;code&gt;||&lt;/code&gt;, &lt;code&gt;()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Here is how our shell program works at a high level:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Input reader&lt;/strong&gt;: The user is given a prompt to enter commands; the command history is saved and can be accessed with arrow keys.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tokenizer&lt;/strong&gt;: The string (line of text) inputted by the user is split by spaces, except for parts within quotes (single or double). &lt;code&gt;$&lt;/code&gt; followed by an environment variable name or &lt;code&gt;?&lt;/code&gt; (last exit status) is expanded within double quotes or unquoted strings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parser&lt;/strong&gt;: The parser’s job is to understand what each token means: Is it a command name, command argument, filename, special character, etc.?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution&lt;/strong&gt;: Child processes are created to execute each command. File redirections and command pipelines are handled. The exit status of the final command in a pipeline is set accordingly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signals&lt;/strong&gt;: The user can send &lt;code&gt;SIGINT&lt;/code&gt; (&lt;code&gt;Ctrl-C&lt;/code&gt;) or &lt;code&gt;SIGQUIT&lt;/code&gt; (&lt;code&gt;Ctrl-\&lt;/code&gt;) at any time, and the shell reacts appropriately depending on its current state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this was done without using any global variables, except one for the very specific task of indicating the current signal (&lt;code&gt;SIGINT&lt;/code&gt; or &lt;code&gt;SIGQUIT&lt;/code&gt;), if received.&lt;/p&gt;
&lt;h3&gt;External functions used&lt;/h3&gt;
&lt;p&gt;A limited number of external/library functions were permitted and used. Most of the functionality was coded using our own functions. Here is a table of library functions used, along with the associated library.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Library&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Functions from this library used in our program&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GNU Readline Library - &lt;code&gt;readline.h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;readline, rl_clear_history, rl_on_new_line, rl_replace_line, rl_redisplay, add_history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard Library - &lt;code&gt;stdlib.h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;malloc, free, exit, getenv&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UNIX Standard - &lt;code&gt;unistd.h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;write, access, close, read, fork, execve, pipe, dup2, unlink, chdir, getcwd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard Input/Output - &lt;code&gt;stdio.h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;printf, perror&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signal Handling - &lt;code&gt;signal.h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sigaction, sigemptyset, sigaddset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process Control - &lt;code&gt;sys/wait.h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;waitpid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Directory Operations - &lt;code&gt;dirent.h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;opendir, readdir, closedir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File Control - &lt;code&gt;fcntl.h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Writing a garbage collector&lt;/h3&gt;
&lt;p&gt;C is notorious for its cumbersome memory management. I decided to create a garbage collector, which helped reduce the headache of manual memory tracking and allowed us to focus more on the shell&apos;s logic. At the same time, it was a great learning opportunity to think through the process of automating memory management, often taken for granted in higher-level languages. This garbage collector was designed to have minimal performance overhead in our context, and I believe its advantages far outweighed its costs.&lt;/p&gt;
&lt;p&gt;The advantages of the garbage collector in our case were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eliminating the risk of double &lt;code&gt;free&lt;/code&gt; (a common and dangerous error).&lt;/li&gt;
&lt;li&gt;Eliminating the risk of memory leaks.&lt;/li&gt;
&lt;li&gt;Removing the need to manually check for &lt;code&gt;malloc&lt;/code&gt; failure after every call.&lt;/li&gt;
&lt;li&gt;Eliminating the risk of double &lt;code&gt;close&lt;/code&gt; for opened file descriptors.&lt;/li&gt;
&lt;li&gt;Eliminating the risk of forgetting to close file descriptors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This helped ensure that our program cleaned up after itself properly, rather than relying solely on the operating system reclaiming resources upon exit (which can mask leaks during development, even if &lt;code&gt;valgrind&lt;/code&gt; catches them later).&lt;/p&gt;
&lt;p&gt;The main idea is this: Two linked lists are maintained—one containing all pointers allocated with &lt;code&gt;malloc&lt;/code&gt; (that must eventually be freed) and the other containing all file descriptors opened (that must eventually be closed).&lt;/p&gt;
&lt;p&gt;The key functions I wrote for this purpose are &lt;code&gt;gc_malloc&lt;/code&gt;, &lt;code&gt;gc_free&lt;/code&gt;, &lt;code&gt;gc_open&lt;/code&gt;, &lt;code&gt;gc_close&lt;/code&gt;, and &lt;code&gt;gc_exit&lt;/code&gt;. For cases where an external function returns a dynamically allocated (&lt;code&gt;malloc&lt;/code&gt;’d) pointer (e.g., &lt;code&gt;readline()&lt;/code&gt;), there are also &lt;code&gt;gc_add_to_allocs&lt;/code&gt; and similarly &lt;code&gt;gc_add_to_open_fds&lt;/code&gt; (for FDs obtained outside &lt;code&gt;gc_open&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;So, throughout the program, instead of directly using &lt;code&gt;malloc&lt;/code&gt;, we used &lt;code&gt;gc_malloc&lt;/code&gt;, which performs these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Call &lt;code&gt;malloc&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check for &lt;code&gt;malloc&lt;/code&gt; failure&lt;/strong&gt;: If &lt;code&gt;malloc&lt;/code&gt; returns &lt;code&gt;NULL&lt;/code&gt;, free all previously tracked memory, close all tracked file descriptors using the garbage collector&apos;s cleanup mechanism, and exit the program gracefully (as memory allocation failure often indicates a critical issue).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add the allocated pointer to a linked list&lt;/strong&gt;: This list tracks dynamically allocated memory that hasn&apos;t yet been freed via the garbage collector.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Anytime we were done with some allocated memory and wanted to free it, instead of using &lt;code&gt;free&lt;/code&gt;, we used &lt;code&gt;gc_free&lt;/code&gt;. This function looks for the given pointer in the linked list, frees the pointer using &lt;code&gt;free&lt;/code&gt;, and then removes the corresponding node from the list. This prevents double &lt;code&gt;free&lt;/code&gt; errors, even if &lt;code&gt;gc_free&lt;/code&gt; is accidentally called again on the same pointer.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gc_open&lt;/code&gt; and &lt;code&gt;gc_close&lt;/code&gt; follow a very similar logic to &lt;code&gt;gc_malloc&lt;/code&gt; and &lt;code&gt;gc_free&lt;/code&gt; but deal with file descriptors (integers) instead of pointers. Interestingly, the same linked list structure could be used for both, storing file descriptors cast to &lt;code&gt;void*&lt;/code&gt; and casting them back to &lt;code&gt;int&lt;/code&gt; before calling &lt;code&gt;close&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;gc_exit&lt;/code&gt; function is called whenever we intend to exit the program. This function ensures that all pointers and file descriptors tracked by our garbage collector lists are cleaned up (freed and closed, respectively) before the program terminates using the standard &lt;code&gt;exit&lt;/code&gt; function with the appropriate exit status.&lt;/p&gt;
&lt;h3&gt;Reading user input&lt;/h3&gt;
&lt;p&gt;User input is read and history maintained using the GNU Readline library, which is also used by bash itself. We use the library’s &lt;code&gt;readline&lt;/code&gt; function to display a custom prompt (&quot;&lt;code&gt;minishell$&lt;/code&gt;&quot;), after which the user can enter their command line. Standard Emacs-like keybindings are available (e.g., &lt;code&gt;Ctrl-A&lt;/code&gt;, &lt;code&gt;Ctrl-E&lt;/code&gt;, &lt;code&gt;Ctrl-W&lt;/code&gt;, &lt;code&gt;Ctrl-U&lt;/code&gt;, &lt;code&gt;Ctrl-L&lt;/code&gt;). The readline library’s &lt;code&gt;add_history&lt;/code&gt; function is called each time a valid line from the user is received, allowing command history access via the up and down arrow keys.&lt;/p&gt;
&lt;p&gt;Since the line returned by &lt;code&gt;readline&lt;/code&gt; is dynamically allocated, we add it to our garbage collector&apos;s list of allocated pointers using &lt;code&gt;gc_add_to_allocs&lt;/code&gt; immediately after receiving it.&lt;/p&gt;
&lt;h3&gt;Tokenizing user input&lt;/h3&gt;
&lt;p&gt;The tokenizer takes the line from the &lt;code&gt;readline&lt;/code&gt; function and splits it into tokens (saved as a NULL-terminated array of strings, &lt;code&gt;char **&lt;/code&gt;). Generally, a token is delimited by whitespace. However, sections enclosed in single (&lt;code&gt;&apos;&lt;/code&gt;) or double (&lt;code&gt;&quot;&lt;/code&gt;) quotes are treated as single tokens. Within double quotes, &lt;code&gt;$&lt;/code&gt; followed by an environment variable name or &lt;code&gt;?&lt;/code&gt; is expanded; within single quotes, no expansion occurs. For example, the input &lt;code&gt;&quot;&amp;#x3C; file1 cat | grep -i &quot;hello, $USER&quot; &gt; file2&quot;&lt;/code&gt; might become &lt;code&gt;{&quot;&amp;#x3C;&quot;, &quot;file1&quot;, &quot;cat&quot;, &quot;|&quot;, &quot;grep&quot;, &quot;-i&quot;, &quot;hello, username&quot;, &quot;&gt;&quot;, &quot;file2&quot;, NULL}&lt;/code&gt; (assuming &lt;code&gt;$USER&lt;/code&gt; is &quot;username&quot;).&lt;/p&gt;
&lt;p&gt;Note that with nested quotes (only possible with alternating single and double quotes), only the outer quotes determining the token boundary are removed. For example, &lt;code&gt;awk &apos;{count++} END {print count}&apos;&lt;/code&gt; becomes &lt;code&gt;{&quot;awk&quot;, &quot;{count++} END {print count}&quot;, NULL}&lt;/code&gt;. However, &lt;code&gt;awk &quot;&apos;{count++} END {print count}&apos;&quot;&lt;/code&gt; becomes &lt;code&gt;{&quot;awk&quot;, &quot;&apos;{count++} END {print count}&apos;&quot;, NULL}&lt;/code&gt; (inner single quotes preserved).&lt;/p&gt;
&lt;p&gt;Following bash, redirection operators (&lt;code&gt;&amp;#x3C;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;&amp;#x3C;&lt;/code&gt;, &lt;code&gt;&gt;&lt;/code&gt;, &lt;code&gt;&gt;&gt;&lt;/code&gt;) act as delimiters even without surrounding spaces. All these are equivalent: &lt;code&gt;echo hello &gt; outfile&lt;/code&gt;, &lt;code&gt;echo hello&gt;outfile&lt;/code&gt;, &lt;code&gt;echo hello &gt;outfile&lt;/code&gt;, &lt;code&gt;echo hello&gt; outfile&lt;/code&gt;, and are tokenized as &lt;code&gt;{&quot;echo&quot;, &quot;hello&quot;, &quot;&gt;&quot;, &quot;outfile&quot;, NULL}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Also following bash, if quoted sections directly abut unquoted text or other quoted sections without spaces, they are concatenated into a single token after quote removal. E.g., &lt;code&gt;grep &quot;hello&quot;world&lt;/code&gt; becomes &lt;code&gt;{&quot;grep&quot;, &quot;helloworld&quot;, NULL}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If the last token is a pipe (&lt;code&gt;|&lt;/code&gt;), our shell throws a syntax error, unlike bash which prompts for more input. Other syntax errors (like missing filenames after redirection) are typically handled by the parser.&lt;/p&gt;
&lt;h3&gt;Parsing the tokens and preparing for execution&lt;/h3&gt;
&lt;p&gt;The parser takes the &lt;code&gt;char **&lt;/code&gt; token array produced by the tokenizer and builds a structure representing the command(s) to be executed. We used a linked list of &quot;command groups&quot;, where each group corresponds to a single command between pipes (or the only command if no pipes exist). Each command group contains all information needed for execution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The command name and its arguments (&lt;code&gt;char **cmd_args&lt;/code&gt;, where &lt;code&gt;cmd_args[0]&lt;/code&gt; is the command name).&lt;/li&gt;
&lt;li&gt;Whether the command is a built-in or an external program (&lt;code&gt;t_cmd_type&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The file descriptor for input (&lt;code&gt;int in_fd&lt;/code&gt;), defaulting to &lt;code&gt;stdin&lt;/code&gt; but potentially redirected to a file or the read-end of a pipe.&lt;/li&gt;
&lt;li&gt;The file descriptor for output (&lt;code&gt;int out_fd&lt;/code&gt;), defaulting to &lt;code&gt;stdout&lt;/code&gt; but potentially redirected to a file or the write-end of a pipe.&lt;/li&gt;
&lt;li&gt;Pointers to the previous and next command groups in the pipeline (if any).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our &lt;code&gt;t_cmd_grp&lt;/code&gt; struct looked something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct s_cmd_grp
{
  char              *cmd_name;  // Command name (e.g., &quot;ls&quot;, &quot;grep&quot;)
  char              **cmd_args; // NULL-terminated array of args (args[0] is cmd_name)
  t_cmd_type        cmd_type;   // enum (BUILTIN or EXTERNAL)
  int               in_fd;      // Input file descriptor (0=stdin, pipe-read, file)
  int               out_fd;     // Output file descriptor (1=stdout, pipe-write, file)
  struct s_cmd_grp  *previous;  // Previous command group in pipeline (or NULL)
  struct s_cmd_grp  *next;      // Next command group in pipeline (or NULL)
} t_cmd_grp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s a high-level view of how the parser processes the tokens:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It identifies the command name (usually the first token unless it&apos;s a redirection).&lt;/li&gt;
&lt;li&gt;It collects subsequent tokens as arguments until a metacharacter (&lt;code&gt;|&lt;/code&gt;, &lt;code&gt;&amp;#x3C;&lt;/code&gt;, &lt;code&gt;&gt;&lt;/code&gt;, &lt;code&gt;&gt;&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;&amp;#x3C;&lt;/code&gt;) or the end of the token list is reached.&lt;/li&gt;
&lt;li&gt;When a redirection operator (&lt;code&gt;&amp;#x3C;&lt;/code&gt;, &lt;code&gt;&gt;&lt;/code&gt;, &lt;code&gt;&gt;&gt;&lt;/code&gt;) is encountered, the next token is treated as a filename. The file is opened (using &lt;code&gt;gc_open&lt;/code&gt;), and the &lt;code&gt;in_fd&lt;/code&gt; or &lt;code&gt;out_fd&lt;/code&gt; of the current command group is updated. Error handling occurs if the file cannot be opened or if the filename token is missing.&lt;/li&gt;
&lt;li&gt;When a heredoc operator (&lt;code&gt;&amp;#x3C;&amp;#x3C;&lt;/code&gt;) is encountered followed by a delimiter token, the shell prompts the user for input line by line until the delimiter is entered on a line by itself. This input is typically stored in a temporary file (e.g., &lt;code&gt;/tmp/minishell_heredoc&lt;/code&gt;). This temp file is opened (using &lt;code&gt;gc_open&lt;/code&gt;), its file descriptor is set as the &lt;code&gt;in_fd&lt;/code&gt; for the command group, and crucially, &lt;code&gt;unlink&lt;/code&gt; is called on the temp file immediately after opening. This makes the filename disappear from the directory listing, but the open file descriptor remains valid until closed, ensuring automatic cleanup even if the shell crashes.&lt;/li&gt;
&lt;li&gt;If a pipe (&lt;code&gt;|&lt;/code&gt;) is encountered, a pipe is created (using &lt;code&gt;pipe()&lt;/code&gt;), the &lt;code&gt;out_fd&lt;/code&gt; of the current command group is set to the write-end of the pipe, and parsing continues for the next command group, whose &lt;code&gt;in_fd&lt;/code&gt; will be set to the read-end of the pipe.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Throughout this process, syntax errors are detected (e.g., &lt;code&gt;&gt; &gt; file&lt;/code&gt;, &lt;code&gt;| |&lt;/code&gt;, redirection without filename) and reported to the user, preventing execution.&lt;/p&gt;
&lt;h3&gt;Writing the built-in commands&lt;/h3&gt;
&lt;p&gt;Our minishell included the following built-in commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo&lt;/code&gt; with the &lt;code&gt;-n&lt;/code&gt; option&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd&lt;/code&gt; with only a relative or absolute path&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pwd&lt;/code&gt; with no options&lt;/li&gt;
&lt;li&gt;&lt;code&gt;export&lt;/code&gt; with arguments (but without support for marking shell variables for export, only environment variables)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unset&lt;/code&gt; with arguments&lt;/li&gt;
&lt;li&gt;&lt;code&gt;env&lt;/code&gt; with no options or arguments&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exit&lt;/code&gt; with an optional numeric status&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Being &quot;built-in&quot; means that the shell&apos;s own code executes these commands directly, instead of searching for and executing an external program from the &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Each built-in corresponds to a function in our program (e.g., &lt;code&gt;ft_echo&lt;/code&gt;, &lt;code&gt;ft_cd&lt;/code&gt;, &lt;code&gt;ft_pwd&lt;/code&gt;, etc., where &lt;code&gt;ft&lt;/code&gt; refers to 42).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ft_echo&lt;/code&gt;: Prints its arguments separated by spaces. If the first argument is &lt;code&gt;-n&lt;/code&gt;, no trailing newline is printed; otherwise, a newline is added.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ft_cd&lt;/code&gt;: Uses the &lt;code&gt;chdir&lt;/code&gt; function to change the current working directory. It updates the &lt;code&gt;PWD&lt;/code&gt; and &lt;code&gt;OLDPWD&lt;/code&gt; environment variables accordingly. Throws a &quot;too many arguments&quot; error if more than one path is given. If no arguments are supplied, it attempts to change to the directory specified by the &lt;code&gt;HOME&lt;/code&gt; environment variable.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ft_pwd&lt;/code&gt;: Uses the &lt;code&gt;getcwd&lt;/code&gt; function to get and print the current working directory path. (Alternatively, it could print the &lt;code&gt;PWD&lt;/code&gt; environment variable, but &lt;code&gt;getcwd&lt;/code&gt; is generally more reliable).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ft_export&lt;/code&gt;: Without arguments, it prints the list of environment variables in a specific format (similar to &lt;code&gt;declare -x&lt;/code&gt; in bash, but possibly simpler in our case, perhaps matching &lt;code&gt;env&lt;/code&gt;). With arguments, it attempts to add or update environment variables. Arguments should be in the format &lt;code&gt;NAME=value&lt;/code&gt;. It performs validity checks on &lt;code&gt;NAME&lt;/code&gt; (must start with a letter or underscore, followed by letters, numbers, or underscores). An error is reported for invalid names or formats. If &lt;code&gt;NAME&lt;/code&gt; is valid but &lt;code&gt;=value&lt;/code&gt; is missing, we reported an error, as our minishell didn&apos;t support shell-local variables that could be marked for export later.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ft_unset&lt;/code&gt;: Takes variable names as arguments and removes the corresponding environment variables. Performs validity checks on the names.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ft_env&lt;/code&gt;: Prints the list of current environment variables (name=value pairs, one per line).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ft_exit&lt;/code&gt;: Causes the minishell to terminate. If a numeric argument is provided (e.g., &lt;code&gt;exit 1&lt;/code&gt;), the shell exits with that status code (modulo 256). If no argument is given, the shell exits with the status code of the last executed command. Handles non-numeric arguments appropriately (prints an error and exits with a specific status like 2 or 1).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Executing the commands&lt;/h3&gt;
&lt;p&gt;Once the parser has built the linked list of command groups, the execution phase begins. For each command group, a child process is typically created using &lt;code&gt;fork()&lt;/code&gt; (with exceptions for certain built-ins, see below). Within each child process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Redirection:&lt;/strong&gt; If &lt;code&gt;cmd_grp.in_fd&lt;/code&gt; is not &lt;code&gt;STDIN_FILENO&lt;/code&gt; (0) or &lt;code&gt;cmd_grp.out_fd&lt;/code&gt; is not &lt;code&gt;STDOUT_FILENO&lt;/code&gt; (1), the &lt;code&gt;dup2&lt;/code&gt; function is used to duplicate the appropriate file descriptor (&lt;code&gt;in_fd&lt;/code&gt; or &lt;code&gt;out_fd&lt;/code&gt;) onto standard input (0) or standard output (1), respectively. Any original file descriptors used for redirection (from files or pipes) that are no longer needed in the child are closed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;If the command is a &lt;strong&gt;built-in&lt;/strong&gt;, the corresponding &lt;code&gt;ft_&lt;/code&gt; function (like &lt;code&gt;ft_echo&lt;/code&gt;, &lt;code&gt;ft_pwd&lt;/code&gt;, &lt;code&gt;ft_env&lt;/code&gt;) is called directly within the child process. After the built-in completes, the child process exits with an appropriate status (usually 0 for success, non-zero for failure).&lt;/li&gt;
&lt;li&gt;If the command is &lt;strong&gt;external&lt;/strong&gt;, the &lt;code&gt;execve&lt;/code&gt; function is called. This function attempts to replace the current process image (the child process) with the specified external program (found via the &lt;code&gt;PATH&lt;/code&gt; environment variable). Arguments (&lt;code&gt;cmd_grp.cmd_args&lt;/code&gt;) and the current environment variables are passed to &lt;code&gt;execve&lt;/code&gt;. If &lt;code&gt;execve&lt;/code&gt; fails (e.g., command not found), an error message is printed, and the child exits with a specific failure status (e.g., 127).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Important Caveat for Built-ins:&lt;/strong&gt; Four built-ins (&lt;code&gt;cd&lt;/code&gt;, &lt;code&gt;export&lt;/code&gt;, &lt;code&gt;unset&lt;/code&gt;, &lt;code&gt;exit&lt;/code&gt;) modify the state of the shell process itself (current directory, environment variables, or termination). Running these in a child process would have no effect on the parent shell. Therefore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the user enters a command line containing &lt;strong&gt;only one&lt;/strong&gt; of these four built-ins (no pipes), the corresponding &lt;code&gt;ft_&lt;/code&gt; function is executed directly in the &lt;strong&gt;parent&lt;/strong&gt; shell process. No child process is created for this command.&lt;/li&gt;
&lt;li&gt;If these built-ins appear within a pipeline (e.g., &lt;code&gt;export VAR=1 | grep VAR&lt;/code&gt;), they &lt;em&gt;are&lt;/em&gt; executed in child processes, meaning their effect will be lost once the child exits. This matches the behavior of standard shells like bash. (The &lt;code&gt;exit&lt;/code&gt; command in a pipeline would just terminate that child process).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After potentially forking child processes, the &lt;strong&gt;parent&lt;/strong&gt; shell process must:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Close any pipe file descriptors it doesn&apos;t need (e.g., the write-end of a pipe after forking the left-side child, the read-end after forking the right-side child). This is crucial for &lt;code&gt;EOF&lt;/code&gt; to propagate correctly through pipelines.&lt;/li&gt;
&lt;li&gt;Wait for all child processes in the pipeline to terminate using &lt;code&gt;waitpid&lt;/code&gt;. It waits for the &lt;em&gt;last&lt;/em&gt; command in the pipeline specifically to retrieve its exit status.&lt;/li&gt;
&lt;li&gt;Save the exit status of the last command to make it available via &lt;code&gt;$?&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Handling Signals&lt;/h3&gt;
&lt;p&gt;Our shell needed to handle user-generated signals gracefully, primarily &lt;code&gt;SIGINT&lt;/code&gt; (&lt;code&gt;Ctrl-C&lt;/code&gt;) and &lt;code&gt;SIGQUIT&lt;/code&gt; (&lt;code&gt;Ctrl-\&lt;/code&gt;). We also naturally handled &lt;code&gt;EOF&lt;/code&gt; (&lt;code&gt;Ctrl-D&lt;/code&gt;), although it&apos;s technically an end-of-file condition on input, not a signal.&lt;/p&gt;
&lt;p&gt;The shell&apos;s response to &lt;code&gt;SIGINT&lt;/code&gt; and &lt;code&gt;SIGQUIT&lt;/code&gt; depends on its current state. We used the &lt;code&gt;sigaction&lt;/code&gt; function to set up signal handlers – functions that are called when a specific signal is received. Our &lt;code&gt;set_signal_handler&lt;/code&gt; function was called at different points to switch between signal handling modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Interactive Mode:&lt;/strong&gt; When the shell is displaying the prompt (&lt;code&gt;minishell$&lt;/code&gt;) and waiting for user input:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SIGINT&lt;/code&gt; (&lt;code&gt;Ctrl-C&lt;/code&gt;): Abort the current input line, print a newline, and display a fresh prompt. Do not exit the shell.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SIGQUIT&lt;/code&gt; (&lt;code&gt;Ctrl-\&lt;/code&gt;): Ignored. Does nothing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Heredoc Mode:&lt;/strong&gt; When the shell is reading input for a heredoc (showing a &lt;code&gt;&gt;&lt;/code&gt; prompt):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SIGINT&lt;/code&gt; (&lt;code&gt;Ctrl-C&lt;/code&gt;): Abort the heredoc input, cancel the entire command line that contained the heredoc, print a newline, and display a fresh &lt;code&gt;minishell$&lt;/code&gt; prompt.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SIGQUIT&lt;/code&gt; (&lt;code&gt;Ctrl-\&lt;/code&gt;): Ignored. Does nothing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Non-interactive Mode (Child Process Running):&lt;/strong&gt; When the shell has launched one or more child processes to execute a command pipeline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SIGINT&lt;/code&gt; (&lt;code&gt;Ctrl-C&lt;/code&gt;): The parent shell ignores &lt;code&gt;SIGINT&lt;/code&gt; itself (so &lt;code&gt;Ctrl-C&lt;/code&gt; doesn&apos;t kill the shell). The signal is passed to the foreground child process group. The default action for &lt;code&gt;SIGINT&lt;/code&gt; usually terminates the process. The parent shell then waits for the children and eventually displays a new prompt. A newline might be printed by the signal handler in the parent for cleaner output.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SIGQUIT&lt;/code&gt; (&lt;code&gt;Ctrl-\&lt;/code&gt;): Similar to &lt;code&gt;SIGINT&lt;/code&gt;, the parent ignores it, and the signal is passed to the children. The default action for &lt;code&gt;SIGQUIT&lt;/code&gt; usually terminates the process &lt;em&gt;and&lt;/em&gt; potentially dumps core. The shell prints &quot;Quit (core dumped)&quot; (or similar) after the child terminates and displays a new prompt.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Setting signal handlers appropriately (e.g., using &lt;code&gt;SIG_DFL&lt;/code&gt; for default behavior in children, or custom handler functions) at the right times (before &lt;code&gt;readline&lt;/code&gt;, before &lt;code&gt;fork&lt;/code&gt;, after &lt;code&gt;fork&lt;/code&gt; in parent/child) is key to achieving this behavior.&lt;/p&gt;
&lt;h2&gt;Closing remarks&lt;/h2&gt;
&lt;p&gt;Before starting the project, it naturally felt intimidating. I had previously never programmed something of this complexity. After finishing this project, it now feels like a much more manageable challenge, albeit a complex one. The process demystified many aspects of how shells operate. I am now ready for bigger challenges, which might also initially seem intimidating or even insurmountable. However, I know that problems are inevitable, but they are also soluble.&lt;/p&gt;</content:encoded><h:img src="/_astro/bash-logo-no-text.C4CbrqdQ.jpg"/><enclosure url="/_astro/bash-logo-no-text.C4CbrqdQ.jpg"/></item></channel></rss>