Welcome to the Multi-Threaded HTTP Server project. This repository explores how to build a production-inspired HTTP server in pure Python, focusing on concurrency, request validation, and static/dynamic payload handling. The code demonstrates GET/POST processing, keep-alive semantics, a cooperative thread pool, and basic safeguards against common mistakes such as directory traversal and Host header spoofing.
http_server.pysets up the listening socket, manages the worker pool, and routes requests to verb-specific handlers.GetHandlervalidates resource paths, enforces allowed extensions, and streams either text or binary bodies.PostHandlervalidates JSON uploads, persists them intoresources/uploads/, and returns structured JSON responses.- Builder classes (
HtmlBuilder,HttpResponseBuilder) encapsulate HTML/error generation and HTTP response headers to keep handlers focused on business logic. - Utility modules (
client.py,client_udp.py,server.py,server_udp.py,simple_server.py) showcase auxiliary experiments (HTTP client interactions, UDP echoing) and can be used for manual testing.
flowchart TD
A[Client] -->|HTTP Request| B(Socket Listener)
B -->|Accept| C{Thread Pool Queue}
C -->|Dispatch| D[Worker Thread]
D --> E[parse_http_request]
E --> F{Method Handler}
F -->|GET| G[GetHandler]
F -->|POST| H[PostHandler]
G --> I[Static/Binary Response]
H --> J[JSON Persistence]
I --> K[HttpResponseBuilder]
J --> K[HttpResponseBuilder]
K --> L{Connection Directive}
L -->|Keep-Alive| D
L -->|Close| M[Socket Close]
multi-threaded-http-server/
├─ http_server.py # Main multi-threaded HTTP server
|─ tests/ # Automated test suite using pytest
├─ resources/ # Static site assets served by the HTTP server
│ ├─ index.html
│ ├─ about.html
│ ├─ contact.html
│ ├─ hello.txt
│ └─ uploads/ # JSON uploads generated by POST /upload
├─ pyproject.toml # Project metadata and tooling dependencies
└─ README.md
GetHandler.serve_fileautomatically detects non-HTML extensions and routes them throughHttpResponseBuilder.add_disposible_file.- Binary payloads are read as raw bytes and returned with
Content-Disposition: attachment, retaining the original filename. Content-Lengthis calculated from the file system to prevent short reads and keep streaming simple.- HTML files are served as UTF-8 text; other allowed extensions (
txt,png,jpg,jpeg) are streamed as bytes. - The handler logs transfer size and type to help diagnose performance issues.
sequenceDiagram
participant Client
participant Listener
participant Queue
participant Worker
Client->>Listener: TCP SYN
Listener-->>Client: TCP SYN/ACK (handshake)
Listener->>Queue: Enqueue (conn, addr)
Worker->>Queue: get()
Queue-->>Worker: (conn, addr)
Worker->>Worker: handle_persistent_connection()
Worker->>Client: Response bytes
alt Connection Keep-Alive
Worker->>Queue: Reuse worker loop
else Close
Worker->>Client: FIN/ACK
end
- A global
queue.Queuebuffers accepted sockets while workers are busy. MAX_THREADSworker threads runthread_worker, blocking on the queue with a one-second timeout so they can exit cleanly on shutdown.active_threadsandthread_locktrack current load and allow the listener to respond with503 Service Unavailablewhen both the queue and workers are saturated.- Each worker processes up to
MAX_REQUESTS_PER_CONNECTIONrequests per socket with per-request connection-type negotiation.
- Host Header Whitelist:
validate_http_requestensures theHostheader matchesINTERFACE/PORTcombinations returned byget_allowed_hosts. - Path Normalisation:
GetHandler.validate_resource_pathrejects absolute paths, traversal attempts (..), and backslash delimiters. - Extension Allow-List: Only
html,txt,png,jpg, andjpegcan be served. - Uploads Segregation: JSON POST bodies are stored beneath
resources/uploads/with timestamped filenames to prevent collisions. - Graceful Overload Handling: The listener returns HTTP 503 instead of dropping sockets when the queue is full, protecting the process from resource exhaustion.
- HTTP parsing is line-oriented and does not support chunked transfer encoding or multi-part uploads.
- No TLS support; all traffic is plaintext.
- MIME detection is extension-based; a dedicated library (e.g.,
mimetypes) would improve accuracy. - Requests larger than the 8 KB buffer are truncated; a streaming parser would be safer for large uploads.
- The project targets Python
>= 3.9as declared inpyproject.toml. - Use uv for isolated virtual environments and dependency management.
- Optional developer tooling:
pytestandruffare listed under thedevdependency group.
# Install uv if you have not already
python -m pip install uv
# Create a virtual environment and install the project (editable)
uv sync# Syntax: uv run http_server.py <port> <host> <max-threads>
uv run .\http_server.py 8080 127.0.0.1 10
# Examples
uv run .\http_server.py # defaults to port 8080, host 127.0.0.1, 10 threads
uv run .\http_server.py 8000 # change port only
uv run .\http_server.py 8000 0.0.0.0 50- Static files live under
resources/. Visitinghttp://127.0.0.1:8080/servesindex.html. - Upload JSON with a POST request to
/uploadusingContent-Type: application/json. The server saves the file and returns the path. - Keep-alive is enabled by default for HTTP/1.1 clients unless they request
Connection: close.
# Fetch landing page (static HTML)
curl http://127.0.0.1:8080/
# Download binary asset and verify size
curl http://127.0.0.1:8080/logo.png --output logo.png
Get-FileHash .\logo.png
# Upload JSON payload (saved under resources/uploads/)
curl -X POST ^
-H "Content-Type: application/json" ^
-d '{"message":"hello"}' ^
http://127.0.0.1:8080/upload
# Trigger error handling (expected 404)
curl -i http://127.0.0.1:8080/nonexistent.png
# Demonstrate traversal defence (expected 403)
curl -i http://127.0.0.1:8080/../etc/passwd- Automated pytest suite:
Run
uv run pytest -vvto start an integration-focused suite that boots the server, hits core GET/POST paths, and asserts binary integrity via checksums. - Logging review: Monitor console output for timeout warnings, host validation results, and file transfer summaries.