server

package
v0.0.111 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2025 License: MIT Imports: 10 Imported by: 0

README

Server Package

Graceful HTTP server with request draining and shutdown coordination.

Quick Start

import "github.com/bitechdev/ResolveSpec/pkg/server"

// Create server
srv := server.NewGracefulServer(server.Config{
    Addr:    ":8080",
    Handler: router,
})

// Start server (blocks until shutdown signal)
if err := srv.ListenAndServe(); err != nil {
    log.Fatal(err)
}

Features

✅ Graceful shutdown on SIGINT/SIGTERM ✅ Request draining (waits for in-flight requests) ✅ Automatic request rejection during shutdown ✅ Health and readiness endpoints ✅ Shutdown callbacks for cleanup ✅ Configurable timeouts

Configuration

config := server.Config{
    // Server address
    Addr: ":8080",

    // HTTP handler
    Handler: myRouter,

    // Maximum time for graceful shutdown (default: 30s)
    ShutdownTimeout: 30 * time.Second,

    // Time to wait for in-flight requests (default: 25s)
    DrainTimeout: 25 * time.Second,

    // Request read timeout (default: 10s)
    ReadTimeout: 10 * time.Second,

    // Response write timeout (default: 10s)
    WriteTimeout: 10 * time.Second,

    // Idle connection timeout (default: 120s)
    IdleTimeout: 120 * time.Second,
}

srv := server.NewGracefulServer(config)

Shutdown Behavior

Signal received (SIGINT/SIGTERM):

  1. Mark as shutting down - New requests get 503
  2. Drain requests - Wait up to DrainTimeout for in-flight requests
  3. Shutdown server - Close listeners and connections
  4. Execute callbacks - Run registered cleanup functions
Time   Event
─────────────────────────────────────────
0s     Signal received: SIGTERM
       ├─ Mark as shutting down
       ├─ Reject new requests (503)
       └─ Start draining...

1s     In-flight: 50 requests
2s     In-flight: 32 requests
3s     In-flight: 12 requests
4s     In-flight: 3 requests
5s     In-flight: 0 requests ✓
       └─ All requests drained

5s     Execute shutdown callbacks
6s     Shutdown complete

Health Checks

Health Endpoint

Returns 200 when healthy, 503 when shutting down:

router.HandleFunc("/health", srv.HealthCheckHandler())

Response (healthy):

{"status":"healthy"}

Response (shutting down):

{"status":"shutting_down"}
Readiness Endpoint

Includes in-flight request count:

router.HandleFunc("/ready", srv.ReadinessHandler())

Response:

{"ready":true,"in_flight_requests":12}

During shutdown:

{"ready":false,"reason":"shutting_down"}

Shutdown Callbacks

Register cleanup functions to run during shutdown:

// Close database
server.RegisterShutdownCallback(func(ctx context.Context) error {
    logger.Info("Closing database connection...")
    return db.Close()
})

// Flush metrics
server.RegisterShutdownCallback(func(ctx context.Context) error {
    logger.Info("Flushing metrics...")
    return metricsProvider.Flush(ctx)
})

// Close cache
server.RegisterShutdownCallback(func(ctx context.Context) error {
    logger.Info("Closing cache...")
    return cache.Close()
})

Complete Example

package main

import (
    "context"
    "log"
    "net/http"
    "time"

    "github.com/bitechdev/ResolveSpec/pkg/middleware"
    "github.com/bitechdev/ResolveSpec/pkg/metrics"
    "github.com/bitechdev/ResolveSpec/pkg/server"
    "github.com/gorilla/mux"
)

func main() {
    // Initialize metrics
    metricsProvider := metrics.NewPrometheusProvider()
    metrics.SetProvider(metricsProvider)

    // Create router
    router := mux.NewRouter()

    // Apply middleware
    rateLimiter := middleware.NewRateLimiter(100, 20)
    sizeLimiter := middleware.NewRequestSizeLimiter(middleware.Size10MB)
    sanitizer := middleware.DefaultSanitizer()

    router.Use(rateLimiter.Middleware)
    router.Use(sizeLimiter.Middleware)
    router.Use(sanitizer.Middleware)
    router.Use(metricsProvider.Middleware)

    // API routes
    router.HandleFunc("/api/data", dataHandler)

    // Create graceful server
    srv := server.NewGracefulServer(server.Config{
        Addr:            ":8080",
        Handler:         router,
        ShutdownTimeout: 30 * time.Second,
        DrainTimeout:    25 * time.Second,
    })

    // Health checks
    router.HandleFunc("/health", srv.HealthCheckHandler())
    router.HandleFunc("/ready", srv.ReadinessHandler())

    // Metrics endpoint
    router.Handle("/metrics", metricsProvider.Handler())

    // Register shutdown callbacks
    server.RegisterShutdownCallback(func(ctx context.Context) error {
        log.Println("Cleanup: Flushing metrics...")
        return nil
    })

    server.RegisterShutdownCallback(func(ctx context.Context) error {
        log.Println("Cleanup: Closing database...")
        // return db.Close()
        return nil
    })

    // Start server (blocks until shutdown)
    log.Printf("Starting server on :8080")
    if err := srv.ListenAndServe(); err != nil {
        log.Fatal(err)
    }

    // Wait for shutdown to complete
    srv.Wait()
    log.Println("Server stopped")
}

func dataHandler(w http.ResponseWriter, r *http.Request) {
    // Your handler logic
    time.Sleep(100 * time.Millisecond) // Simulate work
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"message":"success"}`))
}

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 - is app running?
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 5

        # Readiness probe - can app handle traffic?
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 3

        # Graceful shutdown
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 5"]

        # Environment
        env:
        - name: SHUTDOWN_TIMEOUT
          value: "30"
Service
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer

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
      start_period: 10s
    stop_grace_period: 35s  # Slightly longer than shutdown timeout

Testing Graceful Shutdown

Test Script
#!/bin/bash

# Start server in background
./myapp &
SERVER_PID=$!

# Wait for server to start
sleep 2

# Send some 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 to send more requests (should get 503)
curl -v http://localhost:8080/api/data

# Wait for server to stop
wait $SERVER_PID
echo "Server stopped gracefully"
Expected Output
Starting server on :8080
Received signal: terminated, initiating graceful shutdown
Starting graceful shutdown...
Waiting for 8 in-flight requests to complete...
Waiting for 4 in-flight requests to complete...
Waiting for 1 in-flight requests to complete...
All requests drained in 2.3s
Cleanup: Flushing metrics...
Cleanup: Closing database...
Shutting down HTTP server...
Graceful shutdown complete
Server stopped

Monitoring In-Flight Requests

// Get current in-flight count
count := srv.InFlightRequests()
fmt.Printf("In-flight requests: %d\n", count)

// Check if shutting down
if srv.IsShuttingDown() {
    fmt.Println("Server is shutting down")
}

Advanced Usage

Custom Shutdown Logic
// Implement custom shutdown
go func() {
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)

    <-sigChan
    log.Println("Shutdown signal received")

    // Custom pre-shutdown logic
    log.Println("Running custom cleanup...")

    // Shutdown with callbacks
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := srv.ShutdownWithCallbacks(ctx); err != nil {
        log.Printf("Shutdown error: %v", err)
    }
}()

// Start server
srv.server.ListenAndServe()
Multiple Servers
// HTTP server
httpSrv := server.NewGracefulServer(server.Config{
    Addr:    ":8080",
    Handler: httpRouter,
})

// HTTPS server
httpsSrv := server.NewGracefulServer(server.Config{
    Addr:    ":8443",
    Handler: httpsRouter,
})

// Start both
go httpSrv.ListenAndServe()
go httpsSrv.ListenAndServe()

// Shutdown both on signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)
<-sigChan

ctx := context.Background()
httpSrv.Shutdown(ctx)
httpsSrv.Shutdown(ctx)

Best Practices

  1. Set appropriate timeouts

    • DrainTimeout < ShutdownTimeout
    • ShutdownTimeout < Kubernetes terminationGracePeriodSeconds
  2. Register cleanup 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. 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
Requests Still Timing Out
// Increase write timeout
config.WriteTimeout = 30 * time.Second
Force Shutdown Not Working

The server will force shutdown after ShutdownTimeout even if requests are still in-flight. Adjust timeouts as needed.

Debugging Shutdown
// Enable debug logging
import "github.com/bitechdev/ResolveSpec/pkg/logger"

logger.SetLevel("debug")

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterShutdownCallback

func RegisterShutdownCallback(cb ShutdownCallback)

RegisterShutdownCallback registers a callback to be called during shutdown Useful for cleanup tasks like closing database connections, flushing metrics, etc.

Types

type Config

type Config struct {
	// Addr is the server address (e.g., ":8080")
	Addr string

	// Handler is the HTTP handler
	Handler http.Handler

	// ShutdownTimeout is the maximum time to wait for graceful shutdown
	// Default: 30 seconds
	ShutdownTimeout time.Duration

	// DrainTimeout is the time to wait for in-flight requests to complete
	// before forcing shutdown. Default: 25 seconds
	DrainTimeout time.Duration

	// ReadTimeout is the maximum duration for reading the entire request
	ReadTimeout time.Duration

	// WriteTimeout is the maximum duration before timing out writes of the response
	WriteTimeout time.Duration

	// IdleTimeout is the maximum amount of time to wait for the next request
	IdleTimeout time.Duration
}

Config holds configuration for the graceful server

type GracefulServer

type GracefulServer struct {
	// contains filtered or unexported fields
}

GracefulServer wraps http.Server with graceful shutdown capabilities

func NewGracefulServer

func NewGracefulServer(config Config) *GracefulServer

NewGracefulServer creates a new graceful server

func (*GracefulServer) HealthCheckHandler

func (gs *GracefulServer) HealthCheckHandler() http.HandlerFunc

HealthCheckHandler returns a handler that responds to health checks Returns 200 OK when healthy, 503 Service Unavailable when shutting down

func (*GracefulServer) InFlightRequests

func (gs *GracefulServer) InFlightRequests() int64

InFlightRequests returns the current number of in-flight requests

func (*GracefulServer) IsShuttingDown

func (gs *GracefulServer) IsShuttingDown() bool

IsShuttingDown returns true if the server is shutting down

func (*GracefulServer) ListenAndServe

func (gs *GracefulServer) ListenAndServe() error

ListenAndServe starts the server and handles graceful shutdown

func (*GracefulServer) ReadinessHandler

func (gs *GracefulServer) ReadinessHandler() http.HandlerFunc

ReadinessHandler returns a handler for readiness checks Includes in-flight request count

func (*GracefulServer) Shutdown

func (gs *GracefulServer) Shutdown(ctx context.Context) error

Shutdown performs graceful shutdown with request draining

func (*GracefulServer) ShutdownWithCallbacks

func (gs *GracefulServer) ShutdownWithCallbacks(ctx context.Context) error

ShutdownWithCallbacks performs shutdown and executes all registered callbacks

func (*GracefulServer) TrackRequestsMiddleware

func (gs *GracefulServer) TrackRequestsMiddleware(next http.Handler) http.Handler

TrackRequestsMiddleware tracks in-flight requests and blocks new requests during shutdown

func (*GracefulServer) Wait

func (gs *GracefulServer) Wait()

Wait blocks until shutdown is complete

type ShutdownCallback

type ShutdownCallback func(context.Context) error

ShutdownCallback is a function called during shutdown

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL