A multithreaded HTTP/1.1 web server implementation built from scratch in Rust, demonstrating core networking concepts, concurrent programming, and clean architecture principles.
- Overview
- Architecture
- Features
- Technical Implementation
- Project Structure
- Getting Started
- API Endpoints
- Design Principles
- Dependencies
This project is a pedagogical implementation of an HTTP web server written in Rust. It showcases fundamental concepts in network programming, including TCP socket handling, HTTP protocol parsing, concurrent request processing, and modular software design. The server handles multiple simultaneous connections using Rust's threading model and implements a routing system based on the Handler trait pattern.
┌─────────────┐
│ Client │
└──────┬──────┘
│ TCP Connection
▼
┌─────────────────────────────────────┐
│ TcpListener │
│ (127.0.0.1:8080) │
└──────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Thread Pool │
│ (Worker threads spawn per request) │
└──────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Connection Handler │
│ - Parse HTTP Request │
│ - Extract Headers │
│ - Route to Handler │
└──────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Router │
│ (Path → Handler mapping) │
└──────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Handler Trait Implementations │
│ - HomeHandler │
│ - AboutHandler │
│ - SearchHandler │
│ - UserAgentHandler │
│ - HeadersHandler │
│ - AcceptHandler │
└─────────────────────────────────────┘
The server uses Rust's std::net::TcpListener to bind to a network address and accept incoming TCP connections. Each connection is handled independently, demonstrating the fundamentals of network socket programming.
Key Concepts:
- Socket binding and listening
- Connection acceptance loop
- Stream-based I/O operations
The server implements a subset of the HTTP/1.1 protocol, including:
- Request Line Parsing: Extracts HTTP method, path, and version
- Header Parsing: Reads and stores all HTTP headers in a key-value structure
- Query Parameter Parsing: Extracts URL query parameters (e.g.,
?q=search) - Response Generation: Constructs valid HTTP responses with status codes and headers
HTTP Request Structure Handled:
GET /path?query=value HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/7.64.1
Accept: */*
The server employs thread-based concurrency to handle multiple simultaneous client connections. Each incoming connection spawns a new thread, allowing the server to process multiple requests in parallel without blocking.
Concurrency Model:
- One thread per connection
- Shared router state using
Arc(Atomic Reference Counting) - Thread-safe handler execution
A flexible routing system maps URL paths to specific handler implementations using the Handler trait pattern. This design follows the Open/Closed Principle, allowing new routes to be added without modifying existing code.
Router Features:
- HashMap-based path resolution
- Trait-based handler abstraction
- Dynamic handler registration
- Automatic 404 handling for unmapped routes
Comprehensive error handling using the anyhow crate provides:
- Result-based error propagation
- Context-aware error messages
- Graceful failure recovery
- Proper logging of error conditions
Structured logging using log and env_logger crates enables:
- Multiple log levels (info, debug, error)
- Runtime-configurable verbosity
- Request/response lifecycle tracking
- Error diagnostics
The parse_request function implements a multi-stage parsing pipeline:
- Request Line Parsing: Splits the first line into method, path, and version
- URL Parsing: Separates path from query parameters
- Query Parameter Extraction: Parses key-value pairs from query string
- Header Extraction: Iterates through remaining lines to extract headers
- URL Decoding: Handles percent-encoded characters in URLs
pub fn parse_request(request_lines: &[String]) -> anyhow::Result<HttpRequest> { // Validates request structure // Parses request line components // Extracts and normalizes headers // Builds HttpRequest object }
The Handler trait defines a contract for request processing:
pub trait Handler: Send + Sync { fn handle(&self, request: &HttpRequest) -> anyhow::Result<HttpResponse>; }
Design Benefits:
- Polymorphism: Different handlers implement the same interface
- Testability: Handlers can be tested in isolation
- Extensibility: New handlers added without modifying router
- Type Safety: Compile-time verification of handler implementations
Type-safe HTTP status codes prevent invalid status code usage:
pub enum StatusCode { Ok, // 200 NotFound, // 404 InternalServerError, // 500 }
This ensures only valid status codes can be used, eliminating runtime errors from invalid values.
rust_web_server/
├── src/
│ ├── main.rs # Entry point, server initialization
│ ├── http.rs # HTTP protocol implementation
│ ├── handler.rs # Connection handling logic
│ ├── router/
│ │ └── mod.rs # Router and Handler trait
│ └── handlers/
│ ├── mod.rs # Handler module declarations
│ ├── home.rs # Home page handler
│ ├── about.rs # About page handler (with 5s delay)
│ ├── contact.rs # Contact information handler
│ ├── search.rs # Search with query parameters
│ ├── user_agent.rs # User-Agent header inspection
│ ├── headers.rs # All headers display
│ └── accept.rs # Accept/Host/Content-Type display
├── Cargo.toml # Project dependencies
└── README.md # This file
- main.rs: Server bootstrapping, router configuration, request loop
- http.rs: HTTP data structures, parsing logic, response building
- handler.rs: Connection lifecycle, request/response I/O
- router/: Route registration and handler dispatch
- handlers/: Individual request handlers implementing business logic
- Rust 1.70+ (edition 2024)
- Cargo package manager
# Clone the repository git clone <repository-url> cd rust_web_server # Build the project cargo build --release # Run the server with logging RUST_LOG=info cargo run
The server binds to 127.0.0.1:8080 by default. To change this, modify the bind address in main.rs:
let listener = TcpListener::bind("127.0.0.1:8080")?;
Control log verbosity with the RUST_LOG environment variable:
RUST_LOG=error cargo run # Errors only RUST_LOG=info cargo run # Info, errors RUST_LOG=debug cargo run # Debug, info, errors
Returns a welcome message.
curl http://127.0.0.1:8080/
# Response: Welcome to the home page!Returns about page information (simulates slow response with 5-second delay).
curl http://127.0.0.1:8080/about
# Response: This is the about page (after 5 seconds)Returns contact information.
curl http://127.0.0.1:8080/contact
# Response: Contact us at contact@example.comSearch endpoint demonstrating query parameter extraction.
curl http://127.0.0.1:8080/search?q=rust # Response: You searched for: rust curl http://127.0.0.1:8080/search # Response: No search query provided
Inspects and responds based on the User-Agent header.
curl http://127.0.0.1:8080/user-agent # Response: Hello curl user! Your User-Agent is: curl/7.64.1 curl -H "User-Agent: MyCustomBot/1.0" http://127.0.0.1:8080/user-agent # Response: Hello Bot! Your User-Agent is: MyCustomBot/1.0
Displays all HTTP headers received in the request.
curl http://127.0.0.1:8080/headers # Response: # HTTP Headers: # # host: 127.0.0.1:8080 # user-agent: curl/7.64.1 # accept: */*
Shows specific headers: Host, Accept, and Content-Type.
curl -H "Accept: application/json" http://127.0.0.1:8080/accept # Response: # Request Details: # # Host: 127.0.0.1:8080 # Accept: application/json # Content-Type: none
This implementation adheres to SOLID principles:
Each module has a single, well-defined purpose:
http.rs: HTTP protocol concerns onlyhandler.rs: Connection handling only- Each handler file: One specific route's logic
The router is open for extension (new handlers) but closed for modification. Adding a new route requires:
- Creating a new handler implementing the
Handlertrait - Registering it in
main.rs
No changes to existing router or handler code are needed.
All handler implementations can be substituted for the Handler trait without altering program correctness. The router treats all handlers uniformly.
The Handler trait is minimal and focused, requiring only one method: handle(). Handlers aren't forced to implement unnecessary methods.
- High-level router depends on the
Handlerabstraction - Low-level handlers implement the abstraction
- No direct dependencies between concrete implementations
- anyhow (1.0): Flexible error handling with context
- log (0.4): Logging facade providing abstraction over logging implementations
- env_logger (0.11): Logger implementation configured via environment variables
- derive_builder (0.20): Generates builder pattern implementations for structs
- anyhow: Simplifies error handling by providing a unified error type with context
- log + env_logger: Industry-standard logging solution with runtime configurability
- derive_builder: Reduces boilerplate for constructing complex objects
Potential improvements for educational purposes:
- HTTP Methods: Support POST, PUT, DELETE
- Request Body Parsing: Handle request bodies and form data
- Static File Serving: Serve HTML, CSS, JS files
- Middleware System: Request/response interceptors
- Thread Pool: Fixed-size thread pool instead of unbounded threads
- Keep-Alive: Support persistent connections
- TLS/HTTPS: Secure connections with OpenSSL
- Async/Await: Migration to tokio for async I/O
- HTTP/2: Implement HTTP/2 protocol features
- Compression: Gzip/Brotli response compression
By studying this implementation, you will understand:
- TCP socket programming in Rust
- HTTP protocol structure and parsing
- Concurrent programming with threads
- Error handling patterns in Rust
- Trait-based polymorphism
- Builder pattern for complex object construction
- Logging and observability practices
- Clean architecture and SOLID principles
- Module organization in Rust projects
MIT License - Feel free to use this for educational purposes.
This is an educational project. Contributions that improve clarity, add documentation, or demonstrate additional concepts are welcome.
Built with Rust 🦀 - A language empowering everyone to build reliable and efficient software.