A lightweight HTTP/HTTPS server built from raw TCP sockets in Go to understand HTTP protocol internals and network programming fundamentals. No frameworks - just socket programming, HTTP parsing, and TLS encryption.
Peak performance: 7,721 RPS | Built for learning, not production
git clone https://github.com/codetesla51/raw-http
cd raw-http
go mod tidy
go run main.go
# Server starts on http://localhost:8080
# HTTPS available on https://localhost:8443 (if certificates present)Try it:
curl http://localhost:8080/ping
# Returns: pong
curl -X POST http://localhost:8080/login \
-d "username=admin&password=secret"
# Returns: Login successful HTML page- Raw TCP handling - Parses HTTP requests directly from socket connections
- Custom routing - Simple router with method and path matching
- Static file serving - Serves files from
pages/with proper MIME types - Template rendering - Supports Go's
html/templatefor dynamic content - Connection management - Keep-alive support with proper connection reuse
- Form & JSON parsing - Handles both URL-encoded forms and JSON request bodies
- Security basics - Path traversal protection and request limits
- HTTPS/TLS support - Optional encrypted connections with certificate support
- Graceful shutdown - Clean server termination with signal handling
- Buffer pooling - Memory-optimized request/response handling with
sync.Pool
I recently learned about buffer pooling - a technique for reusing memory buffers instead of constantly allocating and deallocating them. The core idea is simple: instead of creating new buffers for every request, maintain a pool of reusable buffers.
The server uses three buffer pools:
- Request Buffer Pool (8KB) - Reuses buffers for reading incoming HTTP requests
- Chunk Buffer Pool (256 bytes) - Reuses small buffers for chunked reading
- Response Buffer Pool - Reuses
bytes.Bufferfor building HTTP responses
var requestBufferPool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 8192)
return &buf
},
}Get buffer from pool → Reset it → Use it → Return to pool
↓
(Reused by next request)
Instead of:
Allocate new buffer → Use it → Garbage collector cleans up
↓
(Memory churn + GC pressure)
| Metric | Before Buffer Pools | After Buffer Pools | Improvement |
|---|---|---|---|
| Peak RPS | 4,000 | 7,721 | +93% 🔥 |
| Memory Allocations | High | Minimal | Significantly reduced |
| GC Pressure | Constant | Minimal | Much lower |
Result: Nearly doubled throughput by eliminating memory allocation overhead!
The server now supports graceful shutdown, allowing in-flight requests to complete before the server stops. The server listens for interrupt signals (Ctrl+C) and SIGTERM signals, making it safe to stop in production-like environments.
- Signal Detection - Captures
SIGINT(Ctrl+C) andSIGTERMsignals - Stop Accepting Connections - Listeners stop accepting new connections immediately
- Grace Period - Waits 2 seconds for active connections to complete
- Clean Shutdown - Properly closes both HTTP and HTTPS listeners
# Start the server
go run main.go
# Server listening on http://localhost:8080
# TLS listener successfully started on https://localhost:8443
# Press Ctrl+C to trigger graceful shutdown
^C
# Shutting down server...
# Server stopped.The server uses Go's signal.NotifyContext to create a context that cancels when shutdown signals are received. Both HTTP and HTTPS listeners check this context and stop accepting new connections gracefully:
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
// Listeners check ctx.Done() to stop accepting connections
<-ctx.Done()
log.Println("Shutting down server...")This ensures that your application can be safely stopped without abruptly terminating active requests, preventing data loss or incomplete transactions.
This repository includes self-signed certificates for local development and testing:
server.crt- Self-signed certificateserver.key- Private key
The server automatically detects these files and enables HTTPS on port 8443. These certificates are suitable for development and learning purposes only.
To use standard certificates from a Certificate Authority (like Let's Encrypt):
-
Obtain certificates from a trusted CA:
# Using Let's Encrypt (via Certbot) certbot certonly --standalone -d yourdomain.com # Certificates will be in: /etc/letsencrypt/live/yourdomain.com/
-
Copy certificate files to your project directory:
cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem server.crt cp /etc/letsencrypt/live/yourdomain.com/privkey.pem server.key chmod 600 server.key
-
Restart the server - it will automatically use the new certificates:
go run main.go
If you want to create your own self-signed certificates:
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodesThis creates:
server.key- 4096-bit RSA private keyserver.crt- Self-signed certificate valid for 365 days
- Both
server.crtandserver.keymust exist in the project root - Files must be readable by the application
- If either file is missing or invalid, HTTPS will be disabled automatically (HTTP continues to work)
- Private key permissions should be restricted:
chmod 600 server.key
router := server.NewRouter()
// Handle form submissions with browser detection
router.Register("POST", "/login", func(req *server.Request) (string, string) {
username := req.Body["username"]
browser := req.Browser // "Chrome", "Firefox", etc.
if username == "admin" {
return server.CreateResponse("200", "text/html", "OK",
"<h1>Welcome "+username+"!</h1><p>Browser: "+browser+"</p>")
}
return server.CreateResponse("401", "text/html", "Unauthorized",
"<h1>Login Failed</h1>")
})
// Simple API endpoint
router.Register("GET", "/ping", func(req *server.Request) (string, string) {
return server.CreateResponse("200", "text/plain", "OK", "pong")
})| Concurrency Level | RPS | Response Time | Performance Tier |
|---|---|---|---|
| 10 | 7,721 | 0.130ms | Peak Performance 🔥 |
| 50 | 2,926 | 0.342ms | Excellent |
| 100 | 2,067 | 0.484ms | Very Good |
| 200 | 2,082 | 0.480ms | Very Good |
| 500 | 2,286 | 0.437ms | Good |
| 1000 | 1,232 | 0.811ms | Moderate Load |
| 1500 | 1,981 | 0.505ms | High Load |
| 2000 | 1,507 | 0.664ms | Stress Test |
Key insights:
- Optimal range: 10-50 concurrent connections (2,900-7,700 RPS)
- Sweet spot: Low concurrency scenarios achieve sub-millisecond response times
- Scaling: Maintains good performance up to 500 concurrent connections
- Reliability: 0% failure rate across all load levels tested
- Initial buggy version: ~250-282 RPS (Connection: close with processing bug)
- Bug fix: 1,389 RPS (Connection: close, but fixed request handling)
- Keep-alive optimization: 1,710 RPS (enabled proper connection reuse)
- Pre-buffer pooling: 4,000 RPS (optimal concurrency discovered)
- Buffer pooling added: 7,721 RPS (memory optimization breakthrough) 🚀
Total improvement: ~27x from initial version | Buffer pool impact: +93% improvement
These benchmarks test a simple /ping endpoint that returns "pong". Real-world applications with database queries, business logic, file I/O, and external API calls will see significantly lower RPS (typically 100-500 RPS for typical web applications). The buffer pooling optimization helps with the networking layer, but your application logic will be the primary bottleneck in production scenarios.
- TCP Connection Pooling: HTTP/1.1 keep-alive implementation
- Custom HTTP Parser: Zero-dependency request parsing with chunked reading
- Goroutine-per-connection: Leverages Go's concurrency model
- Memory Pooling:
sync.Poolfor buffer reuse across requests - MIME Detection: Comprehensive content-type handling for static files
- Memory Efficient: Streams request bodies instead of full buffering
- TLS/SSL Layer: Optional HTTPS encryption for secure connections
- Signal Handling: Context-based graceful shutdown with proper cleanup
├── server/
│ ├── server.go # Core HTTP server logic with buffer pools
│ └── server_test.go # Test suite
├── pages/ # Static files and templates
│ ├── index.html
│ ├── login.html
│ ├── welcome.html
│ └── styles.css
├── server.crt # Self-signed certificate (for HTTPS)
├── server.key # Private key (for HTTPS)
├── main.go # Example web application
└── README.md
Building from TCP sockets up provided insights into:
- How HTTP requests are structured and parsed at the protocol level
- TCP connection lifecycle and keep-alive mechanics
- TLS/SSL encryption and certificate management
- Security considerations (DoS protection, path traversal attacks)
- Go's networking primitives and goroutine-per-connection model
- Template rendering and form data handling from scratch
- The critical importance of proper connection reuse for performance
- Signal handling and graceful shutdown patterns for reliable server termination
- Buffer pooling and memory optimization - Reusing buffers with
sync.Poolto eliminate allocation overhead and reduce GC pressure
go test ./serverThe test suite covers request parsing, routing, response generation, and includes integration tests with real TCP connections.
Test the server with ApacheBench:
# Peak performance test (low concurrency)
ab -n 10000 -c 10 -k http://localhost:8080/ping
# High concurrency test
ab -n 5000 -c 1000 -k http://localhost:8080/ping
# Without keep-alive (shows the impact of connection reuse)
ab -n 5000 -c 100 http://localhost:8080/pingThis is a learning project, not production software:
- Basic error handling
- Simple routing (no path parameters or regex matching)
- No middleware system
- Limited HTTP method support
- Self-signed certificates only (use proper CAs for production)
- No rate limiting or DDoS protection
- No database connection pooling
- No caching layer
- Synthetic benchmarks (real apps with DB/logic will be slower)
Most web development happens at the framework level (Express, Flask, etc.). Building from TCP sockets up helps understand what's actually happening under the hood - HTTP parsing, connection management, TLS encryption, memory optimization, and the networking fundamentals that frameworks abstract away.
The performance optimization journey demonstrates how low-level implementation details (like buffer pooling and connection reuse) can have dramatic impacts on throughput and response times.
Visit these paths when running the server:
/- Home page with project info/welcome- Dynamic template rendering demo/login- Form handling example/hello- About page with technical details/ping- Simple API endpoint
Built with Go 1.21+ • Created by Uthman • Learning project focused on HTTP/HTTPS fundamentals and performance optimization