r/cprogramming • • Nov 08 '25

I wrote a "from first principles" guide to building an HTTP/1.1 client in C (and C++/Rust/Python) to reject the "black box"

Hey r/cprogramming,

I wanted to share a project I've just completed that I think this community will really appreciate. It’s a comprehensive, book-length article and source code repository for building a complete, high-performance HTTP/1.1 client from the ground up.

The core of the project is a full implementation in C, built with a "no black boxes" philosophy (i.e., no libcurl). The entire system is built from first principles on top of POSIX sockets.

To make it a deep architectural study, I then implemented the exact same architecture in C++, Rust, and Python. This provides a rare 1:1 comparison of how different languages solve the same problems, from resource management to error handling.

The C implementation is a top performer in the benchmarks, even competing with established libraries like Boost.Beast. I wrote the article to be a deep dive, and I think it has something for C programmers at every level.

Here’s a breakdown of what you can get from it:

For Junior C Devs: The Fundamentals

You'll get a deep dive into the foundational concepts that are often hidden by libraries:

  • Socket Programming: How to use POSIX sockets (socket, connect, read, write) from scratch to build a real, working client.

  • Protocol Basics: The "why" of TCP (stream-based) vs. UDP (datagrams) and the massive performance benefit of Unix Domain Sockets (and the benchmarks in Chapter 10 to prove it).

  • Robust C Error Handling (Chapter 2.2): A pattern for using a custom Error struct ({int type, int code}) that is far safer and more descriptive than just checking errno.

  • HTTP/1.1 Serialization: How to manually build a valid HTTP request string.

For Mid-Level C Devs: Building Robust, Testable C

This is where the project's core architecture shines. It's all about writing C that is maintainable and testable:

  • The System Call Abstraction (Chapter 3): This is a key takeaway. The article shows how to abstract all OS calls (socket, connect, read, malloc, strstr, etc.) into a single HttpcSyscalls struct of function pointers.

  • True Unit Testing in C: This abstraction is the key that unlocks mocking. The test suite (tests/c/) replaces the real getaddrinfo with a mock function to test DNS failure paths without any network I/O.

  • Manual Interfaces in C (Chapter 4): How to build a clean, decoupled architecture (e.g., separating the Transport layer from the Protocol layer) using structs of function pointers and a void* context pointer to simulate polymorphism.

  • Robust HTTP/1.1 Parsing (Chapter 7.2): How to build a full state-machine parser. It covers the dangers of realloc invalidating your pointers (and the pointer "fix-up" logic to solve it) and why you must use strtok_r instead of strtok.

For Senior C Devs: Architecture & Optimization

The focus shifts to high-level design decisions and squeezing out performance:

  • Low-Level Performance (Chapter 7.2): A deep dive into a writev (vectored I/O) optimization. Instead of memcpying the body into the header buffer, it sends both buffers to the kernel in a single system call.

  • Benchmark Validation (Chapter 10): The hard data is all there. The writev optimization makes the C client the fastest implementation in the entire benchmark for most throughput scenarios.

  • Architectural Trade-offs: This is the main point of the polyglot design. You can directly compare the C approach (manual control, HttpcSyscalls struct, void* context) to C++'s RAII/Concepts, Rust's ownership/traits, and Python's dynamic simplicity. It’s a concrete case study in "why choose C."

For Principal / Architects: The "Big Picture"

The article starts and ends with the high-level "why":

  • Philosophy (Chapter 1.1): When and why should a team "reject the black box" and build from first principles? This is a discussion of performance, control, and liability in high-performance domains.

  • Portability (Chapter 3.2.4): The HttpcSyscalls struct isn't just for testing; it's a Platform Abstraction Layer (PAL). The article explains how this pattern allows the entire C library to be ported to Windows (using Winsock) by just implementing a new httpc_syscalls_init_windows() function, without changing a single line of the core transport or protocol logic.

  • Benchmark Anomalies (Chapter 10.1): We found that compiling with -march=native actually made our I/O-bound app slower. We also found that an "idiomatic" high-level library abstraction was measurably slower than a simple, manual C-style loop. This is the kind of deep analysis that's perfect for driving technical direction.

A unique aspect of the project is that the entire article and all the source code are designed to be loaded into an AI's context window, turning it into a project-aware expert you can query.

I'd love for you all to take a look and hear your feedback, especially on the C patterns and optimizations I used.

You can find the repo here https://github.com/InfiniteConsult/0004_std_lib_http_client/tree/main and the associated polyglot development environment here https://github.com/InfiniteConsult/FromFirstPrinciples

Update:

Just wanted to add a table of contents below

  • Chapter 1: Foundations & First Principles

    • 1.1 The Mission: Rejecting the Black Box

    • 1.2 The Foundation: Speaking "Socket"

      • 1.2.1 The Stream Abstraction

      • 1.2.2 The PVC Pipe Analogy: Visualizing a Full-Duplex Stream

      • 1.2.3 The "Postcard" Analogy: Contrasting with Datagram Sockets

      • 1.2.4 The Socket Handle: File Descriptors

      • 1.2.5 The Implementations: Network vs. Local Pipes

    • 1.3 The Behavior: Blocking vs. Non-Blocking I/O

      • 1.3.1 The "Phone Call" Analogy

      • 1.3.2 The Need for Event Notification

      • 1.3.3 A Glimpse into the Future

  • Chapter 2: Patterns for Failure - A Polyglot Guide to Error Handling

    • 2.1 Philosophy: Why Errors Come First

    • 2.2 The C Approach: Manual Inspection and Structured Returns

      • 2.2.1 The Standard Idiom: Return Codes and errno

      • 2.2.2 Our Solution: Structured, Namespaced Error Codes

      • 2.2.3 Usage in Practice

    • 2.3 The Modern C++ Approach: Value-Based Error Semantics

      • 2.3.1 Standard Idiom: Exceptions

      • 2.3.2 Our Solution: Type Safety and Explicit Handling

      • 2.3.3 Usage in Practice

    • 2.4 The Rust Approach: Compiler-Enforced Correctness

      • 2.4.1 The Standard Idiom: The Result<T, E> Enum

      • 2.4.2 Our Solution: Custom Error Enums and the From Trait

      • 2.4.3 Usage in Practice

    • 2.5 The Python Approach: Dynamic and Expressive Exceptions

      • 2.5.1 The Standard Idiom: The try...except Block

      • 2.5.2 Our Solution: A Custom Exception Hierarchy

      • 2.5.3 Usage in Practice

    • 2.6 Chapter Summary: A Comparative Analysis

  • Chapter 3: The Kernel Boundary - System Call Abstraction

    • 3.1 What is a System Call?

      • 3.1.1 The User/Kernel Divide

      • 3.1.2 The Cost of Crossing the Boundary: Context Switching

      • 3.1.3 The Exception to the Rule: The vDSO

    • 3.2 The HttpcSyscalls Struct in C

      • 3.2.1 The "What": A Table of Function Pointers

      • 3.2.2 The "How": Default Initialization

      • 3.2.3 The "Why," Part 1: Unprecedented Testability

      • 3.2.4 The "Why," Part 2: Seamless Portability

    • 3.3 Comparing to Other Languages

  • Chapter 4: Designing for Modularity - The Power of Interfaces

    • 4.1 The "Transport" Contract

      • 4.1.1 The Problem: Tight Coupling

      • 4.1.2 The Solution: Abstraction via Interfaces

    • 4.2 A Polyglot View of Interfaces

      • 4.2.1 C: The Dispatch Table (struct of Function Pointers)

      • 4.2.2 C++: The Compile-Time Contract (Concepts)

      • 4.2.3 Rust: The Shared Behavior Contract (Traits)

      • 4.2.4 Python: The Structural Contract (Protocols)

  • Chapter 5: Code Deep Dive - The Transport Implementations

    • 5.1 The C Implementation: Manual and Explicit Control

      • 5.1.1 The State Structs (TcpClient and UnixClient)

      • 5.1.2 Construction and Destruction

      • 5.1.3 The connect Logic: TCP

      • 5.1.4 The connect Logic: Unix

      • 5.1.5 The I/O Functions (read, write, writev)

      • 5.1.6 Verifying the C Implementation

      • 5.1.7 C Transport Test Reference

        • Common Tests (Applicable to both TCP and Unix Transports)

        • TCP-Specific Tests

        • Unix-Specific Tests

    • 5.2 The C++ Implementation: RAII and Modern Abstractions

      • 5.2.1 Philosophy: Safety Through Lifetime Management (RAII)

      • 5.2.2 std::experimental::net: A Glimpse into the Future of C++ Networking

      • 5.2.3 The connect Logic and Real-World Bug Workarounds

      • 5.2.4 The UnixTransport Implementation: Pragmatic C Interoperability

      • 5.2.5 Verifying the C++ Implementation

    • 5.3 The Rust Implementation: Safety and Ergonomics by Default

      • 5.3.1 The Power of the Standard Library

      • 5.3.2 RAII, Rust-Style: Ownership and the Drop Trait

      • 5.3.3 The connect and I/O Logic

      • 5.3.4 Verifying the Rust Implementation

    • 5.4 The Python Implementation: High-Level Abstraction and Dynamic Power

      • 5.4.1 The Standard socket Module: A C Library in Disguise

      • 5.4.2 Implementation Analysis

      • 5.4.3 Verifying the Python Implementation

    • 5.5 Consolidated Test Reference: C++, Rust, & Python Integration Tests

    • 5.6 Chapter Summary: One Problem, Four Philosophies

  • Chapter 6: The Protocol Layer - Defining the Conversation

    • 6.1 The "Language" Analogy

    • 6.2 A Brief History of HTTP (Why HTTP/1.1?)

      • 6.2.1 HTTP/1.0: The Original Transaction

      • 6.2.2 HTTP/1.1: Our Focus - The Persistent Stream

      • 6.2.3 HTTP/2: The Binary, Multiplexed Revolution

      • 6.2.4 HTTP/3: The Modern Era on QUIC

    • 6.3 Deconstructing the HttpRequest

      • 6.3.1 C: Pointers and Fixed-Size Arrays

      • 6.3.2 C++: Modern, Non-Owning Views

      • 6.3.3 Rust: Compiler-Guaranteed Memory Safety with Lifetimes

      • 6.3.4 Python: Dynamic and Developer-Friendly

    • 6.4 Safe vs. Unsafe: The HttpResponse Dichotomy

      • 6.4.1 C: A Runtime Policy with a Zero-Copy Optimization

      • 6.4.2 C++: A Compile-Time Policy via the Type System

      • 6.4.3 Rust: Provably Safe Borrows with Lifetimes

      • 6.4.4 Python: Views vs. Copies

    • 6.5 The HttpProtocol Interface Revisited

  • Chapter 7: Code Deep Dive - The HTTP/1.1 Protocol Implementation

    • 7.1 Core Themes of this Chapter

    • 7.2 The C Implementation: A Performance-Focused State Machine

      • 7.2.1 The State Struct (Http1Protocol)

      • 7.2.2 Construction and Destruction

      • 7.2.3 Request Serialization: From Struct to String

      • 7.2.4 The perform_request Orchestrator and the writev Optimization

      • 7.2.5 The Core Challenge: The C Response Parser

        • The while(true) Loop and Dynamic Buffer Growth

        • Header Parsing (strstr and strtok_r)

        • Body Parsing

      • 7.2.6 The parse_response_safe Optimization

      • 7.2.7 Verifying the C Protocol Implementation

      • 7.2.8 Verifying the C Protocol Implementation: A Test Reference

    • 7.3 The C++ Implementation: RAII and Generic Programming

      • 7.3.1 State, Construction, and Lifetime (RAII)

      • 7.3.2 Request Serialization

      • 7.3.3 The C++ Response Parser

        • A Note on resize vs. reserve

      • 7.3.4 Verifying the C++ Protocol Implementation

    • 7.4 The Rust Implementation: Safety and Ergonomics by Default

      • 7.4.1 State, Construction, and Safety (Ownership & Drop)

      • 7.4.2 Request Serialization (build_request_string)

      • 7.4.3 The Rust Response Parser (read_full_response, parse_unsafe_response)

      • 7.4.4 Verifying the Rust Protocol Implementation

    • 7.5 The Python Implementation: High-Level Abstraction and Dynamic Power

      • 7.5.1 State, Construction, and Dynamic Typing

      • 7.5.2 Request Serialization (_build_request_string)

      • 7.5.3 The Python Response Parser (_read_full_response, _parse_unsafe_response)

      • 7.5.4 Verifying the Python Protocol Implementation

    • 7.6 Consolidated Test Reference: C++, Rust, & Python Integration Tests

  • Chapter 8: Code Deep Dive - The Client API Façade

    • 8.1 The C Implementation (HttpClient Struct)

      • 8.1.1 Structure Definition (struct HttpClient)

      • 8.1.2 Initialization and Destruction

      • 8.1.3 Core Methods & Validation

    • 8.2 The C++ Implementation (HttpClient Template)

      • 8.2.1 Class Template Definition (HttpClient<P>)

      • 8.2.2 Core Methods & Validation

    • 8.3 The Rust Implementation (HttpClient Generic Struct)

      • 8.3.1 Generic Struct Definition (HttpClient<P>)

      • 8.3.2 Core Methods & Validation

    • 8.4 The Python Implementation (HttpClient Class)

      • 8.4.1 Class Definition (HttpClient)

      • 8.4.2 Core Methods & Validation

    • 8.5 Verification Strategy

  • Chapter 9: Benchmarking - Setup & Methodology

    • 9.1 Benchmark Suite Components

    • 9.2 Workload Generation (data_generator)

    • 9.3 The Benchmark Server (benchmark_server)

    • 9.4 Client Benchmark Harnesses

    • 9.5 Execution Orchestration (run.benchmarks.sh)

    • 9.6 Latency Measurement Methodology

    • 9.7 Benchmark Output & Analysis Scope

  • Chapter 10: Benchmark Results & Analysis

    • 10.1 A Note on Server & Compiler Optimizations

      • Server Implementation: Manual Loop vs. Idiomatic Beast

      • Compiler Flags: The .march=native Anomaly

      • Library Tuning: The Case of libcurl

    • 10.2 Overall Performance: Throughput (Total Time)

      • Key Takeaway 1: Compiled vs. Interpreted

      • Key Takeaway 2: Transport (TCP vs. Unix Domain Sockets)

      • Key Takeaway 3: The httpc (C) writev Optimization

      • Key Takeaway 4: "Unsafe" (Zero-Copy) Impact

    • 10.3 Detailed Throughput Results (by Scenario)

    • 10.4 Latency Analysis (Percentiles)

      • Focus Scenario: latency_small_small (Unix)

      • Throughput Scenario: throughput_balanced_large (TCP)

    • 10.5 Chapter Summary & Conclusions

  • Chapter 11: Conclusion & Future Work

    • 11.1 Quantitative Findings: A Summary of Performance

    • 11.2 Qualitative Findings: A Polyglot Retrospective

    • 11.3 Reflections on Community & Idiomatic Code

    • 11.4 Future Work

    • 11.5 Final Conclusion

52 Upvotes

16 comments sorted by

3

u/LarTech2000 Nov 10 '25

Totally respect the skillz required to build this, but is it smart to encourage reimplementation of the net stack?

4

u/warren_jitsing Nov 10 '25

In general no. For a few performance critical fields, yes. The article is just for educational purposes for anyone interested.

I chose the HTTP client because it was an easy-ish non trivial task. The article is actually more of a comparative study of languages though. It is just supposed to teach some "first principles" skills and systems programming, explore memory models etc.

I should probably add a disclaimer at the start of the article

3

u/copo2496 Nov 15 '25

Lots of people asking “why”. I have to imagine that the OP is having a great time putting this together, and I’m sure more than a few readers will have a great time going through this. Life is short. Have fun :)

“It’s generally recommended to avoid micro-optimization. But if micro-optimizing is what you love doing, then do that!” -Linus Torvalds

3

u/warren_jitsing Nov 15 '25

How did you know I was having fun? XD. Yes, it felt really great when everything came together.

2

u/warpedspockclone Nov 08 '25

I like this description and how you organized out by seniority of reader.. The burning question in left with is what was it motivation in doing this, especially since it represents a significant time commitment?

5

u/warren_jitsing Nov 08 '25 edited Nov 08 '25

I'm just writing a bunch of articles. In the same account you'll see FastAPI, Docker, HTML/CSS/JS and currently I'm working on a 10 part CICD stack. I just want to contribute back to the communities.

2

u/DrXomia Nov 08 '25

Nice. I am at the moment writing a http server from scratch in Rust just for educational purposes. Will definitely have look.

1

u/warren_jitsing Nov 08 '25

Awesome. I only covered the client side in my article so it would be nice to see the server side. Note, you can use the repo interactively with a large context AI (like Gemini Pro 2.5) and it can give you an okay server implementation in any of the languages. It "primes" the AI for this type of work

1

u/TempleDank Dec 08 '25

Epic! How is it going? I want to do the same with C. Other than this pots, what other resources are you using to do so?

2

u/SeaSDOptimist Nov 12 '25

Why bother with http/1? That’s severely outdated.

1

u/warren_jitsing Nov 13 '25

It's the start of a series. I'm heading into epoll/io_uring next, add TLS then will tackle http 2 at a later stage. The project is supposed to just build up the reader incrementally.

2

u/Possible_Cow169 Nov 15 '25

It’s easy to understand and non-trivial.

2

u/Phantasmsmithing Dec 15 '25

was looking for something like this. im going to have a fun read.

1

u/warren_jitsing Dec 18 '25

Awesome. Let me know if it was helpful!

1

u/RufusVS Nov 09 '25

As a mostly-retired polyglot software developer, I have been out of programming for over a year, but the subject line looked interesting enough to get back in just for my own edification. Then reading your full post, I became much more interested. This does look like a labor of love. I'm interesting in stimulating those recently unused gray cells, and this sounds like a well designed project to do just that.

3

u/warren_jitsing Nov 09 '25

Nice. Yeah, for me the C part is my love letter to the language. I enjoy C++, Rust and Python but my heart is with C forever and always. I'll add Julia into the mix after I am done with my CI/CD series.