- start the HTTP web server:
cargo run
- test the server from CLI:
# Serves static/index.html
curl http://127.0.0.1:8080/
# Returns {"message": "Hello, API!"}
curl http://127.0.0.1:8080/api/hello
TDD guide for HTTP Web Sever with Rust
- Overview
- Types
- Core Functions
- Content Type Mapping
- Error Handling
- Security Notes
This library implements a basic HTTP web server with support for static file serving and custom route handlers. It provides functionality for parsing HTTP requests, handling different routes, and serving responses.
pub type Handler = fn() -> (String, String);
A type alias representing route handler functions that return a tuple of:
- Response body (
String
) - Content type (
String
)
pub fn parse_request_line(line: &str) -> (&str, &str, &str)
Purpose: Parses the first line of an HTTP request.
Parameters:
line
: The request line string (e.g., "GET /path HTTP/1.1")
Returns:
- Tuple containing:
- HTTP method (e.g., "GET")
- Request path (e.g., "/path")
- Protocol version (e.g., "HTTP/1.1")
Error Handling:
- Returns empty strings ("", "", "") if parsing fails
pub fn parse_request(request: &str) -> (String, String, HashMap<String, String>)
Purpose: Parses a complete HTTP request including headers.
Parameters:
request
: Complete HTTP request string
Returns:
- Tuple containing:
- HTTP method
- Request path
- Headers map
pub fn handle_request(
method: &str,
path: &str,
base_dir: &str,
routes: &HashMap<String, Handler>,
) -> (u16, String, String, Box<dyn Fn(&mut dyn Write) -> std::io::Result<()>>)
Purpose: Processes HTTP requests and generates appropriate responses.
Parameters:
method
: HTTP methodpath
: Request pathbase_dir
: Base directory for static filesroutes
: Map of custom route handlers
Returns:
- Tuple containing:
- Status code (u16)
- Status reason phrase (String)
- Content type (String)
- Stream writer function (Box)
Behavior:
- Only handles GET requests (returns 405 for other methods)
- Checks for custom route handlers first
- Falls back to static file serving
- Returns 404 if resource not found
pub fn handle_connection(
mut stream: impl Read + Write,
base_dir: &str,
routes: &HashMap<String, Handler>,
)
Purpose: Handles individual TCP connections and manages the request-response cycle.
Parameters:
stream
: TCP stream implementing Read + Writebase_dir
: Base directory for static filesroutes
: Map of custom route handlers
Behavior:
- Reads request from stream
- Parses request headers
- Validates presence of Host header
- Processes request using
handle_request
- Writes response headers and body to stream
The server automatically determines content types for static files:
.html
→ "text/html".css
→ "text/css"- Other extensions → "application/octet-stream"
- 400 Bad Request: Missing Host header
- 404 Not Found: Missing resources
- 405 Method Not Allowed: Non-GET requests
- Basic path sanitization implemented
- Directory traversal protection via
Path::new().join()
- No authentication/authorization
This documentation covers the test module for a Rust HTTP web server implementation. The tests verify various functionalities including request parsing, request handling, API routing, and file streaming.
use crate::{Handler, handle_connection, handle_request, parse_request, parse_request_line};
use std::collections::HashMap;
use std::fs::File;
use std::io::Cursor;
use std::io::{Read, Write};
use tempfile::TempDir;
- Purpose: Validates parsing of basic HTTP request line
- Tests:
- Method extraction (GET)
- Path extraction (/)
- Protocol version extraction (HTTP/1.1)
- Expected Output: Correctly parsed components of the request line
- Purpose: Tests parsing of complete HTTP requests with headers
- Tests:
- Request line parsing
- Header parsing
- Host header validation
- Expected Output: Correctly parsed method, path, and headers map
- Purpose: Verifies handling of requests without headers
- Tests: Request parsing with empty header section
- Expected Output: Valid method and path with empty headers map
- Purpose: Tests serving of index.html file
- Setup: Creates temporary directory with index.html
- Tests: GET request to root path
- Expected Output:
- 200 status code
- text/html content type
- Purpose: Validates 404 response for missing files
- Tests: GET request to non-existent path
- Expected Output:
- 404 status code
- "Not Found" reason
- text/plain content type
- Purpose: Tests API route handling
- Setup: Registers /api/hello route handler
- Tests: GET request to API endpoint
- Expected Output:
- 200 status code
- application/json content type
- Correct JSON response
struct MockStream {
read_data: Cursor<Vec<u8>>,
write_data: Vec<u8>,
}
- Purpose: Simulates TCP stream for testing
- Capabilities:
- Read simulation
- Write capture
- Flush operation
- Purpose: End-to-end testing of connection handling
- Setup:
- Configures API route
- Creates mock stream with request
- Tests: Complete request-response cycle
- Expected Output:
- 200 OK response
- Valid JSON payload
- Purpose: Tests file streaming functionality
- Setup: Creates temporary file with HTML content
- Tests: File streaming to response
- Expected Output:
- 200 status code
- text/html content type
- Correct file contents in stream
impl Read for MockStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.read_data.read(buf)
}
}
impl Write for MockStream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write_data.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
- Purpose: Provides mock implementation for testing I/O operations
- Features:
- Simulated read operations
- Captured write operations
- No-op flush implementation
The test suite covers:
- Request parsing
- Header handling
- File serving
- API routing
- Error handling
- Stream handling
- File streaming
This file implements a multi-threaded HTTP web server in Rust that supports both API routes and static file serving. The server runs on localhost:8080
and demonstrates basic routing and concurrent connection handling.
use rust_http_web_server::{Handler, handle_connection}; // Custom handler types and connection logic
use std::collections::HashMap; // Route storage
use std::net::TcpListener; // TCP server functionality
use std::thread; // Multi-threading support
The main()
function serves as the entry point and performs these key operations:
-
Server Initialization
- Creates a TCP listener bound to
127.0.0.1:8080
- Provides immediate feedback on server startup
- Creates a TCP listener bound to
-
Route Configuration
- Initializes a
HashMap
to store route handlers - Routes are mapped as
String
(path) toHandler
(function) pairs
- Initializes a
-
API Route Registration
- Demonstrates route registration with
/api/hello
endpoint - Returns JSON response with appropriate content type
- Demonstrates route registration with
-
Connection Handling
- Implements an infinite loop to accept incoming connections
- Spawns a new thread for each connection
- Passes route information to connection handlers
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
- Binds server to localhost port 8080
- Uses
unwrap()
to handle binding failures with immediate panic
let mut routes: HashMap<String, Handler> = HashMap::new();
- Creates a mutable HashMap for storing route handlers
Handler
is a custom type (likely a function type) defined in the project's lib.rs
routes.insert("/api/hello".to_string(), || {
(
r#"{"message": "Hello, API!"}"#.to_string(),
"application/json".to_string(),
)
});
- Registers a simple JSON API endpoint
- Handler returns a tuple containing:
- Response body (JSON string)
- Content-Type header value
for stream in listener.incoming() {
let stream = stream.unwrap();
let routes = routes.clone();
thread::spawn(move || {
handle_connection(stream, "static", &routes);
});
}
- Implements concurrent connection handling
- Each connection runs in its own thread
- Routes are cloned for thread safety
- Static file directory is set to "static"
-
Thread Safety
- Route handlers must be
Clone
-able - Each connection gets its own copy of routes
- Route handlers must be
-
Error Handling
- Server binding uses
unwrap()
- fails fast on startup errors - Connection acceptance also uses
unwrap()
- individual connection failures don't crash server
- Server binding uses
-
Static File Serving
- Server expects static files in a "static" directory
- Directory path is relative to server execution path
-
Start the server:
cargo run
-
Access the API endpoint:
curl http://localhost:8080/api/hello
Expected response:
{ "message": "Hello, API!" }
- Custom
rust_http_web_server
library - Standard library components (
std::collections
,std::net
,std::thread
)