/* * MIT License * * Copyright (c) 2025 Nicolas JUHEL * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * */ // Package httpserver provides production-grade HTTP/HTTPS server management with comprehensive // lifecycle control, configuration validation, TLS support, and integrated monitoring. // // # Overview // // This package offers a robust abstraction layer for managing HTTP and HTTPS servers in Go // applications, emphasizing production readiness through comprehensive lifecycle management, // declarative configuration with validation, optional TLS/SSL support, handler management, // and built-in monitoring capabilities. // // The package is designed for scenarios requiring multiple server instances, dynamic // configuration updates, graceful shutdowns, and centralized management through the pool // subpackage. It extends the standard library's http.Server with additional features while // maintaining compatibility with existing http.Handler implementations. // // # Design Philosophy // // The httpserver package follows these core design principles: // // 1. Configuration-Driven Architecture: Servers are defined through declarative configuration // structures that are validated before use, enabling early detection of configuration errors // and supporting externalized configuration from files, environment variables, or databases. // // 2. Lifecycle Management: Complete control over server start, stop, and restart operations // with context-aware cancellation and graceful shutdown support. Servers can be started, // stopped, and restarted programmatically with proper resource cleanup. // // 3. Thread-Safe Operations: All operations use atomic values and proper synchronization // primitives, ensuring safe concurrent access from multiple goroutines without external // locking requirements. // // 4. Production-Ready Features: Built-in monitoring integration, structured logging with // field context, error handling with typed error codes, port conflict detection, and health // checking capabilities. // // 5. Composable Design: The pool subpackage enables orchestration of multiple server instances // as a unified entity, supporting filtering, batch operations, and aggregated monitoring. // // # Key Features // // Complete Lifecycle Control: // - Context-aware Start, Stop, and Restart operations // - Automatic resource cleanup and graceful shutdown // - Uptime tracking and running state queries // - Error tracking and recovery mechanisms // // Configuration Management: // - Declarative configuration with struct tag validation // - Deep cloning for safe configuration copies // - Dynamic configuration updates with SetConfig // - TLS/HTTPS configuration with certificate management // // Handler Management: // - Dynamic handler registration via function callbacks // - Multiple named handlers per server instance // - Handler key-based routing for multi-handler scenarios // - Fallback BadHandler for misconfigured servers // // TLS/HTTPS Support: // - Integrated certificate management // - Optional and mandatory TLS modes // - Default TLS configuration inheritance // - Automatic TLS detection and configuration // // Monitoring and Health Checking: // - Built-in health check endpoints // - Integration with monitor package // - Server metrics and status information // - Unique monitoring identifiers per server // // Pool Management (via pool subpackage): // - Coordinate multiple server instances // - Unified start/stop/restart operations // - Advanced filtering by name, address, or URL // - Configuration-based pool creation // // Thread-Safe Operations: // - Atomic value storage for handlers and state // - Concurrent access without explicit locking // - Safe handler replacement during operation // - Context-based synchronization // // Port Management: // - Automatic port availability checking // - Conflict detection before binding // - Retry logic for port conflicts // - Support for all interface binding (0.0.0.0) // // # Architecture // // The package consists of three main components working together: // // ┌────────────────────────────────────┐ // │ Application Layer │ // │ (HTTP Handlers & Routes) │ // └─────────────────┬──────────────────┘ // │ // ┌─────────▼─────────┐ // │ httpserver │ // │ Package API │ // └─────────┬─────────┘ // │ // ┌─────────────┼──────────────┐ // │ │ │ // ┌───▼───┐ ┌────▼────┐ ┌───▼────┐ // │Server │ │ Pool │ │ Types │ // │ │ │ │ │ │ // │Config │◄───┤ Manager │ │Handler │ // │Run │ │ Filter │ │Fields │ // │Monitor│ │ Clone │ │Const │ // └───┬───┘ └────┬────┘ └────────┘ // │ │ // └──────┬──────┘ // │ // ┌──────▼──────┐ // │ Go stdlib │ // │ http.Server │ // └─────────────┘ // // Component Responsibilities: // // - Server: Core HTTP server implementation with lifecycle management // - Config: Declarative configuration with validation and cloning // - Pool: Multi-server orchestration and batch operations // - Types: Shared type definitions and constants // - Monitor: Health checking and metrics collection // - Handler: Dynamic handler registration and management // // Data Flow: // 1. Configuration is created and validated // 2. Handlers are registered via function callbacks // 3. Server instance is created from configuration // 4. Start operation binds to port and begins serving // 5. Handlers process incoming HTTP requests // 6. Monitoring tracks health and metrics // 7. Stop operation gracefully shuts down with timeout // // # Thread Safety Architecture // // The package uses multiple synchronization mechanisms for thread safety: // // Component | Mechanism | Concurrency Model // --------------------|---------------------|---------------------------------- // Server State | atomic.Value | Lock-free reads, atomic writes // Handler Registry | atomic.Value | Lock-free handler swapping // Logger | atomic.Value | Thread-safe logging // Runner | atomic.Value | Lifecycle synchronization // Config Storage | context.Config | Context-based atomic storage // Pool Map | sync.RWMutex | Multiple readers, exclusive writes // // All public methods are safe for concurrent use from multiple goroutines without // external synchronization. Internal state updates use atomic operations to prevent // data races. // // # Basic Usage // // Creating and starting a simple HTTP server: // // cfg := httpserver.Config{ // Name: "api-server", // Listen: "127.0.0.1:8080", // Expose: "http://localhost:8080", // } // // cfg.RegisterHandlerFunc(func() map[string]http.Handler { // mux := http.NewServeMux() // mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { // w.WriteHeader(http.StatusOK) // w.Write([]byte("OK")) // }) // return map[string]http.Handler{"": mux} // }) // // if err := cfg.Validate(); err != nil { // log.Fatal(err) // } // // srv, err := httpserver.New(cfg, nil) // if err != nil { // log.Fatal(err) // } // // ctx := context.Background() // if err := srv.Start(ctx); err != nil { // log.Fatal(err) // } // defer srv.Stop(ctx) // // # Configuration // // The Config structure defines all server parameters: // // type Config struct { // Name string // Server identifier (required) // Listen string // Bind address host:port (required) // Expose string // Public URL (required) // HandlerKey string // Handler map key (optional) // Disabled bool // Disable flag for maintenance // Monitor moncfg.Config // Monitoring configuration // TLSMandatory bool // Require valid TLS // TLS libtls.Config // TLS/certificate configuration // ReadTimeout libdur.Duration // Request read timeout // ReadHeaderTimeout libdur.Duration // Header read timeout // WriteTimeout libdur.Duration // Response write timeout // MaxHeaderBytes int // Maximum header size // // ... HTTP/2 configuration fields ... // } // // Configuration Validation: // // All configurations must pass validation before server creation: // - Name: Must be non-empty string // - Listen: Must be valid hostname:port format // - Expose: Must be valid URL with scheme // - TLS: Must be valid if TLSMandatory is true // // Configuration Methods: // - Validate(): Comprehensive validation with detailed errors // - Clone(): Deep copy of configuration // - RegisterHandlerFunc(): Set handler function // - SetDefaultTLS(): Set default TLS provider // - SetContext(): Set parent context provider // - Server(): Create server instance from config // // # Handler Management // // Handlers are registered via callback functions returning handler maps: // // handlerFunc := func() map[string]http.Handler { // return map[string]http.Handler{ // "": defaultHandler, // Default handler // "api": apiHandler, // Named handler for API // "admin": adminHandler, // Named handler for admin // } // } // // cfg.RegisterHandlerFunc(handlerFunc) // // Handler Keys: // // The HandlerKey configuration field selects which handler from the map to use: // - Empty string or "": Uses default handler // - "api": Uses handler registered with "api" key // - Custom keys: Application-specific handler selection // // Multiple servers can share the same handler function but use different keys // to serve different handlers on different ports. // // # TLS/HTTPS Configuration // // Servers support optional and mandatory TLS/SSL: // // cfg := httpserver.Config{ // Name: "secure-api", // Listen: "0.0.0.0:8443", // Expose: "https://api.example.com", // TLSMandatory: true, // TLS: libtls.Config{ // CertPEM: "/path/to/cert.pem", // KeyPEM: "/path/to/key.pem", // // Additional TLS options... // }, // } // // TLS Modes: // // - TLSMandatory = false: TLS is optional, server starts without certificates // - TLSMandatory = true: Valid TLS config required, server fails to start without it // // TLS Configuration Inheritance: // // Servers can inherit from a default TLS configuration: // // cfg.SetDefaultTLS(func() libtls.TLSConfig { // return defaultTLSConfig // }) // cfg.TLS.InheritDefault = true // // # Lifecycle Management // // Servers implement the libsrv.Runner interface for lifecycle control: // // // Start server // err := srv.Start(ctx) // if err != nil { // log.Fatal(err) // } // // // Check if running // if srv.IsRunning() { // log.Println("Server is running") // } // // // Get uptime // uptime := srv.Uptime() // log.Printf("Server uptime: %v", uptime) // // // Stop gracefully // ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // defer cancel() // err = srv.Stop(ctx) // // // Restart (stop then start) // err = srv.Restart(ctx) // // Graceful Shutdown: // // The Stop method performs graceful shutdown: // 1. Stops accepting new connections // 2. Waits for active requests to complete (up to timeout) // 3. Closes server resources and listeners // 4. Returns error if shutdown exceeds timeout // // # Server Information // // Servers provide read-only access to configuration and state: // // name := srv.GetName() // Server identifier // bind := srv.GetBindable() // Listen address // expose := srv.GetExpose() // Public URL // disabled := srv.IsDisable() // Disabled flag // hasTLS := srv.IsTLS() // TLS enabled check // cfg := srv.GetConfig() // Full configuration // // # Monitoring and Health Checking // // Servers integrate with the monitor package for health checks and metrics: // // // Get monitoring identifier // monitorName := srv.MonitorName() // // // Get monitor with health checks and metrics // monitor, err := srv.Monitor(versionInfo) // if err != nil { // log.Printf("Monitor error: %v", err) // } // // Health checks verify: // - Server is running (runner state check) // - Port is bound and accepting connections // - No fatal errors in server operation // - TCP connection can be established to bind address // // Monitor provides: // - Health check status and last error // - Server configuration and runtime information // - Uptime and running state // - Custom metrics from monitoring configuration // // # Port Management // // The package includes port conflict detection and resolution: // // // Check if port is available // err := httpserver.PortNotUse(ctx, "127.0.0.1:8080") // if err == nil { // // Port is available // } // // // Check if port is in use // err = httpserver.PortInUse(ctx, "127.0.0.1:8080") // if err == nil { // // Port is in use // } // // Automatic Port Conflict Handling: // // Servers automatically check for port conflicts before binding: // - Retries up to 5 times with delays // - Returns ErrorPortUse if port remains unavailable // - Configurable retry count via RunIfPortInUse // // # Error Handling // // The package defines typed errors with diagnostic codes: // // Error Code | Description // -----------------------|------------------------------------------ // ErrorParamEmpty | Required parameter missing // ErrorInvalidInstance | Invalid server instance // ErrorHTTP2Configure | HTTP/2 configuration failed // ErrorServerValidate | Configuration validation failed // ErrorServerStart | Server failed to start or listen // ErrorInvalidAddress | Bind address format invalid // ErrorPortUse | Port is already in use // // Error checking: // // if err := srv.Start(ctx); err != nil { // var liberr errors.Error // if errors.As(err, &liberr) { // switch liberr.Code() { // case httpserver.ErrorPortUse: // log.Println("Port already in use") // case httpserver.ErrorServerValidate: // log.Println("Invalid configuration") // default: // log.Printf("Server error: %v", err) // } // } // } // // # Performance Characteristics // // Server Operations: // // Operation | Time | Memory | Notes // ------------------------|--------------|-----------|------------------------ // Config Validation | ~100ns | O(1) | Field validation only // Server Creation | <1ms | ~5KB | Includes initialization // Start Server | 1-5ms | ~10KB | Port binding overhead // Stop Server (graceful) | <5s | O(1) | Default timeout // Handler Execution | Variable | Variable | Depends on handler // Info Methods | <100ns | O(1) | Atomic reads // Configuration Update | <1ms | O(1) | Validation + atomic swap // // Throughput: // - HTTP Requests: Limited by Go's http.Server (~50k+ req/s typical) // - HTTPS/TLS: ~20-30k req/s depending on cipher suite and hardware // - Overhead: Minimal (<1% vs standard http.Server) // // Memory Usage: // - Single Server: ~10-15KB baseline + handler memory // - With Monitoring: +5KB per server // - Pool Overhead: ~1KB per server in pool // // Scalability: // - Supports hundreds of server instances per process // - Linear memory growth with server count // - No global locks on request path // // # Use Cases // // ## Microservices Architecture // // Run multiple API versions simultaneously: // // // API v1 on port 8080 // cfgV1 := httpserver.Config{ // Name: "api-v1", // Listen: "0.0.0.0:8080", // Expose: "http://api.example.com/v1", // HandlerKey: "v1", // } // // // API v2 on port 8081 // cfgV2 := httpserver.Config{ // Name: "api-v2", // Listen: "0.0.0.0:8081", // Expose: "http://api.example.com/v2", // HandlerKey: "v2", // } // // ## Multi-Tenant Systems // // Dedicated server per tenant with isolated configuration: // // for _, tenant := range tenants { // cfg := httpserver.Config{ // Name: tenant.ID, // Listen: fmt.Sprintf("0.0.0.0:%d", tenant.Port), // Expose: tenant.Domain, // TLS: tenant.TLSConfig, // } // srv, _ := httpserver.New(cfg, nil) // srv.Start(ctx) // } // // ## Development and Testing // // Dynamic server creation for integration tests: // // func TestAPI(t *testing.T) { // port := getFreePort() // cfg := httpserver.Config{ // Name: "test-server", // Listen: fmt.Sprintf("127.0.0.1:%d", port), // Expose: fmt.Sprintf("http://localhost:%d", port), // } // cfg.RegisterHandlerFunc(testHandler) // srv, _ := httpserver.New(cfg, nil) // srv.Start(context.Background()) // defer srv.Stop(context.Background()) // // Run tests... // } // // ## API Gateway // // Route traffic to multiple backend servers: // // pool := pool.New(nil, gatewayHandler) // for _, backend := range backends { // cfg := httpserver.Config{ // Name: backend.Name, // Listen: backend.Address, // Expose: backend.URL, // } // pool.StoreNew(cfg, nil) // } // pool.Start(ctx) // // ## Production Deployments // // Graceful shutdown during rolling updates: // // // Handle SIGTERM for graceful shutdown // sigChan := make(chan os.Signal, 1) // signal.Notify(sigChan, syscall.SIGTERM) // go func() { // <-sigChan // ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // defer cancel() // if err := srv.Stop(ctx); err != nil { // log.Printf("Shutdown error: %v", err) // } // }() // // # Limitations // // 1. Handler Replacement During Operation: Handlers can be replaced while the server // is running, but active requests continue using the previous handler. New requests // use the updated handler. This may cause inconsistent behavior during handler updates. // // 2. TLS Certificate Rotation: Updating TLS certificates requires server restart. // The SetConfig method does not apply TLS changes to running servers. // // 3. Port Binding Limitations: Cannot bind to privileged ports (<1024) without // appropriate system permissions (CAP_NET_BIND_SERVICE on Linux or root access). // // 4. Listen Address Changes: Changing the Listen address via SetConfig requires // a server restart to take effect. The new address is not applied to running servers. // // 5. HTTP/2 Configuration: HTTP/2 settings cannot be changed after server creation // without recreating the server instance. // // 6. Concurrent Stop Calls: Multiple concurrent Stop calls may result in redundant // shutdown attempts. Use external synchronization if multiple goroutines may call Stop. // // 7. Context Cancellation: Server operations respect context cancellation, but // forcefully cancelled contexts (e.g., exceeded deadline during graceful shutdown) // may result in abrupt connection termination. // // 8. Monitoring Overhead: Each server maintains monitoring state. For deployments // with hundreds of servers, consider disabling monitoring for non-critical instances. // // # Best Practices // // DO: // - Always validate configuration before creating servers // - Use context.WithTimeout for Stop operations to prevent hanging // - Register handlers before calling New() when possible // - Use defer srv.Stop(ctx) to ensure cleanup // - Check IsRunning() before calling Stop to avoid errors // - Set appropriate timeouts (ReadTimeout, WriteTimeout, IdleTimeout) // - Use the pool subpackage for managing multiple servers // - Monitor server health in production deployments // - Use unique server names for debugging and monitoring // // DON'T: // - Don't create servers without validating configuration // - Don't ignore errors from Start, Stop, or SetConfig // - Don't assume port availability without checking // - Don't call Stop without context timeout // - Don't modify Config after passing to New() (use Clone first) // - Don't rely on handler updates to active connections // - Don't bind to 0.0.0.0 without considering security implications // - Don't start servers in goroutines without proper error handling // // # Integration with Standard Library // // The package integrates seamlessly with Go's standard library: // // Standard Library | Integration Point // -------------------------|------------------------------------------ // net/http.Server | Wrapped and managed by this package // net/http.Handler | Compatible with all standard handlers // context.Context | Used throughout for cancellation // net.Listener | Managed internally for binding // crypto/tls | TLS configuration via certificates package // // # Related Packages // // This package works with other golib packages: // // - github.com/nabbar/golib/httpserver/pool: Multi-server orchestration // - github.com/nabbar/golib/httpserver/types: Shared type definitions // - github.com/nabbar/golib/certificates: TLS/SSL certificate management // - github.com/nabbar/golib/monitor: Health checking and metrics // - github.com/nabbar/golib/logger: Structured logging integration // - github.com/nabbar/golib/runner: Lifecycle management primitives // - github.com/nabbar/golib/context: Typed context storage // // External Dependencies: // // - github.com/go-playground/validator/v10: Configuration validation // - golang.org/x/net/http2: HTTP/2 support // // # Testing // // The package includes comprehensive testing using Ginkgo v2 and Gomega: // // - Configuration validation tests // - Server lifecycle tests (start, stop, restart) // - Handler management tests // - Monitoring integration tests // - Concurrency and race detection tests // - Integration tests with actual HTTP servers // // Run tests: // // go test -v ./... // go test -race -v ./... // go test -cover -v ./... // // For detailed testing documentation, see TESTING.md in the package directory. // // # Examples // // See example_test.go for comprehensive usage examples covering: // - Basic HTTP server creation and lifecycle // - HTTPS server with TLS configuration // - Multiple handlers with handler keys // - Server pool management // - Dynamic configuration updates // - Graceful shutdown patterns // - Monitoring integration // - Error handling strategies package httpserver