A robust, production-ready HTTP/1.1 server from scratch using low-level socket programming and featuring thread pooling, persistent connections, binary file transfer, and comprehensive security measures.
HTTP-server/
βββ server.py # Main server implementation
βββ test.py # Test suite
βββ resources/ # Web root directory
βββ index.html # Default homepage
βββ about.html # About page
βββ contact.html # Contact page
βββ logo.png # PNG image
βββ logo2.png # PNG image
βββ photo.jpg # JPEG image
βββ sample.txt # Text file
βββ uploads/ # POST upload directory
βββ upload_*.json # Uploaded JSON files
- Python 3.6 or higher
- No external dependencies required (uses standard library only)
- Clone or download the project:
cd HTTP-server- Verify directory structure:
# Ensure resources directory exists
mkdir -p resources/uploads
# Check file permissions
chmod +x server.py- Add your content files to resources/:
# Your HTML, images, and text files should be in resources/
ls resources/
# Expected: index.html, about.html, contact.html, logo.png, photo.jpg, sample.txtpython3 server.pypython3 server.py 8000python3 server.py 8000 0.0.0.0python3 server.py [PORT] [HOST] [MAX_THREADS]
# Example: Port 8000, all interfaces, 20 threads
python3 server.py 8000 0.0.0.0 20./server.py 8080 127.0.0.1 10[2024-03-15 10:30:00] HTTP Server started on http://127.0.0.1:8080
[2024-03-15 10:30:00] Thread pool size: 10
[2024-03-15 10:30:00] Serving files from 'resources' directory
[2024-03-15 10:30:00] Press Ctrl+C to stop the server
Press Ctrl+C to gracefully shutdown:
^C[2024-03-15 10:35:00] Shutting down server...
The server implements efficient binary file transfer using chunked transfer encoding for handling large files without loading them entirely into memory.
def is_supported_file(filename):
"""Identifies file types for binary transfer"""
ext = filename.lower().split('.')[-1]
return ext in ['html', 'txt', 'png', 'jpg', 'jpeg']def get_content_headers(full_path):
"""Sets appropriate headers based on file type"""
# HTML files - rendered in browser
if ext == "html":
headers["Content-Type"] = "text/html; charset=utf-8"
# Binary files - downloaded as attachments
elif ext in ("txt", "png", "jpg", "jpeg"):
headers["Content-Type"] = "application/octet-stream"
headers["Content-Disposition"] = f'attachment; filename="{basename}"'The server uses chunked transfer encoding to stream large files efficiently:
def handle_get(client_socket, path, headers, client_address, request_line, host_header):
buffer_size = 8192 # 8KB chunks
with open(full_path, "rb") as f:
# Send headers with Transfer-Encoding: chunked
headers_response["Transfer-Encoding"] = "chunked"
client_socket.sendall(build_http_response("200 OK", headers_response, b""))
# Stream file in chunks
total_sent = 0
while True:
chunk = f.read(buffer_size)
if not chunk:
break
# Send chunk size in hexadecimal
chunk_header = f"{len(chunk):X}\r\n".encode()
client_socket.sendall(chunk_header)
# Send chunk data
client_socket.sendall(chunk)
client_socket.sendall(b"\r\n")
total_sent += len(chunk)
# Send final zero-length chunk
client_socket.sendall(b"0\r\n\r\n")β
Memory Efficient: Files are streamed in 8KB chunks, not loaded entirely into memory
β
Large File Support: Can handle files of any size
β
Progress Tracking: Logs total bytes transferred
β
Binary Mode: Files read in rb mode to preserve binary data
β
Proper Headers: Includes Content-Disposition for downloads
[2024-03-15 10:30:15] [Thread-1] Starting binary file transfer: photo.jpg
[2024-03-15 10:30:15] [Thread-1] Completed file transfer: photo.jpg (2453678 bytes)
[2024-03-15 10:30:15] [Thread-1] Response: 200 OK (2453678 bytes transferred)
The server implements a custom thread pool with connection queuing to handle concurrent requests efficiently while preventing resource exhaustion.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Main Server Thread β
β (Accept Connections) β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β Thread Pool Manager β
β (Synchronization) β
βββββββββββββ¬ββββββββββββ
β
βββββββββββββββββββββΌββββββββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Thread 1 β β Thread 2 β β Thread N β
β (Worker) β β (Worker) β β (Worker) β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β β β
βΌ βΌ βΌ
[Client 1] [Client 2] [Client N]
β
βΌ
βββββββββββββββββββββββββ
β Connection Queue β
β (When pool full) β
β Max: 50 connections β
βββββββββββββββββββββββββ
def thread_pool_manager(client_socket, client_address):
"""Manages thread allocation and queuing"""
global active_threads
with active_threads_lock:
# Check pool capacity
if active_threads >= MAX_THREADS:
# Queue connection if space available
if len(connection_queue) < QUEUE_SIZE:
log("Warning: Thread pool saturated, queuing connection")
connection_queue.append((client_socket, client_address))
else:
# Reject with 503 Service Unavailable
send_503_response(client_socket)
return
# Allocate thread
active_threads += 1
# Create daemon thread for client
t = threading.Thread(target=handle_client,
args=(client_socket, client_address),
daemon=True)
t.start()# Global state management
active_threads_lock = threading.Lock() # Protects active_threads counter
connection_queue_lock = threading.Lock() # Protects connection_queue
active_threads = 0 # Current active thread count
connection_queue = [] # Pending connections# After accepting new connection, process queue
while True:
with active_threads_lock:
available = active_threads < MAX_THREADS
with connection_queue_lock:
if available and connection_queue:
queued_socket, queued_addr = connection_queue.pop(0)
log(f"Connection dequeued, assigned to Thread-{threading.active_count()}")
thread_pool_manager(queued_socket, queued_addr)
else:
breakβ
Configurable Size: Default 10 threads, adjustable via CLI
β
Connection Queuing: 50-connection queue when pool saturated
β
Automatic Scaling: Threads created on-demand up to limit
β
Graceful Degradation: Returns 503 when queue full
β
Thread Safety: Mutex locks prevent race conditions
β
Resource Cleanup: Proper thread and socket cleanup
β
Real-time Monitoring: Visual status indicators (π’π‘π΄)
[2024-03-15 10:35:00] π’ Thread pool: 3/10 active | Queue: 0/50 # Normal
[2024-03-15 10:35:30] π‘ Thread pool: 8/10 active | Queue: 2/50 # Near capacity
[2024-03-15 10:35:45] π΄ Thread pool: 10/10 active | Queue: 15/50 # Saturated
[2024-03-15 10:35:50] Warning: Thread pool saturated, queuing connection
[2024-03-15 10:35:55] Connection dequeued, assigned to Thread-3
| Metric | Value | Description |
|---|---|---|
| Default Threads | 10 | Configurable via CLI |
| Queue Size | 50 | Pending connections |
| Per-Thread Timeout | 30s | Idle connection timeout |
| Requests/Connection | 100 | Keep-alive limit |
| Request Size | 8192 bytes | Max HTTP request |
# Test thread pool with concurrent requests
for i in {1..25}; do
curl http://localhost:8080/photo.jpg -o /dev/null &
done
wait
# Monitor server logs to see:
# - Thread pool saturation
# - Connection queuing
# - Dequeuing as threads free upThreat: Attackers attempt to access files outside the web root using directory traversal.
Implementation:
def safe_path(path):
"""Validates and canonicalizes paths"""
# Decode URL encoding
path = unquote(path)
# Block obvious traversal attempts
if '..' in path or './' in path or path.startswith(('//', '\\')):
return None
# Construct and normalize path
full_path = os.path.abspath(os.path.join(RESOURCES_DIR, path))
base_dir = os.path.abspath(RESOURCES_DIR)
# Verify path stays within base directory
common = os.path.commonpath([base_dir, full_path])
if common != base_dir:
return None
# Double-check with string prefix
if not (full_path.startswith(base_dir + os.sep) or full_path == base_dir):
return None
return full_pathBlocked Attacks:
# All return 403 Forbidden
curl http://localhost:8080/../etc/passwd
curl http://localhost:8080/../../sensitive.txt
curl http://localhost:8080//etc/hosts
curl http://localhost:8080/%2e%2e%2f%2e%2e%2fetc%2fpasswd # URL encoded
curl http://localhost:8080/....//....//etc/passwdLogging:
[2024-03-15 10:30:15] [Thread-1] Path traversal/forbidden: /../etc/passwd
Threat: Host header injection attacks and request smuggling.
Implementation:
def valid_host(host_header, host_setting, port_setting):
"""Validates Host header matches server configuration"""
valid_hosts = {
f"localhost:{port_setting}",
f"127.0.0.1:{port_setting}",
f"{host_setting}:{port_setting}",
"localhost",
"127.0.0.1",
host_setting
}
return host_header in valid_hostsProtection:
# Valid requests
curl -H "Host: localhost:8080" http://localhost:8080/
curl -H "Host: 127.0.0.1:8080" http://localhost:8080/
# Blocked requests (403 Forbidden)
curl -H "Host: malicious.com" http://localhost:8080/
curl -H "Host: evil.com:8080" http://localhost:8080/
# Missing Host header (400 Bad Request)
curl -H "Host:" http://localhost:8080/Logging:
[2024-03-15 10:30:15] [Thread-1] Host validation: localhost:8080 β
[2024-03-15 10:30:16] [Thread-2] Host validation: malicious.com β
[2024-03-15 10:30:16] [Thread-2] Host validation failed: malicious.com
Threat: Execution of uploaded scripts or serving of sensitive files.
Implementation:
def is_supported_file(filename):
"""Whitelist approach - only allow specific extensions"""
ext = filename.lower().split('.')[-1]
return ext in ['html', 'txt', 'png', 'jpg', 'jpeg']Protection:
- β
Serves:
.html,.txt,.png,.jpg,.jpeg - β Blocks:
.php,.py,.sh,.exe,.js,.env, etc.
# Allowed
curl http://localhost:8080/index.html # 200 OK
curl http://localhost:8080/photo.jpg # 200 OK
# Blocked (415 Unsupported Media Type)
curl http://localhost:8080/script.php
curl http://localhost:8080/server.py
curl http://localhost:8080/.envThreat: File upload attacks, XSS via uploaded content.
Implementation:
def handle_post(client_socket, path, headers, body):
"""Only accept JSON content"""
content_type = headers.get("Content-Type", "")
if content_type != "application/json":
return send_415_error(client_socket)
# Validate JSON structure
try:
data = json.loads(body)
except:
return send_400_error(client_socket)Protection:
# Allowed
curl -X POST http://localhost:8080/upload \
-H "Content-Type: application/json" \
-d '{"data": "value"}'
# Blocked (415 Unsupported Media Type)
curl -X POST http://localhost:8080/upload \
-H "Content-Type: text/html" \
-d '<script>alert("xss")</script>'
curl -X POST http://localhost:8080/upload \
-H "Content-Type: multipart/form-data" \
--form "file=@malicious.php"Threat: Denial of Service via large requests.
Implementation:
def handle_client(client_socket, client_address):
data = client_socket.recv(8192) # Maximum 8KB per requestProtection:
- Maximum request size: 8192 bytes (8KB)
- Prevents memory exhaustion attacks
- Requests exceeding limit are truncated
Threat: Resource exhaustion via connection flooding.
Implementation:
# Per-connection limits
CONNECTION_TIMEOUT = 30 # 30 second timeout
MAX_PERSISTENT_REQUESTS = 100 # Max requests per connection
# Global limits
MAX_THREADS = 10 # Configurable thread pool
QUEUE_SIZE = 50 # Connection queue limitProtection:
# After 100 requests on same connection
[2024-03-15 10:30:15] [Thread-1] Closing persistent connection
# When thread pool saturated and queue full
HTTP/1.1 503 Service Unavailable
Retry-After: 5Threat: Slowloris and slow-read attacks.
Implementation:
client_socket.settimeout(CONNECTION_TIMEOUT) # 30 seconds
try:
data = client_socket.recv(8192)
except socket.timeout:
log_thread("Connection timed out")
close_connection()Protection:
- Idle connections closed after 30 seconds
- Prevents resource holding attacks
- Logged for monitoring
| Security Feature | Status | Impact |
|---|---|---|
| Path Traversal Protection | β Implemented | Prevents unauthorized file access |
| Host Header Validation | β Implemented | Prevents header injection |
| File Type Whitelist | β Implemented | Prevents script execution |
| Content-Type Enforcement | β Implemented | Prevents upload attacks |
| Request Size Limits | β Implemented | Prevents DoS |
| Connection Limits | β Implemented | Prevents resource exhaustion |
| Automatic Timeouts | β Implemented | Prevents slowloris attacks |
| Input Validation | β Implemented | Prevents malformed requests |
| Comprehensive Logging | β Implemented | Enables security monitoring |
- β HTTP/2 Not Supported: Only HTTP/1.1 protocol implemented
- β No HTTPS/TLS: Traffic is unencrypted (plain HTTP only)
β οΈ Impact: Sensitive data transmitted in plain text- π‘ Mitigation: Use reverse proxy (nginx/Apache) with TLS termination
- β 8KB Request Limit: Maximum request size is 8192 bytes
β οΈ Impact: Large POST bodies (>8KB) will be truncatedβ οΈ Affects: Large JSON uploads, multipart form data- π‘ Workaround: Split large uploads or increase
recv()buffer
- β JSON Only: POST accepts only
application/jsoncontent-type - β No Multipart Support: Cannot handle file uploads via forms
- β No Binary POST: Cannot POST images, documents, etc.
β οΈ Impact: Limited to JSON API use cases- π‘ Workaround: Use Base64 encoding for binary data in JSON
- β No gzip/deflate: Responses are not compressed
β οΈ Impact: Higher bandwidth usage, slower transfer speedsβ οΈ Affects: Large HTML files, JSON responses- π‘ Mitigation: Use reverse proxy for compression
- β No Cache-Control: Doesn't set caching headers
- β No ETag Support: No conditional request support
- β No Last-Modified: Cannot validate cached content
β οΈ Impact: Clients can't cache effectively, repeated downloads- π‘ Mitigation: Add caching middleware or use CDN
- β No Partial Content: Cannot resume interrupted downloads
- β No Range Header:
Range: bytes=0-1023not supported β οΈ Impact: Large file downloads must restart from beginningβ οΈ Affects: Video streaming, large binary files- π‘ Workaround: Use external download manager or CDN
- β No Authentication: No user authentication mechanism
- β No Authorization: No access control lists
- β No Rate Limiting Per User: Only global connection limits
β οΈ Impact: All content publicly accessible- π‘ Mitigation: Implement reverse proxy with authentication
β οΈ Thread Per Connection: Each connection consumes one threadβ οΈ Not Async/Await: Blocks on I/O operationsβ οΈ GIL Impact: Python's Global Interpreter Lock affects CPU-bound tasksβ οΈ Impact: Limited to ~1000 concurrent connections realistically- π‘ Alternative: Consider async frameworks (aiohttp, asyncio) for higher concurrency
β οΈ Chunked Transfer: Efficient for files, but whole-file modes exist in codeβ οΈ Connection Queue: 50 queued connections consume memoryβ οΈ Thread Overhead: Each thread ~8MB memory on Linux- π‘ Estimation: 10 threads + 50 queue β 80-100MB base memory
- β No Graceful Restart: Server must be manually restarted
- β No Health Checks: No monitoring endpoint
- β No Metrics Export: Cannot export Prometheus/StatsD metrics
- π‘ Mitigation: Use process manager (systemd, supervisor)
- β IPv4 Only: Only binds to IPv4 addresses
β οΈ Impact: Cannot serve IPv6-only clients- π‘ Workaround: Modify socket to
AF_INET6for IPv6 support
- β No CORS Headers: Cross-Origin Resource Sharing not implemented
β οΈ Impact: AJAX requests from other domains blocked by browsers- π‘ Mitigation: Add CORS headers or use proxy
β οΈ Console Only: Logs only to stdout, no file rotation- β No Log Levels: Cannot filter by severity (DEBUG, INFO, ERROR)
- β No Structured Logging: Plain text, not JSON/machine-parseable
- π‘ Mitigation: Redirect to file with log rotation tool
β οΈ Manual Testing: No automated test suite provided- β No Unit Tests: Individual functions not unit tested
- β No Integration Tests: End-to-end scenarios not automated
- π‘ Recommendation: Add pytest-based test suite
- β Not Production-Hardened: Missing features for production use:
- No process manager integration
- No health check endpoint
- No graceful shutdown handling
- No request ID tracking
- No distributed tracing
- π‘ Recommendation: Use behind reverse proxy (nginx) with proper monitoring
For production use, deploy behind a reverse proxy:
[Client] β [nginx/Apache] β [HTTP Server]
β
- HTTPS/TLS
- Compression
- Caching
- Rate Limiting
- Load Balancing
Example nginx configuration:
upstream http_server {
server 127.0.0.1:8080;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
gzip on;
gzip_types text/html text/css application/json;
location / {
proxy_pass http://http_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}GET /
GET /index.html
GET /about.html
GET /contact.htmlResponse: 200 OK with text/html; charset=utf-8
GET /logo.png
GET /logo2.png
GET /photo.jpg
GET /sample.txtResponse: 200 OK with application/octet-stream and Content-Disposition: attachment
POST /upload
Content-Type: application/json
{"key": "value", "data": "example"}Response: 201 Created
{
"status": "success",
"message": "File created successfully",
"filepath": "/uploads/upload_20240315_123456_a7b9.json"
}| Code | Description | Example |
|---|---|---|
| 400 | Bad Request | Malformed HTTP, missing Host |
| 403 | Forbidden | Path traversal, Host mismatch |
| 404 | Not Found | File doesn't exist |
| 405 | Method Not Allowed | PUT, DELETE, PATCH |
| 415 | Unsupported Media Type | Wrong Content-Type or file extension |
| 500 | Internal Server Error | Server exception |
| 503 | Service Unavailable | Thread pool exhausted |
python3 test.pycurl http://localhost:8080/index.html
curl http://localhost:8080/about.htmlcurl -O http://localhost:8080/logo.png
curl -O http://localhost:8080/photo.jpg
curl -O http://localhost:8080/sample.txtcurl -X POST http://localhost:8080/upload \
-H "Content-Type: application/json" \
-d '{"name": "test", "value": 123}'# show headers + body
curl -i --path-as-is "http://localhost:8080/../etc/passwd" -H "Host: localhost:8080"
curl -i --path-as-is "http://localhost:8080/./././../config" -H "Host: localhost:8080"
# Host validation (should return 403)
curl -H "Host: malicious.com" http://localhost:8080/
Typical performance on modern hardware (Intel i5, 16GB RAM):
| Metric | Value |
|---|---|
| Requests/sec | ~500-1000 |
| Avg Latency | 10-50ms |
| Max Concurrent | ~50-100 |
| Memory Usage | 80-150MB |
| CPU Usage | 20-60% |
Note: Performance varies based on file sizes and connection patterns
# Find process using port
lsof -i :8080
netstat -tulpn | grep 8080
# Kill process
kill -9 <PID>
# Or use different port
python3 server.py 8081# Make executable
chmod +x server.py
# Or use python directly
python3 server.py
# For ports < 1024, use sudo (not recommended)
sudo python3 server.py 80# Verify files exist
ls -la resources/
# Check file permissions
chmod 644 resources/*.html resources/*.png resources/*.jpg
# Check directory permissions
chmod 755 resources/# Check if server is running
ps aux | grep server.py
# Check firewall
sudo ufw status
sudo ufw allow 8080/tcp
# Test locally first
curl http://127.0.0.1:8080/# Create upload directory
mkdir -p resources/uploads
# Set permissions
chmod 755 resources/uploads/This project is provided as-is for educational and commercial use.
Contributions welcome! Priority areas:
- HTTP/2 support
- HTTPS/TLS implementation
- Compression middleware
- Caching headers
- Range request support
- Automated test suite
- Check server logs for detailed error information
- Verify directory structure and file permissions
- Test with curl for detailed HTTP debugging
- Review security logs for blocked requests
- Check resource usage (memory, threads, connections)
Built with Python 3 β’ Thread-safe β’ Security-focused