Files
ResolveSpec/pkg/server
Hein 2017465cb8 feat(staticweb): add path prefix stripping to all filesystem providers
Add PrefixStrippingProvider interface and implement it in all providers
(EmbedFSProvider, LocalFSProvider, ZipFSProvider) to support serving
files from subdirectories at the root level.
2026-01-03 14:39:51 +02:00
..
2025-12-30 17:46:33 +02:00
2025-12-29 17:19:16 +02:00
2025-12-29 17:19:16 +02:00
2025-12-29 17:19:16 +02:00
2025-12-30 14:40:45 +02:00

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

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:

  1. Mark as shutting down → New requests get 503
  2. Execute callbacks → Run cleanup functions
  3. Drain requests → Wait up to DrainTimeout for in-flight requests
  4. 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

  1. Set appropriate timeouts

    • DrainTimeout < ShutdownTimeout
    • ShutdownTimeout < Kubernetes terminationGracePeriodSeconds
  2. Use shutdown callbacks for:

    • Database connections
    • Message queues
    • Metrics flushing
    • Cache shutdown
    • Background workers
  3. Health checks

    • Use /health for liveness (is app alive?)
    • Use /ready for readiness (can app serve traffic?)
  4. Load balancer considerations

    • Set preStop hook in Kubernetes (5-10s delay)
    • Allows load balancer to deregister before shutdown
  5. HTTPS in production

    • Use AutoTLS for public-facing services
    • Use certificate files for enterprise PKI
    • Use self-signed only for development/testing
  6. 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 manager
  • Add(cfg Config) - Register server instance
  • Get(name string) - Get server by name
  • Remove(name string) - Stop and remove server
  • StartAll() - Start all registered servers
  • StopAll() - Stop all servers gracefully
  • StopAllWithContext(ctx) - Stop with timeout
  • RestartAll() - Restart all servers
  • List() - Get all server instances
  • ServeWithGracefulShutdown() - Start and block until shutdown
  • RegisterShutdownCallback(cb) - Register cleanup function

Instance Methods

  • Start() - Start the server
  • Stop(ctx) - Stop gracefully
  • Addr() - Get server address
  • Name() - Get server name
  • HealthCheckHandler() - Get health handler
  • ReadinessHandler() - Get readiness handler
  • InFlightRequests() - Get in-flight count
  • IsShuttingDown() - Check shutdown status
  • Wait() - Block until shutdown complete