Documentation
¶
Overview ¶
Package pool provides unified management of multiple HTTP servers through a thread-safe pool abstraction. It enables simultaneous operation of multiple servers with different configurations, unified lifecycle control, and advanced filtering capabilities.
Overview ¶
The pool package extends github.com/nabbar/golib/httpserver by providing a container for managing multiple HTTP server instances as a cohesive unit. All servers in the pool can be started, stopped, or restarted together while maintaining individual configurations and bind addresses.
Key capabilities:
- Unified lifecycle management (Start/Stop/Restart all servers)
- Thread-safe concurrent operations with sync.RWMutex
- Advanced filtering by name, bind address, or expose address
- Server merging and configuration validation
- Monitoring integration for all pooled servers
- Dynamic server addition and removal during operation
Design Philosophy ¶
1. Unified Management: Control multiple heterogeneous servers as a single logical unit 2. Thread Safety First: All operations are protected by mutex for concurrent safety 3. Flexibility: Support for dynamic server addition, removal, and configuration updates 4. Observability: Built-in monitoring and health check integration 5. Error Aggregation: Collect and report errors from all servers systematically
Architecture ¶
The pool implementation uses a layered architecture:
┌─────────────────────────────────────────────────────────┐ │ Pool │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────────────┐ │ │ │ Context │ │ Handler Function │ │ │ │ Provider │ │ (shared optional) │ │ │ └──────┬───────┘ └──────────┬───────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────────────────────────────────────┐ │ │ │ Server Map (libctx.Config[string]) │ │ │ │ Key: Bind Address (e.g., "0.0.0.0:8080") │ │ │ │ Value: libhtp.Server instance │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────┐ │ │ │ Individual Server Instances │ │ │ │ │ │ │ │ Server 1 ──┐ Server 2 ──┐ Server N ──┐ │ │ │ │ :8080 │ :8443 │ :9000 │ │ │ │ │ HTTP │ HTTPS │ Custom │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ Pool Operations │ │ │ │ - Walk: Iterate all servers │ │ │ │ - WalkLimit: Iterate specific servers │ │ │ │ - Filter: Query by criteria │ │ │ │ - Start/Stop/Restart: Lifecycle │ │ │ │ - Monitor: Health and metrics │ │ │ └─────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘
Data Flow ¶
Server Lifecycle:
- Configuration Phase: Servers defined via libhtp.Config
- Pool Creation: New() creates empty pool or with initial servers
- Server Addition: StoreNew() validates config and adds server
- Lifecycle Control: Start() initiates all servers concurrently
- Runtime Operations: Filter, Walk, Monitor during operation
- Graceful Shutdown: Stop() drains and closes all servers
Error Handling:
- Validation errors collected during config validation
- Startup errors aggregated during Start()
- Shutdown errors collected during Stop()
- All errors use liberr.Error with proper code hierarchy
Thread Safety ¶
All pool operations are thread-safe through sync.RWMutex:
- Read operations (Load, Walk, Filter, List) use RLock
- Write operations (Store, Delete, Clean) use Lock
- Atomic server map updates via libctx.Config
- Safe concurrent access to individual servers
Synchronization guarantees:
- No data races during concurrent operations
- Consistent view of server collection
- Safe iteration during modifications
- Proper memory barriers for visibility
Basic Usage ¶
Creating and managing a simple pool:
// Create configurations
cfg1 := libhtp.Config{
Name: "api-server",
Listen: "0.0.0.0:8080",
Expose: "http://api.example.com",
}
cfg1.RegisterHandlerFunc(apiHandler)
cfg2 := libhtp.Config{
Name: "admin-server",
Listen: "127.0.0.1:9000",
Expose: "http://localhost:9000",
}
cfg2.RegisterHandlerFunc(adminHandler)
// Create pool and add servers
p := pool.New(nil, nil)
p.StoreNew(cfg1, nil)
p.StoreNew(cfg2, nil)
// Start all servers
ctx := context.Background()
if err := p.Start(ctx); err != nil {
log.Fatal(err)
}
// Graceful shutdown
defer func() {
stopCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
p.Stop(stopCtx)
}()
Advanced Features ¶
## Configuration Helpers
The Config slice type provides bulk operations:
configs := pool.Config{cfg1, cfg2, cfg3}
// Validate all configurations
if err := configs.Validate(); err != nil {
log.Fatal(err)
}
// Set shared handler for all
configs.SetHandlerFunc(sharedHandler)
// Set shared context
configs.SetContext(context.Background())
// Create pool from configs
p, err := configs.Pool(nil, nil, nil)
## Dynamic Server Management
Add, remove, and update servers during operation:
// Add new server dynamically
newCfg := libhtp.Config{Name: "metrics", Listen: ":2112"}
p.StoreNew(newCfg, nil)
// Remove server
p.Delete(":2112")
// Atomic load and delete
if srv, ok := p.LoadAndDelete(":8080"); ok {
srv.Stop(ctx)
}
// Clear all servers
p.Clean()
## Filtering and Querying
Filter servers by various criteria:
// Filter by name pattern
apiServers := p.Filter(srvtps.FieldName, "", "^api-.*")
// Filter by bind address
localServers := p.Filter(srvtps.FieldBind, "", "^127\\.0\\.0\\.1:.*")
// Filter by expose address
prodServers := p.Filter(srvtps.FieldExpose, "", ".*\\.example\\.com.*")
// List specific fields
names := p.List(srvtps.FieldBind, srvtps.FieldName, "", ".*")
// Chain filters
filtered := p.Filter(srvtps.FieldBind, "", "^0\\.0\\.0\\.0:.*").
Filter(srvtps.FieldName, "", "^api-.*")
## Pool Merging
Combine multiple pools:
pool1 := New(nil, nil)
pool1.StoreNew(cfg1, nil)
pool2 := New(nil, nil)
pool2.StoreNew(cfg2, nil)
// Merge pool2 into pool1
if err := pool1.Merge(pool2, nil); err != nil {
log.Printf("Merge error: %v", err)
}
## Iteration and Inspection
Walk through servers with custom logic:
// Iterate all servers
p.Walk(func(bindAddress string, srv libhtp.Server) bool {
log.Printf("Server %s at %s", srv.GetName(), bindAddress)
return true // continue iteration
})
// Iterate specific servers
p.WalkLimit(func(bindAddress string, srv libhtp.Server) bool {
if srv.IsRunning() {
log.Printf("Running: %s", srv.GetName())
}
return true
}, ":8080", ":8443")
// Check server existence
if p.Has(":8080") {
log.Println("Server on :8080 exists")
}
// Get server count
log.Printf("Pool has %d servers", p.Len())
## Monitoring Integration
Access monitoring data for all servers:
version := libver.New("MyApp", "1.0.0")
monitors, err := p.Monitor(version)
if err != nil {
log.Printf("Monitor errors: %v", err)
}
for _, mon := range monitors {
log.Printf("Server %s status: %v", mon.Name, mon.Health)
}
// Get monitor identifiers
names := p.MonitorNames()
Use Cases ¶
## Multi-Port HTTP Server
Run HTTP and HTTPS servers simultaneously:
httpCfg := libhtp.Config{
Name: "http",
Listen: ":80",
Expose: "http://example.com",
}
httpsCfg := libhtp.Config{
Name: "https",
Listen: ":443",
Expose: "https://example.com",
// TLS configuration...
}
p := pool.New(nil, sharedHandler)
p.StoreNew(httpCfg, nil)
p.StoreNew(httpsCfg, nil)
p.Start(context.Background())
## Microservices Gateway
Route different services on different ports:
configs := pool.Config{
makeConfig("users-api", ":8081", usersHandler),
makeConfig("orders-api", ":8082", ordersHandler),
makeConfig("payments-api", ":8083", paymentsHandler),
}
p, err := configs.Pool(nil, nil, logger)
p.Start(context.Background())
## Development vs Production
Different configurations per environment:
var configs pool.Config
if isProd {
configs = pool.Config{
makeTLSConfig("api", ":443"),
makeTLSConfig("admin", ":8443"),
}
} else {
configs = pool.Config{
makeConfig("api", ":8080"),
makeConfig("admin", ":9000"),
}
}
p, _ := configs.Pool(ctx, handler, logger)
## Blue-Green Deployment
Gradually switch traffic between server pools:
bluePool := createPoolFromConfigs(blueConfigs) greenPool := createPoolFromConfigs(greenConfigs) // Start green pool greenPool.Start(ctx) // Switch traffic... time.Sleep(verificationPeriod) // Shutdown blue pool bluePool.Stop(ctx)
## Admin and Public Separation
Isolate administrative interfaces:
publicCfg := libhtp.Config{
Name: "public",
Listen: "0.0.0.0:8080",
Expose: "https://api.example.com",
}
adminCfg := libhtp.Config{
Name: "admin",
Listen: "127.0.0.1:9000", // localhost only
Expose: "http://localhost:9000",
}
p := pool.New(nil, nil)
p.StoreNew(publicCfg, nil)
p.StoreNew(adminCfg, nil)
Performance Characteristics ¶
Pool operations have the following complexity:
- Store/Load/Delete: O(1) average, O(n) worst case (map operations)
- Walk/WalkLimit: O(n) where n is number of servers
- Filter: O(n) with regex matching overhead
- List: O(n) + O(m) where m is filtered result size
- Start/Stop/Restart: O(n) parallel server operations
Memory usage:
- Base pool overhead: ~200 bytes
- Per-server overhead: ~100 bytes (map entry)
- Total: Base + (n × Server size) + (n × Overhead)
- Typical pool with 10 servers: ~50KB
Concurrency:
- Read operations scale with goroutines (RLock)
- Write operations serialize (Lock)
- Server lifecycle operations run concurrently
- No goroutine leaks during normal operation
Error Handling ¶
The package defines error codes in the liberr hierarchy:
- ErrorParamEmpty: Invalid or empty parameters
- ErrorPoolAdd: Failed to add server to pool
- ErrorPoolValidate: Configuration validation failure
- ErrorPoolStart: One or more servers failed to start
- ErrorPoolStop: One or more servers failed to stop
- ErrorPoolRestart: One or more servers failed to restart
- ErrorPoolMonitor: Monitoring operation failure
All errors implement liberr.Error interface with:
- Error code for programmatic handling
- Parent error chains for context
- Multiple error aggregation support
Limitations and Constraints ¶
Known limitations:
Bind Address Uniqueness: Each server must have a unique bind address. Attempting to add servers with duplicate bind addresses will overwrite the existing server.
No Automatic Port Allocation: The pool does not assign ports automatically. All bind addresses must be explicitly configured.
Synchronous Lifecycle: Start/Stop/Restart operations are synchronous and wait for all servers to complete. Use context timeouts for control.
No Load Balancing: The pool manages servers but does not distribute traffic between them. Use external load balancers for traffic distribution.
Error Aggregation: Errors from multiple servers are collected but the first error stops iteration in some operations (e.g., Merge).
No Health Checks: The pool does not perform automatic health checks. Integrate with monitoring systems for health management.
Best Practices ¶
DO:
- Validate all configurations before pool creation
- Use unique bind addresses for each server
- Set appropriate context timeouts for Start/Stop operations
- Check error codes for specific failure types
- Use Filter operations to manage subsets of servers
- Clean up pools with defer Stop(ctx)
- Use monitoring integration for production observability
DON'T:
- Don't assume all operations succeed (check errors)
- Don't use the same bind address for multiple servers
- Don't ignore validation errors
- Don't block indefinitely on Start/Stop (use context timeouts)
- Don't modify server configurations directly (use pool methods)
- Don't forget to handle partial failures during batch operations
Integration with golib Ecosystem ¶
The pool package integrates with:
- github.com/nabbar/golib/httpserver: Server implementation
- github.com/nabbar/golib/httpserver/types: Server types and interfaces
- github.com/nabbar/golib/context: Context management utilities
- github.com/nabbar/golib/logger: Logging integration
- github.com/nabbar/golib/errors: Error handling framework
- github.com/nabbar/golib/monitor/types: Monitoring abstractions
- github.com/nabbar/golib/runner: Lifecycle management interfaces
Testing ¶
The package includes comprehensive tests:
- Pool creation and initialization tests
- Server management operation tests (Store/Load/Delete)
- Filtering and querying tests
- Merge and clone operation tests
- Configuration validation tests
- Lifecycle management tests
- Error handling and edge case tests
Run tests with:
go test -v ./httpserver/pool CGO_ENABLED=1 go test -race -v ./httpserver/pool
Thread Safety Validation ¶
All operations are validated for thread safety:
- Zero race conditions with -race detector
- Concurrent read/write test scenarios
- Stress tests with multiple goroutines
- Safe server addition during iteration
Related Packages ¶
For single server management, see:
- github.com/nabbar/golib/httpserver: Individual HTTP server
- github.com/nabbar/golib/httpserver/types: Server type definitions
For other pooling patterns, see:
- github.com/nabbar/golib/runner/startStop: Generic runner pool
Example (Clean) ¶
Example_clean demonstrates clearing all servers. Shows how to empty the pool.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
cfg1 := libhtp.Config{Name: "s1", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"}
cfg1.RegisterHandlerFunc(handler)
p.StoreNew(cfg1, nil)
cfg2 := libhtp.Config{Name: "s2", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"}
cfg2.RegisterHandlerFunc(handler)
p.StoreNew(cfg2, nil)
fmt.Printf("Before clean: %d servers\n", p.Len())
p.Clean()
fmt.Printf("After clean: %d servers\n", p.Len())
}
Output: Before clean: 2 servers After clean: 0 servers
Example (Clone) ¶
Example_clone demonstrates cloning a pool. Shows how to create an independent copy of a pool.
package main
import (
"context"
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
original := pool.New(nil, nil)
cfg := libhtp.Config{
Name: "original-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()}
})
original.StoreNew(cfg, nil)
cloned := original.Clone(context.Background())
fmt.Printf("Original: %d servers\n", original.Len())
fmt.Printf("Cloned: %d servers\n", cloned.Len())
}
Output: Original: 1 servers Cloned: 1 servers
Example (ComplexFiltering) ¶
Example_complexFiltering demonstrates chaining multiple filters. Shows advanced filtering techniques.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
srvtps "github.com/nabbar/golib/httpserver/types"
)
func main() {
p := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
configs := []libhtp.Config{
{Name: "api-public", Listen: "0.0.0.0:8080", Expose: "http://api.example.com:8080"},
{Name: "api-private", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "web-public", Listen: "0.0.0.0:80", Expose: "http://www.example.com"},
}
for _, cfg := range configs {
cfg.RegisterHandlerFunc(handler)
p.StoreNew(cfg, nil)
}
filtered := p.Filter(srvtps.FieldBind, "", "^0\\.0\\.0\\.0:.*").
Filter(srvtps.FieldName, "", "^api-.*")
fmt.Printf("Public API servers: %d\n", filtered.Len())
}
Output: Public API servers: 1
Example (ConfigSlice) ¶
Example_configSlice demonstrates using Config slice for bulk operations. Shows validation and pool creation from multiple configurations.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
configs := pool.Config{
{Name: "server1", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "server2", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"},
}
configs.SetHandlerFunc(handler)
err := configs.Validate()
if err != nil {
fmt.Printf("Validation error: %v\n", err)
return
}
p, err := configs.Pool(nil, nil, nil)
if err != nil {
fmt.Printf("Pool creation error: %v\n", err)
return
}
fmt.Printf("Created pool with %d servers\n", p.Len())
}
Output: Created pool with 2 servers
Example (ConfigWalk) ¶
Example_configWalk demonstrates walking through configuration slice. Shows how to iterate over configurations before pool creation.
package main
import (
"fmt"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
configs := pool.Config{
{Name: "config1", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "config2", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"},
}
var count int
configs.Walk(func(cfg libhtp.Config) bool {
count++
fmt.Printf("Config: %s\n", cfg.Name)
return true
})
fmt.Printf("Total: %d configs\n", count)
}
Output: Config: config1 Config: config2 Total: 2 configs
Example (Delete) ¶
Example_delete demonstrates removing servers from the pool. Shows server deletion operations.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
cfg := libhtp.Config{
Name: "temp-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()}
})
p.StoreNew(cfg, nil)
fmt.Printf("Before delete: %d servers\n", p.Len())
p.Delete("127.0.0.1:8080")
fmt.Printf("After delete: %d servers\n", p.Len())
}
Output: Before delete: 1 servers After delete: 0 servers
Example (Filter) ¶
Example_filter demonstrates filtering servers by criteria. Shows how to create filtered subsets of the pool.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
srvtps "github.com/nabbar/golib/httpserver/types"
)
func main() {
p := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
configs := []libhtp.Config{
{Name: "api-server", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "api-v2-server", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"},
{Name: "web-server", Listen: "127.0.0.1:9000", Expose: "http://localhost:9000"},
}
for _, cfg := range configs {
cfg.RegisterHandlerFunc(handler)
p.StoreNew(cfg, nil)
}
filtered := p.Filter(srvtps.FieldName, "", "^api-.*")
fmt.Printf("Filtered pool has %d servers\n", filtered.Len())
}
Output: Filtered pool has 2 servers
Example (HandlerUpdate) ¶
Example_handlerUpdate demonstrates updating handler function. Shows how to change the shared handler function.
package main
import (
"fmt"
"net/http"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{
"/api": http.NotFoundHandler(),
}
}
p.Handler(handler)
fmt.Println("Handler updated successfully")
}
Output: Handler updated successfully
Example (List) ¶
Example_list demonstrates listing server attributes. Shows how to extract specific fields from servers.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
srvtps "github.com/nabbar/golib/httpserver/types"
)
func main() {
p := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
configs := []libhtp.Config{
{Name: "server1", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "server2", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"},
}
for _, cfg := range configs {
cfg.RegisterHandlerFunc(handler)
p.StoreNew(cfg, nil)
}
names := p.List(srvtps.FieldName, srvtps.FieldName, "", ".*")
fmt.Printf("Found %d servers\n", len(names))
}
Output: Found 2 servers
Example (LoadAndCheck) ¶
Example_loadAndCheck demonstrates loading servers and checking existence. Shows how to retrieve and verify servers in the pool.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
cfg := libhtp.Config{
Name: "test-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()}
})
p.StoreNew(cfg, nil)
if p.Has("127.0.0.1:8080") {
srv := p.Load("127.0.0.1:8080")
fmt.Printf("Found server: %s\n", srv.GetName())
}
if !p.Has("127.0.0.1:9999") {
fmt.Println("Server on :9999 not found")
}
}
Output: Found server: test-server Server on :9999 not found
Example (LoadAndDelete) ¶
Example_loadAndDelete demonstrates atomic load and delete. Shows how to retrieve and remove a server in one operation.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
cfg := libhtp.Config{
Name: "remove-me",
Listen: "127.0.0.1:8080",
Expose: "http://localhost:8080",
}
cfg.RegisterHandlerFunc(func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
})
p.StoreNew(cfg, nil)
srv, loaded := p.LoadAndDelete("127.0.0.1:8080")
if loaded {
fmt.Printf("Removed: %s\n", srv.GetName())
}
fmt.Printf("Pool now has %d servers\n", p.Len())
}
Output: Removed: remove-me Pool now has 0 servers
Example (Merge) ¶
Example_merge demonstrates merging two pools. Shows how to combine servers from different pools.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
pool1 := pool.New(nil, nil)
pool2 := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
cfg1 := libhtp.Config{Name: "server1", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"}
cfg1.RegisterHandlerFunc(handler)
pool1.StoreNew(cfg1, nil)
cfg2 := libhtp.Config{Name: "server2", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"}
cfg2.RegisterHandlerFunc(handler)
pool2.StoreNew(cfg2, nil)
err := pool1.Merge(pool2, nil)
if err != nil {
fmt.Printf("Merge error: %v\n", err)
return
}
fmt.Printf("Merged pool has %d servers\n", pool1.Len())
}
Output: Merged pool has 2 servers
Example (MonitorNames) ¶
Example_monitorNames demonstrates accessing monitoring identifiers. Shows how to retrieve monitor names for all servers.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
configs := []libhtp.Config{
{Name: "api", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "web", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"},
}
for _, cfg := range configs {
cfg.RegisterHandlerFunc(handler)
p.StoreNew(cfg, nil)
}
names := p.MonitorNames()
fmt.Printf("Monitor count: %d\n", len(names))
}
Output: Monitor count: 2
Example (MultiStepPool) ¶
Example_multiStepPool demonstrates a complete pool lifecycle. Shows configuration, creation, management, and cleanup.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
configs := pool.Config{
{Name: "primary", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "backup", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"},
}
configs.SetHandlerFunc(handler)
if err := configs.Validate(); err != nil {
fmt.Printf("Validation failed: %v\n", err)
return
}
p, err := configs.Pool(nil, nil, nil)
if err != nil {
fmt.Printf("Pool creation failed: %v\n", err)
return
}
fmt.Printf("Phase 1: %d servers\n", p.Len())
newCfg := libhtp.Config{
Name: "emergency",
Listen: "127.0.0.1:9000",
Expose: "http://localhost:9000",
}
newCfg.RegisterHandlerFunc(handler)
p.StoreNew(newCfg, nil)
fmt.Printf("Phase 2: %d servers\n", p.Len())
p.Delete("127.0.0.1:8081")
fmt.Printf("Phase 3: %d servers\n", p.Len())
}
Output: Phase 1: 2 servers Phase 2: 3 servers Phase 3: 2 servers
Example (MultipleServers) ¶
Example_multipleServers demonstrates managing multiple servers in a pool. Shows how to add several servers with different configurations.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
configs := []libhtp.Config{
{Name: "api", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "admin", Listen: "127.0.0.1:9000", Expose: "http://localhost:9000"},
{Name: "metrics", Listen: "127.0.0.1:2112", Expose: "http://localhost:2112"},
}
for _, cfg := range configs {
cfg.RegisterHandlerFunc(handler)
p.StoreNew(cfg, nil)
}
fmt.Printf("Pool contains %d servers\n", p.Len())
}
Output: Pool contains 3 servers
Example (SimplePool) ¶
Example_simplePool demonstrates creating a pool with a single server. This shows basic server configuration and addition to the pool.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
cfg := libhtp.Config{
Name: "simple-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()}
})
err := p.StoreNew(cfg, nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Pool has %d server(s)\n", p.Len())
}
Output: Pool has 1 server(s)
Example (Walk) ¶
Example_walk demonstrates iterating over all servers in the pool. Shows how to execute logic for each server.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
configs := []libhtp.Config{
{Name: "server-a", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "server-b", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"},
}
for _, cfg := range configs {
cfg.RegisterHandlerFunc(handler)
p.StoreNew(cfg, nil)
}
var count int
p.Walk(func(bindAddress string, srv libhtp.Server) bool {
count++
return true
})
fmt.Printf("Walked through %d servers\n", count)
}
Output: Walked through 2 servers
Example (WalkLimit) ¶
Example_walkLimit demonstrates iterating over specific servers. Shows how to process only selected servers by bind address.
package main
import (
"fmt"
"net/http"
libhtp "github.com/nabbar/golib/httpserver"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
handler := func() map[string]http.Handler {
return map[string]http.Handler{"": http.NotFoundHandler()}
}
configs := []libhtp.Config{
{Name: "api", Listen: "127.0.0.1:8080", Expose: "http://localhost:8080"},
{Name: "web", Listen: "127.0.0.1:8081", Expose: "http://localhost:8081"},
{Name: "admin", Listen: "127.0.0.1:9000", Expose: "http://localhost:9000"},
}
for _, cfg := range configs {
cfg.RegisterHandlerFunc(handler)
p.StoreNew(cfg, nil)
}
var names []string
p.WalkLimit(func(bindAddress string, srv libhtp.Server) bool {
names = append(names, srv.GetName())
return true
}, "127.0.0.1:8080", "127.0.0.1:9000")
if len(names) == 2 {
fmt.Printf("Selected %d servers\n", len(names))
}
}
Output: Selected 2 servers
Index ¶
- Constants
- type Config
- func (p Config) Pool(ctx context.Context, hdl srvtps.FuncHandler, defLog liblog.FuncLog) (Pool, error)
- func (p Config) SetContext(f context.Context)
- func (p Config) SetDefaultTLS(f libtls.FctTLSDefault)
- func (p Config) SetHandlerFunc(hdl srvtps.FuncHandler)
- func (p Config) Validate() error
- func (p Config) Walk(fct FuncWalkConfig)
- type Filter
- type FuncWalk
- type FuncWalkConfig
- type Manage
- type Pool
Examples ¶
- Package (Clean)
- Package (Clone)
- Package (ComplexFiltering)
- Package (ConfigSlice)
- Package (ConfigWalk)
- Package (Delete)
- Package (Filter)
- Package (HandlerUpdate)
- Package (List)
- Package (LoadAndCheck)
- Package (LoadAndDelete)
- Package (Merge)
- Package (MonitorNames)
- Package (MultiStepPool)
- Package (MultipleServers)
- Package (SimplePool)
- Package (Walk)
- Package (WalkLimit)
- New
Constants ¶
const ( // ErrorParamEmpty indicates that required parameters are missing or empty. ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgHttpServerPool // ErrorPoolAdd indicates failure to add a server to the pool. ErrorPoolAdd // ErrorPoolValidate indicates at least one server configuration is invalid. ErrorPoolValidate // ErrorPoolStart indicates at least one server failed to start. ErrorPoolStart // ErrorPoolStop indicates at least one server failed to stop gracefully. ErrorPoolStop // ErrorPoolRestart indicates at least one server failed to restart. ErrorPoolRestart // ErrorPoolMonitor indicates failure in monitoring operations. ErrorPoolMonitor )
Error codes for pool operations following liberr.CodeError hierarchy.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
Config is a slice of server configurations used to create a pool of servers. It provides convenience methods for bulk operations on multiple server configurations.
func (Config) Pool ¶
func (p Config) Pool(ctx context.Context, hdl srvtps.FuncHandler, defLog liblog.FuncLog) (Pool, error)
Pool creates a new server pool from the configurations. All configurations are validated and instantiated as servers in the pool. Returns an error if any configuration is invalid or server creation fails.
Parameters:
- ctx: Context provider for server operations (can be nil)
- hdl: Handler function for all servers (can be nil if already set on configs)
- defLog: Default logger function (can be nil)
Returns:
- Pool: Initialized pool with all servers
- error: Aggregated errors from server creation, nil if all succeed
func (Config) SetContext ¶
SetContext sets the context provider function for all server configurations. This provides a shared context source for all servers in the configuration.
func (Config) SetDefaultTLS ¶
func (p Config) SetDefaultTLS(f libtls.FctTLSDefault)
SetDefaultTLS sets the default TLS configuration provider for all server configurations. This allows servers to inherit a shared TLS configuration when needed.
func (Config) SetHandlerFunc ¶
func (p Config) SetHandlerFunc(hdl srvtps.FuncHandler)
SetHandlerFunc registers the same handler function with all server configurations in the slice. This is useful for setting a shared handler across multiple servers before pool creation.
func (Config) Validate ¶
Validate validates all server configurations in the slice. Returns an aggregated error containing all validation failures, or nil if all are valid.
Returns:
- error: Aggregated validation errors, nil if all configurations are valid
func (Config) Walk ¶
func (p Config) Walk(fct FuncWalkConfig)
Walk iterates over all configurations, calling the provided function for each. Iteration stops if the callback returns false or all configurations have been processed. Does nothing if the callback function is nil.
type Filter ¶
type Filter interface {
// Has checks if a server with the given bind address exists in the pool.
Has(bindAddress string) bool
// Len returns the number of servers in the pool.
Len() int
// List returns a list of server field values matching the filter criteria.
// fieldFilter specifies which field to match against, fieldReturn specifies which field to return.
// Pattern uses glob-style matching (* wildcards), regex uses regular expressions.
List(fieldFilter, fieldReturn srvtps.FieldType, pattern, regex string) []string
// Filter creates a new pool containing only servers matching the criteria.
// field specifies which field to filter on, pattern uses globs, regex uses regular expressions.
Filter(field srvtps.FieldType, pattern, regex string) Pool
}
Filter provides filtering and querying operations for servers in the pool.
type FuncWalk ¶
FuncWalk is a callback function used when iterating over servers in the pool. The function receives the bind address and server instance for each iteration. Return true to continue iteration, false to stop.
type FuncWalkConfig ¶
FuncWalkConfig is a callback function for iterating over server configurations. Return true to continue iteration, false to stop.
type Manage ¶
type Manage interface {
// Walk iterates over all servers in the pool, calling the provided function for each.
// Iteration stops if the callback returns false. Returns true if all servers were visited.
Walk(fct FuncWalk)
// WalkLimit iterates over specific servers identified by their bind addresses.
// If no addresses are provided, behaves like Walk. Returns true if iteration completed.
WalkLimit(fct FuncWalk, onlyBindAddress ...string)
// Clean removes all servers from the pool.
Clean()
// Load retrieves a server by its bind address. Returns nil if not found.
Load(bindAddress string) libhtp.Server
// Store adds or updates a server in the pool, using its bind address as the key.
Store(srv libhtp.Server)
// Delete removes a server from the pool by its bind address.
Delete(bindAddress string)
// StoreNew creates a new server from configuration and adds it to the pool.
// Returns an error if server creation or validation fails.
StoreNew(cfg libhtp.Config, defLog liblog.FuncLog) error
// LoadAndDelete atomically retrieves and removes a server.
// Returns the server and true if found, nil and false otherwise.
LoadAndDelete(bindAddress string) (val libhtp.Server, loaded bool)
// MonitorNames returns a list of all monitoring identifiers for servers in the pool.
MonitorNames() []string
}
Manage provides server management operations for a pool. All operations are thread-safe and can be called concurrently.
type Pool ¶
type Pool interface {
// Runner embeds base server interface for lifecycle management
libsrv.Runner
// Manage embeds server management operations
Manage
// Filter embeds filtering and query operations
Filter
// Clone creates a deep copy of the pool with an optional new context.
// The cloned pool contains independent copies of all servers.
Clone(ctx context.Context) Pool
// Merge combines servers from another pool into this one.
// Servers with conflicting bind addresses will be updated.
Merge(p Pool, def liblog.FuncLog) error
// Handler registers a handler function for all servers in the pool.
Handler(fct srvtps.FuncHandler)
// Monitor retrieves monitoring data for all servers in the pool.
// Returns a slice of Monitor instances, one per server.
Monitor(vrs libver.Version) ([]montps.Monitor, liberr.Error)
}
Pool represents a collection of HTTP servers managed as a unified group. It combines server lifecycle management (Start/Stop/Restart) with advanced filtering, monitoring, and configuration operations. All methods are thread-safe.
func New ¶
New creates a new server pool with optional initial servers. The pool manages server lifecycle and provides unified operations across all servers.
Parameters:
- ctx: Context provider function for server operations (can be nil)
- hdl: Handler function to register with all servers (can be nil)
- srv: Optional initial servers to add to the pool
Returns:
- Pool: Initialized pool ready for use
Example:
pool := pool.New(nil, handlerFunc) pool.StoreNew(config1, nil) pool.StoreNew(config2, nil) pool.Start(context.Background())
Example ¶
ExampleNew demonstrates creating an empty pool. This is the simplest usage pattern - creating a pool container.
package main
import (
"fmt"
"github.com/nabbar/golib/httpserver/pool"
)
func main() {
p := pool.New(nil, nil)
fmt.Printf("Pool created with %d servers\n", p.Len())
}
Output: Pool created with 0 servers