13 KiB
Server Package
Production-ready HTTP server manager with graceful shutdown, request draining, and comprehensive TLS/HTTPS support.
Features
✅ Multiple Server Management - Run multiple HTTP/HTTPS servers concurrently ✅ Graceful Shutdown - Handles SIGINT/SIGTERM with request draining ✅ Automatic Request Rejection - New requests get 503 during shutdown ✅ Health & Readiness Endpoints - Kubernetes-ready health checks ✅ Shutdown Callbacks - Register cleanup functions (DB, cache, metrics) ✅ Comprehensive TLS Support:
- Certificate files (production)
- Self-signed certificates (development/testing)
- Let's Encrypt / AutoTLS (automatic certificate management) ✅ GZIP Compression - Optional response compression ✅ Panic Recovery - Automatic panic recovery middleware ✅ Configurable Timeouts - Read, write, idle, drain, and shutdown timeouts
Quick Start
Single Server
import "github.com/bitechdev/ResolveSpec/pkg/server"
// Create server manager
mgr := server.NewManager()
// Add server
_, err := mgr.Add(server.Config{
Name: "api-server",
Host: "localhost",
Port: 8080,
Handler: myRouter,
GZIP: true,
})
// Start and wait for shutdown signal
if err := mgr.ServeWithGracefulShutdown(); err != nil {
log.Fatal(err)
}
Multiple Servers
mgr := server.NewManager()
// Public API
mgr.Add(server.Config{
Name: "public-api",
Port: 8080,
Handler: publicRouter,
})
// Admin API
mgr.Add(server.Config{
Name: "admin-api",
Port: 8081,
Handler: adminRouter,
})
// Start all and wait
mgr.ServeWithGracefulShutdown()
HTTPS/TLS Configuration
Option 1: Certificate Files (Production)
mgr.Add(server.Config{
Name: "https-server",
Host: "0.0.0.0",
Port: 443,
Handler: handler,
SSLCert: "/etc/ssl/certs/server.crt",
SSLKey: "/etc/ssl/private/server.key",
})
Option 2: Self-Signed Certificate (Development)
mgr.Add(server.Config{
Name: "dev-server",
Host: "localhost",
Port: 8443,
Handler: handler,
SelfSignedSSL: true, // Auto-generates certificate
})
Option 3: Let's Encrypt / AutoTLS (Production)
mgr.Add(server.Config{
Name: "prod-server",
Host: "0.0.0.0",
Port: 443,
Handler: handler,
AutoTLS: true,
AutoTLSDomains: []string{"example.com", "www.example.com"},
AutoTLSEmail: "admin@example.com",
AutoTLSCacheDir: "./certs-cache", // Certificate cache directory
})
Configuration
server.Config{
// Basic configuration
Name: "my-server", // Server name (required)
Host: "0.0.0.0", // Bind address
Port: 8080, // Port (required)
Handler: myRouter, // HTTP handler (required)
Description: "My API server", // Optional description
// Features
GZIP: true, // Enable GZIP compression
// TLS/HTTPS (choose one option)
SSLCert: "/path/to/cert.pem", // Certificate file
SSLKey: "/path/to/key.pem", // Key file
SelfSignedSSL: false, // Auto-generate self-signed cert
AutoTLS: false, // Let's Encrypt
AutoTLSDomains: []string{}, // Domains for AutoTLS
AutoTLSEmail: "", // Email for Let's Encrypt
AutoTLSCacheDir: "./certs-cache", // Cert cache directory
// Timeouts
ShutdownTimeout: 30 * time.Second, // Max shutdown time
DrainTimeout: 25 * time.Second, // Request drain timeout
ReadTimeout: 15 * time.Second, // Request read timeout
WriteTimeout: 15 * time.Second, // Response write timeout
IdleTimeout: 60 * time.Second, // Idle connection timeout
}
Graceful Shutdown
Automatic (Recommended)
mgr := server.NewManager()
// Add servers...
// This blocks until SIGINT/SIGTERM
mgr.ServeWithGracefulShutdown()
Manual Control
mgr := server.NewManager()
// Add and start servers
mgr.StartAll()
// Later... stop gracefully
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := mgr.StopAllWithContext(ctx); err != nil {
log.Printf("Shutdown error: %v", err)
}
Shutdown Callbacks
Register cleanup functions to run during shutdown:
// Close database
mgr.RegisterShutdownCallback(func(ctx context.Context) error {
log.Println("Closing database...")
return db.Close()
})
// Flush metrics
mgr.RegisterShutdownCallback(func(ctx context.Context) error {
log.Println("Flushing metrics...")
return metrics.Flush(ctx)
})
// Close cache
mgr.RegisterShutdownCallback(func(ctx context.Context) error {
log.Println("Closing cache...")
return cache.Close()
})
Health Checks
Adding Health Endpoints
instance, _ := mgr.Add(server.Config{
Name: "api-server",
Port: 8080,
Handler: router,
})
// Add health endpoints to your router
router.HandleFunc("/health", instance.HealthCheckHandler())
router.HandleFunc("/ready", instance.ReadinessHandler())
Health Endpoint
Returns server health status:
Healthy (200 OK):
{"status":"healthy"}
Shutting Down (503 Service Unavailable):
{"status":"shutting_down"}
Readiness Endpoint
Returns readiness with in-flight request count:
Ready (200 OK):
{"ready":true,"in_flight_requests":12}
Not Ready (503 Service Unavailable):
{"ready":false,"reason":"shutting_down"}
Shutdown Behavior
When a shutdown signal (SIGINT/SIGTERM) is received:
- Mark as shutting down → New requests get 503
- Execute callbacks → Run cleanup functions
- Drain requests → Wait up to
DrainTimeoutfor in-flight requests - Shutdown servers → Close listeners and connections
Time Event
─────────────────────────────────────────
0s Signal received: SIGTERM
├─ Mark servers as shutting down
├─ Reject new requests (503)
└─ Execute shutdown callbacks
1s Callbacks complete
└─ Start draining requests...
2s In-flight: 50 requests
3s In-flight: 32 requests
4s In-flight: 12 requests
5s In-flight: 3 requests
6s In-flight: 0 requests ✓
└─ All requests drained
6s Shutdown servers
7s All servers stopped ✓
Server Management
Get Server Instance
instance, err := mgr.Get("api-server")
if err != nil {
log.Fatal(err)
}
// Check status
fmt.Printf("Address: %s\n", instance.Addr())
fmt.Printf("Name: %s\n", instance.Name())
fmt.Printf("In-flight: %d\n", instance.InFlightRequests())
fmt.Printf("Shutting down: %v\n", instance.IsShuttingDown())
List All Servers
instances := mgr.List()
for _, instance := range instances {
fmt.Printf("Server: %s at %s\n", instance.Name(), instance.Addr())
}
Remove Server
// Stop and remove a server
if err := mgr.Remove("api-server"); err != nil {
log.Printf("Error removing server: %v", err)
}
Restart All Servers
// Gracefully restart all servers
if err := mgr.RestartAll(); err != nil {
log.Printf("Error restarting: %v", err)
}
Kubernetes Integration
Deployment with Probes
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
template:
spec:
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 8080
# Liveness probe
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
# Readiness probe
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
# Graceful shutdown
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
env:
- name: SHUTDOWN_TIMEOUT
value: "30"
# Allow time for graceful shutdown
terminationGracePeriodSeconds: 35
Docker Compose
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SHUTDOWN_TIMEOUT=30
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 3
stop_grace_period: 35s
Complete Example
package main
import (
"context"
"log"
"net/http"
"time"
"github.com/bitechdev/ResolveSpec/pkg/server"
)
func main() {
// Create server manager
mgr := server.NewManager()
// Register shutdown callbacks
mgr.RegisterShutdownCallback(func(ctx context.Context) error {
log.Println("Cleanup: Closing database...")
// return db.Close()
return nil
})
// Create router
router := http.NewServeMux()
router.HandleFunc("/api/data", dataHandler)
// Add server
instance, err := mgr.Add(server.Config{
Name: "api-server",
Host: "0.0.0.0",
Port: 8080,
Handler: router,
GZIP: true,
ShutdownTimeout: 30 * time.Second,
DrainTimeout: 25 * time.Second,
})
if err != nil {
log.Fatal(err)
}
// Add health endpoints
router.HandleFunc("/health", instance.HealthCheckHandler())
router.HandleFunc("/ready", instance.ReadinessHandler())
// Start and wait for shutdown
log.Println("Starting server on :8080")
if err := mgr.ServeWithGracefulShutdown(); err != nil {
log.Printf("Server stopped: %v", err)
}
log.Println("Server shutdown complete")
}
func dataHandler(w http.ResponseWriter, r *http.Request) {
time.Sleep(100 * time.Millisecond) // Simulate work
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message":"success"}`))
}
Testing Graceful Shutdown
Test Script
#!/bin/bash
# Start server in background
./myapp &
SERVER_PID=$!
# Wait for server to start
sleep 2
# Send requests
for i in {1..10}; do
curl http://localhost:8080/api/data &
done
# Wait a bit
sleep 1
# Send shutdown signal
kill -TERM $SERVER_PID
# Try more requests (should get 503)
curl -v http://localhost:8080/api/data
# Wait for server to stop
wait $SERVER_PID
echo "Server stopped gracefully"
Best Practices
-
Set appropriate timeouts
DrainTimeout<ShutdownTimeoutShutdownTimeout< KubernetesterminationGracePeriodSeconds
-
Use shutdown callbacks for:
- Database connections
- Message queues
- Metrics flushing
- Cache shutdown
- Background workers
-
Health checks
- Use
/healthfor liveness (is app alive?) - Use
/readyfor readiness (can app serve traffic?)
- Use
-
Load balancer considerations
- Set
preStophook in Kubernetes (5-10s delay) - Allows load balancer to deregister before shutdown
- Set
-
HTTPS in production
- Use AutoTLS for public-facing services
- Use certificate files for enterprise PKI
- Use self-signed only for development/testing
-
Monitoring
- Track in-flight requests in metrics
- Alert on slow drains
- Monitor shutdown duration
Troubleshooting
Shutdown Takes Too Long
// Increase drain timeout
config.DrainTimeout = 60 * time.Second
config.ShutdownTimeout = 65 * time.Second
Requests Timing Out
// Increase write timeout
config.WriteTimeout = 30 * time.Second
Certificate Issues
// Verify certificate files exist and are readable
if _, err := os.Stat(config.SSLCert); err != nil {
log.Fatalf("Certificate not found: %v", err)
}
// For AutoTLS, ensure:
// - Port 443 is accessible
// - Domains resolve to server IP
// - Cache directory is writable
Debug Logging
import "github.com/bitechdev/ResolveSpec/pkg/logger"
// Enable debug logging
logger.SetLevel("debug")
API Reference
Manager Methods
NewManager()- Create new server managerAdd(cfg Config)- Register server instanceGet(name string)- Get server by nameRemove(name string)- Stop and remove serverStartAll()- Start all registered serversStopAll()- Stop all servers gracefullyStopAllWithContext(ctx)- Stop with timeoutRestartAll()- Restart all serversList()- Get all server instancesServeWithGracefulShutdown()- Start and block until shutdownRegisterShutdownCallback(cb)- Register cleanup function
Instance Methods
Start()- Start the serverStop(ctx)- Stop gracefullyAddr()- Get server addressName()- Get server nameHealthCheckHandler()- Get health handlerReadinessHandler()- Get readiness handlerInFlightRequests()- Get in-flight countIsShuttingDown()- Check shutdown statusWait()- Block until shutdown complete