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