Documentation
¶
Overview ¶
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:
- Configuration is created and validated
- Handlers are registered via function callbacks
- Server instance is created from configuration
- Start operation binds to port and begins serving
- Handlers process incoming HTTP requests
- Monitoring tracks health and metrics
- 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:
- Stops accepting new connections
- Waits for active requests to complete (up to timeout)
- Closes server resources and listeners
- 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
Example (BasicServer) ¶
Example_basicServer demonstrates creating and configuring a basic HTTP server. Shows the essential steps: configuration, validation, and creation.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "basic-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)
})
return map[string]http.Handler{"": mux}
})
if err := cfg.Validate(); err != nil {
fmt.Printf("Validation failed: %v\n", err)
return
}
srv, err := httpserver.New(cfg, nil)
if err != nil {
fmt.Printf("Creation failed: %v\n", err)
return
}
fmt.Printf("Server: %s on %s\n", srv.GetName(), srv.GetBindable())
}
Output: Server: basic-server on 127.0.0.1:8080
Example (Complete) ¶
Example_complete demonstrates a complete server setup workflow. Shows a realistic scenario with all components.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "complete-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
})
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
return map[string]http.Handler{"": mux}
})
if err := cfg.Validate(); err != nil {
fmt.Printf("Validation error: %v\n", err)
return
}
srv, err := httpserver.New(cfg, nil)
if err != nil {
fmt.Printf("Creation error: %v\n", err)
return
}
fmt.Printf("Server: %s\n", srv.GetName())
fmt.Printf("Binding: %s\n", srv.GetBindable())
fmt.Printf("Expose: %s\n", srv.GetExpose())
fmt.Printf("Ready: %t\n", !srv.IsDisable())
}
Output: Server: complete-server Binding: 127.0.0.1:8080 Expose: localhost:8080 Ready: true
Example (ConfigClone) ¶
Example_configClone demonstrates configuration cloning. Shows how to create independent configuration copies.
package main
import (
"fmt"
"github.com/nabbar/golib/httpserver"
)
func main() {
original := httpserver.Config{
Name: "original",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cloned := original.Clone()
cloned.Name = "cloned"
fmt.Printf("Original: %s\n", original.Name)
fmt.Printf("Cloned: %s\n", cloned.Name)
}
Output: Original: original Cloned: cloned
Example (ConfigValidation) ¶
Example_configValidation demonstrates configuration validation. Shows how validation catches configuration errors early.
package main
import (
"fmt"
"github.com/nabbar/golib/httpserver"
)
func main() {
validCfg := httpserver.Config{
Name: "valid-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
if err := validCfg.Validate(); err != nil {
fmt.Println("Valid config failed")
} else {
fmt.Println("Valid config passed")
}
invalidCfg := httpserver.Config{
Name: "invalid-server",
// Missing Listen and Expose
}
if err := invalidCfg.Validate(); err != nil {
fmt.Println("Invalid config failed as expected")
} else {
fmt.Println("Invalid config unexpectedly passed")
}
}
Output: Valid config passed Invalid config failed as expected
Example (DisabledServer) ¶
Example_disabledServer demonstrates the disabled flag. Shows how to create a server that won't start.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "disabled-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
Disabled: true,
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv, _ := httpserver.New(cfg, nil)
fmt.Printf("Server disabled: %t\n", srv.IsDisable())
}
Output: Server disabled: true
Example (DynamicHandler) ¶
Example_dynamicHandler demonstrates dynamic handler replacement. Shows how to update handlers after server creation.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "dynamic-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv, _ := httpserver.New(cfg, nil)
newHandler := func() map[string]http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/new", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("New Handler"))
})
return map[string]http.Handler{"": mux}
}
srv.Handler(newHandler)
fmt.Println("Handler updated dynamically")
}
Output: Handler updated dynamically
Example (GetConfig) ¶
Example_getConfig demonstrates retrieving server configuration. Shows how to access the current configuration.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "config-test",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv, _ := httpserver.New(cfg, nil)
retrievedCfg := srv.GetConfig()
fmt.Printf("Name: %s\n", retrievedCfg.Name)
fmt.Printf("Listen: %s\n", retrievedCfg.Listen)
}
Output: Name: config-test Listen: 127.0.0.1:8080
Example (GracefulPattern) ¶
Example_gracefulPattern demonstrates a graceful shutdown pattern. Shows the recommended way to handle server lifecycle.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "graceful-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv, err := httpserver.New(cfg, nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// In real code, you would call:
// ctx := context.Background()
// err = srv.Start(ctx)
// defer srv.Stop(ctx)
fmt.Printf("Server ready for lifecycle: %s\n", srv.GetName())
}
Output: Server ready for lifecycle: graceful-server
Example (HandlerRegistration) ¶
Example_handlerRegistration demonstrates handler registration. Shows how to register custom HTTP handlers.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "handler-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
})
return map[string]http.Handler{"": mux}
})
srv, err := httpserver.New(cfg, nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Handler registered for %s\n", srv.GetName())
}
Output: Handler registered for handler-server
Example (HttpVersions) ¶
Example_httpVersions demonstrates HTTP and HTTPS configuration. Shows both HTTP and HTTPS expose URLs.
package main
import (
"fmt"
"github.com/nabbar/golib/httpserver"
)
func main() {
httpCfg := httpserver.Config{
Name: "http-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
httpsCfg := httpserver.Config{
Name: "https-server",
Listen: "127.0.0.1:8443",
Expose: "https://localhost:8443",
}
fmt.Printf("HTTP valid: %v\n", httpCfg.Validate() == nil)
fmt.Printf("HTTPS valid: %v\n", httpsCfg.Validate() == nil)
}
Output: HTTP valid: true HTTPS valid: true
Example (LifecycleState) ¶
Example_lifecycleState demonstrates checking server state. Shows how to query if a server is running.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "state-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv, _ := httpserver.New(cfg, nil)
fmt.Printf("Running initially: %t\n", srv.IsRunning())
// Note: Not actually starting to keep test simple
Example (MonitorName) ¶
Example_monitorName demonstrates monitoring identifier. Shows how to get the unique monitoring name.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "monitor-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv, _ := httpserver.New(cfg, nil)
monitorName := srv.MonitorName()
fmt.Printf("Monitor name contains bind: %t\n", len(monitorName) > 0)
}
Output: Monitor name contains bind: true
Example (MultipleHandlers) ¶
Example_multipleHandlers demonstrates multiple named handlers. Shows how to use handler keys for different handlers.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
handlerFunc := func() map[string]http.Handler {
return map[string]http.Handler{
"api": http.NotFoundHandler(),
"admin": http.NotFoundHandler(),
"web": http.NotFoundHandler(),
}
}
apiCfg := httpserver.Config{
Name: "api-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
HandlerKey: "api",
}
apiCfg.RegisterHandlerFunc(handlerFunc)
adminCfg := httpserver.Config{
Name: "admin-server",
Listen: "127.0.0.1:8081",
Expose: "http://localhost:8081",
HandlerKey: "admin",
}
adminCfg.RegisterHandlerFunc(handlerFunc)
apiSrv, _ := httpserver.New(apiCfg, nil)
adminSrv, _ := httpserver.New(adminCfg, nil)
fmt.Printf("API server: %s\n", apiSrv.GetName())
fmt.Printf("Admin server: %s\n", adminSrv.GetName())
}
Output: API server: api-server Admin server: admin-server
Example (PortBinding) ¶
Example_portBinding demonstrates different bind address formats. Shows various ways to specify the listen address.
package main
import (
"fmt"
"github.com/nabbar/golib/httpserver"
)
func main() {
configs := []httpserver.Config{
{
Name: "localhost",
Listen: "localhost:8080",
Expose: "http://localhost:8080",
},
{
Name: "specific-ip",
Listen: "192.168.1.100:8080",
Expose: "http://192.168.1.100:8080",
},
{
Name: "all-interfaces",
Listen: "0.0.0.0:8080",
Expose: "http://localhost:8080",
},
}
for _, cfg := range configs {
if err := cfg.Validate(); err != nil {
fmt.Printf("%s: invalid\n", cfg.Name)
} else {
fmt.Printf("%s: valid\n", cfg.Name)
}
}
}
Output: localhost: valid specific-ip: valid all-interfaces: valid
Example (ServerFromConfig) ¶
Example_serverFromConfig demonstrates creating server from config method. Shows the convenience method on Config.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "convenience-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv, err := cfg.Server(nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Created via config method: %s\n", srv.GetName())
}
Output: Created via config method: convenience-server
Example (ServerInfo) ¶
Example_serverInfo demonstrates accessing server information. Shows how to retrieve server properties after creation.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "info-server",
Listen: "127.0.0.1:9000",
Expose: "http://localhost:9000",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv, _ := httpserver.New(cfg, nil)
fmt.Printf("Name: %s\n", srv.GetName())
fmt.Printf("Bind: %s\n", srv.GetBindable())
fmt.Printf("Expose: %s\n", srv.GetExpose())
fmt.Printf("Disabled: %t\n", srv.IsDisable())
fmt.Printf("TLS: %t\n", srv.IsTLS())
}
Output: Name: info-server Bind: 127.0.0.1:9000 Expose: localhost:9000 Disabled: false TLS: false
Example (ServerMerge) ¶
Example_serverMerge demonstrates merging server configurations. Shows how to update one server with another's configuration.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg1 := httpserver.Config{
Name: "server1",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg1.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
cfg2 := httpserver.Config{
Name: "server2",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg2.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv1, _ := httpserver.New(cfg1, nil)
srv2, _ := httpserver.New(cfg2, nil)
fmt.Printf("Before merge: %s\n", srv1.GetName())
srv1.Merge(srv2, nil)
fmt.Printf("After merge: %s\n", srv1.GetName())
}
Output: Before merge: server1 After merge: server2
Example (SetConfig) ¶
Example_setConfig demonstrates updating server configuration. Shows how to change server settings after creation.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
originalCfg := httpserver.Config{
Name: "original-name",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
originalCfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv, _ := httpserver.New(originalCfg, nil)
fmt.Printf("Initial: %s\n", srv.GetName())
updatedCfg := httpserver.Config{
Name: "updated-name",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
updatedCfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
srv.SetConfig(updatedCfg, nil)
fmt.Printf("Updated: %s\n", srv.GetName())
}
Output: Initial: original-name Updated: updated-name
Index ¶
- Constants
- func PortInUse(ctx context.Context, listen string) liberr.Error
- func PortNotUse(ctx context.Context, listen string) error
- type Config
- func (c *Config) CheckTLS() (libtls.TLSConfig, error)
- func (c *Config) Clone() Config
- func (c *Config) GetExpose() *url.URL
- func (c *Config) GetHandlerKey() string
- func (c *Config) GetListen() *url.URL
- func (c *Config) GetTLS() libtls.TLSConfig
- func (c *Config) IsTLS() bool
- func (c *Config) RegisterHandlerFunc(hdl srvtps.FuncHandler)
- func (c *Config) Server(defLog liblog.FuncLog) (Server, error)
- func (c *Config) SetContext(f context.Context)
- func (c *Config) SetDefaultTLS(f libtls.FctTLSDefault)
- func (c *Config) Validate() error
- type Info
- type Server
Examples ¶
- Package (BasicServer)
- Package (Complete)
- Package (ConfigClone)
- Package (ConfigValidation)
- Package (DisabledServer)
- Package (DynamicHandler)
- Package (GetConfig)
- Package (GracefulPattern)
- Package (HandlerRegistration)
- Package (HttpVersions)
- Package (LifecycleState)
- Package (MonitorName)
- Package (MultipleHandlers)
- Package (PortBinding)
- Package (ServerFromConfig)
- Package (ServerInfo)
- Package (ServerMerge)
- Package (SetConfig)
- New
Constants ¶
const ( ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgHttpServer ErrorInvalidInstance ErrorHTTP2Configure ErrorServerValidate ErrorServerStart ErrorInvalidAddress ErrorPortUse )
const (
// DefaultNameMonitor is the default prefix for monitoring identifiers.
DefaultNameMonitor = "HTTP Server"
)
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Config ¶ added in v1.10.0
type Config struct {
// Name is the unique identifier for the server instance.
// Multiple servers can be configured, each identified by a unique name.
// If not defined, the listen address is used as the name.
Name string `mapstructure:"name" json:"name" yaml:"name" toml:"name" validate:"required"`
// Listen is the local bind address (host:port) for the server.
// The server will bind to this address and listen for incoming connections.
// Examples: "127.0.0.1:8080", "0.0.0.0:443", "localhost:3000"
Listen string `mapstructure:"listen" json:"listen" yaml:"listen" toml:"listen" validate:"required,hostname_port"`
// Expose is the public-facing URL used to access this server externally.
// This allows using a single domain with multiple servers on different ports.
// Examples: "http://localhost:8080", "https://api.example.com"
Expose string `mapstructure:"expose" json:"expose" yaml:"expose" toml:"expose" validate:"required,url"`
// HandlerKey associates this server with a specific handler from the handler map.
// This enables multiple servers to use different handlers from a shared registry,
// allowing different APIs to run on different ports with a single configuration.
HandlerKey string `mapstructure:"handler_key" json:"handler_key" yaml:"handler_key" toml:"handler_key"`
// Disabled allows disabling a server without removing its configuration.
// Useful for maintenance mode or gradual rollout scenarios.
Disabled bool `mapstructure:"disabled" json:"disabled" yaml:"disabled" toml:"disabled"`
// Monitor defines the monitoring configuration for health checks and metrics collection.
// Enables integration with the monitoring system for server health tracking.
Monitor moncfg.Config `mapstructure:"monitor" json:"monitor" yaml:"monitor" toml:"monitor"`
// TLSMandatory requires valid TLS configuration for the server to start.
// If true, the server will fail to start without proper TLS certificates.
TLSMandatory bool `mapstructure:"tls_mandatory" json:"tls_mandatory" yaml:"tls_mandatory" toml:"tls_mandatory"`
// TLS is the certificate configuration for HTTPS/TLS support.
// Set InheritDefault to true to inherit from default TLS config, or provide
// specific certificate paths. Leave empty to disable TLS for this server.
TLS libtls.Config `mapstructure:"tls" json:"tls" yaml:"tls" toml:"tls"`
// ReadTimeout is the maximum duration for reading the entire
// request, including the body.
//
// Because ReadTimeout does not let Handlers make per-request
// decisions on each request body's acceptable deadline or
// upload rate, most users will prefer to use
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout libdur.Duration `mapstructure:"read_timeout" json:"read_timeout" yaml:"read_timeout" toml:"read_timeout"`
// ReadHeaderTimeout is the amount of time allowed to read
// request headers. The connection's read deadline is reset
// after reading the headers and the Handler can decide what
// is considered too slow for the body. If ReadHeaderTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, there is no timeout.
ReadHeaderTimeout libdur.Duration `mapstructure:"read_header_timeout" json:"read_header_timeout" yaml:"read_header_timeout" toml:"read_header_timeout"`
// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
// let Handlers make decisions on a per-request basis.
WriteTimeout libdur.Duration `mapstructure:"write_timeout" json:"write_timeout" yaml:"write_timeout" toml:"write_timeout"`
// MaxHeaderBytes controls the maximum number of bytes the
// srv will read parsing the request header's keys and
// values, including the request line. It does not limit the
// size of the request body.
// If zero, DefaultMaxHeaderBytes is used.
MaxHeaderBytes int `mapstructure:"max_header_bytes" json:"max_header_bytes" yaml:"max_header_bytes" toml:"max_header_bytes"`
// MaxHandlers limits the number of http.Handler ServeHTTP goroutines
// which may run at a time over all connections.
// Negative or zero no limit.
MaxHandlers int `mapstructure:"max_handlers" json:"max_handlers" yaml:"max_handlers" toml:"max_handlers"`
// MaxConcurrentStreams optionally specifies the number of
// concurrent streams that each client may have open at a
// time. This is unrelated to the number of http.Handler goroutines
// which may be active globally, which is MaxHandlers.
// If zero, MaxConcurrentStreams defaults to at least 100, per
// the HTTP/2 spec's recommendations.
MaxConcurrentStreams uint32 `` /* 127-byte string literal not displayed */
// MaxReadFrameSize optionally specifies the largest frame
// this srv is willing to read. A valid value is between
// 16k and 16M, inclusive. If zero or otherwise invalid, a
// default value is used.
MaxReadFrameSize uint32 `mapstructure:"max_read_frame_size" json:"max_read_frame_size" yaml:"max_read_frame_size" toml:"max_read_frame_size"`
// PermitProhibitedCipherSuites, if true, permits the use of
// cipher suites prohibited by the HTTP/2 spec.
PermitProhibitedCipherSuites bool `` /* 163-byte string literal not displayed */
// IdleTimeout specifies how long until idle clients should be
// closed with a GOAWAY frame. PING frames are not considered
// activity for the purposes of IdleTimeout.
IdleTimeout libdur.Duration `mapstructure:"idle_timeout" json:"idle_timeout" yaml:"idle_timeout" toml:"idle_timeout"`
// MaxUploadBufferPerConnection is the size of the initial flow
// control window for each connections. The HTTP/2 spec does not
// allow this to be smaller than 65535 or larger than 2^32-1.
// If the value is outside this range, a default value will be
// used instead.
MaxUploadBufferPerConnection int32 `` /* 167-byte string literal not displayed */
// MaxUploadBufferPerStream is the size of the initial flow control
// window for each stream. The HTTP/2 spec does not allow this to
// be larger than 2^32-1. If the value is zero or larger than the
// maximum, a default value will be used instead.
MaxUploadBufferPerStream int32 `` /* 151-byte string literal not displayed */
// DisableKeepAlive controls whether HTTP keep-alives are disabled.
// By default, keep-alives are always enabled. Only very
// resource-constrained environments or servers in the process of
// shutting down should disable them.
DisableKeepAlive bool `mapstructure:"disable_keep_alive" json:"disable_keep_alive" yaml:"disable_keep_alive" toml:"disable_keep_alive"`
// Logger is used to define the logger options.
Logger logcfg.Options `mapstructure:"logger" json:"logger" yaml:"logger" toml:"logger"`
// contains filtered or unexported fields
}
Config defines the complete HTTP server configuration including network settings, TLS options, timeouts, and HTTP/2 parameters. All fields are serializable to various formats (JSON, YAML, TOML) for externalized configuration.
func (*Config) CheckTLS ¶ added in v1.10.0
CheckTLS validates the TLS configuration and returns it if valid. Returns an error if no certificates are defined.
func (*Config) Clone ¶ added in v1.10.0
Clone creates a deep copy of the Config structure. All fields are copied, including function pointers.
func (*Config) GetExpose ¶ added in v1.10.0
GetExpose parses and returns the expose address as a URL. Falls back to the listen address with appropriate scheme if not set.
func (*Config) GetHandlerKey ¶ added in v1.10.0
GetHandlerKey returns the handler key for this server configuration. Returns empty string if no specific key is set (uses default handler).
func (*Config) GetListen ¶ added in v1.10.0
GetListen parses and returns the listen address as a URL. Returns nil if the address is invalid.
func (*Config) GetTLS ¶ added in v1.10.0
GetTLS returns the TLS configuration for the server. If InheritDefault is true, it merges with the default TLS configuration.
func (*Config) IsTLS ¶ added in v1.10.0
IsTLS returns true if the server has a valid TLS configuration.
func (*Config) RegisterHandlerFunc ¶ added in v1.10.0
func (c *Config) RegisterHandlerFunc(hdl srvtps.FuncHandler)
RegisterHandlerFunc registers a handler function that provides HTTP handlers. The function should return a map of handler keys to http.Handler instances.
func (*Config) Server ¶ added in v1.10.0
Server creates a new HTTP server instance from this configuration. This is a convenience method that calls the New function.
func (*Config) SetContext ¶ added in v1.10.0
SetContext registers a function that provides the parent context for the server. The context is used for lifecycle management and cancellation.
func (*Config) SetDefaultTLS ¶ added in v1.10.0
func (c *Config) SetDefaultTLS(f libtls.FctTLSDefault)
SetDefaultTLS registers a function that provides default TLS configuration. This is used when TLS.InheritDefault is enabled.
type Info ¶ added in v1.10.0
type Info interface {
// GetName returns the unique identifier name of the server instance.
GetName() string
// GetBindable returns the local bind address (host:port) the server listens on.
GetBindable() string
// GetExpose returns the public-facing URL used to access this server externally.
GetExpose() string
// IsDisable returns true if the server is configured as disabled and should not start.
IsDisable() bool
// IsTLS returns true if the server is configured to use TLS/HTTPS.
IsTLS() bool
}
Info provides read-only access to server identification and configuration information. It exposes essential metadata about the server instance without allowing modifications.
type Server ¶ added in v1.5.0
type Server interface {
// Server embeds the base server interface providing lifecycle management
// methods: Start, Stop, Restart, IsRunning, Uptime, etc.
libsrv.Runner
// Info embeds server information access methods
Info
// Handler registers or updates the handler function that provides HTTP handlers.
// The function should return a map of handler keys to http.Handler instances.
Handler(h srvtps.FuncHandler)
// Merge combines configuration from another server instance into this one.
// This is useful for updating configuration dynamically without recreating the server.
Merge(s Server, def liblog.FuncLog) error
// GetConfig returns the current server configuration.
// The returned pointer should not be modified directly; use SetConfig instead.
GetConfig() *Config
// SetConfig updates the server configuration with validation.
// The server must be stopped before calling SetConfig to apply changes.
SetConfig(cfg Config, defLog liblog.FuncLog) error
// Monitor returns monitoring data for the server including health and metrics.
// Requires a version parameter for versioning the monitoring data format.
Monitor(vrs libver.Version) (montps.Monitor, error)
// MonitorName returns the unique monitoring identifier for this server instance.
MonitorName() string
}
Server defines the complete interface for managing an HTTP server instance. It extends libsrv.Runner with HTTP-specific functionality including configuration, handler management, and monitoring capabilities. All operations are thread-safe.
func New ¶ added in v1.10.0
New creates and initializes a new HTTP server instance from the provided configuration. The configuration is validated before creating the server. Returns an error if configuration validation fails or if server initialization encounters an error.
Parameters:
- cfg: Server configuration with all required fields populated
- defLog: Optional default logger function (can be nil)
Returns:
- Server: Initialized server instance ready to start
- error: Configuration validation or initialization error
Example:
cfg := httpserver.Config{
Name: "api-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(handlerFunc)
srv, err := httpserver.New(cfg, nil)
Example ¶
ExampleNew demonstrates the simplest way to create an HTTP server. This is the most basic use case for server creation.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver"
)
func main() {
cfg := httpserver.Config{
Name: "example-server",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{
"": http.NotFoundHandler(),
}
})
srv, err := httpserver.New(cfg, nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Server created: %s\n", srv.GetName())
}
Output: Server created: example-server
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package pool provides unified management of multiple HTTP servers through a thread-safe pool abstraction.
|
Package pool provides unified management of multiple HTTP servers through a thread-safe pool abstraction. |
|
Package types provides core type definitions and constants for HTTP server implementations.
|
Package types provides core type definitions and constants for HTTP server implementations. |