Skip to content

Commit 250fcf6

Browse files
committed
feat(config): add multiple server instances support
- Add ServersConfig and ServerInstanceConfig structs - Support configuring multiple named server instances - Add global timeout defaults with per-instance overrides - Add TLS configuration options (SSL cert/key, self-signed, AutoTLS) - Add validation for server configurations - Add helper methods for applying defaults and getting default server - Add conversion helper to avoid import cycles
1 parent 47cfc4b commit 250fcf6

File tree

8 files changed

+839
-70
lines changed

8 files changed

+839
-70
lines changed

cmd/testserver/main.go

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ func main() {
3939
logger.UpdateLoggerPath(cfg.Logger.Path, cfg.Logger.Dev)
4040
}
4141
logger.Info("ResolveSpec test server starting")
42-
logger.Info("Configuration loaded - Server will listen on: %s", cfg.Server.Addr)
4342

4443
// Initialize database manager
4544
ctx := context.Background()
@@ -73,47 +72,30 @@ func main() {
7372
// Create server manager
7473
mgr := server.NewManager()
7574

76-
// Parse host and port from addr
77-
host := ""
78-
port := 8080
79-
if cfg.Server.Addr != "" {
80-
// Parse addr (format: ":8080" or "localhost:8080")
81-
if cfg.Server.Addr[0] == ':' {
82-
// Just port
83-
_, err := fmt.Sscanf(cfg.Server.Addr, ":%d", &port)
84-
if err != nil {
85-
logger.Error("Invalid server address: %s", cfg.Server.Addr)
86-
os.Exit(1)
87-
}
88-
} else {
89-
// Host and port
90-
_, err := fmt.Sscanf(cfg.Server.Addr, "%[^:]:%d", &host, &port)
91-
if err != nil {
92-
logger.Error("Invalid server address: %s", cfg.Server.Addr)
93-
os.Exit(1)
94-
}
95-
}
75+
// Get default server configuration
76+
defaultServerCfg, err := cfg.Servers.GetDefault()
77+
if err != nil {
78+
logger.Error("Failed to get default server config: %v", err)
79+
os.Exit(1)
9680
}
9781

98-
// Add server instance
99-
_, err = mgr.Add(server.Config{
100-
Name: "api",
101-
Host: host,
102-
Port: port,
103-
Handler: r,
104-
ShutdownTimeout: cfg.Server.ShutdownTimeout,
105-
DrainTimeout: cfg.Server.DrainTimeout,
106-
ReadTimeout: cfg.Server.ReadTimeout,
107-
WriteTimeout: cfg.Server.WriteTimeout,
108-
IdleTimeout: cfg.Server.IdleTimeout,
109-
})
82+
// Apply global defaults
83+
defaultServerCfg.ApplyGlobalDefaults(cfg.Servers)
84+
85+
// Convert to server.Config and add instance
86+
serverCfg := server.FromConfigInstanceToServerConfig(defaultServerCfg, r)
87+
88+
logger.Info("Configuration loaded - Server '%s' will listen on %s:%d",
89+
serverCfg.Name, serverCfg.Host, serverCfg.Port)
90+
91+
_, err = mgr.Add(serverCfg)
11092
if err != nil {
11193
logger.Error("Failed to add server: %v", err)
11294
os.Exit(1)
11395
}
11496

11597
// Start server with graceful shutdown
116-
logger.Info("Starting server on %s", cfg.Server.Addr)
98+
logger.Info("Starting server '%s' on %s:%d", serverCfg.Name, serverCfg.Host, serverCfg.Port)
11799
if err := mgr.ServeWithGracefulShutdown(); err != nil {
118100
logger.Error("Server failed: %v", err)
119101
os.Exit(1)

pkg/config/config.go

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,77 @@ import "time"
44

55
// Config represents the complete application configuration
66
type Config struct {
7-
Server ServerConfig `mapstructure:"server"`
8-
Tracing TracingConfig `mapstructure:"tracing"`
9-
Cache CacheConfig `mapstructure:"cache"`
10-
Logger LoggerConfig `mapstructure:"logger"`
11-
ErrorTracking ErrorTrackingConfig `mapstructure:"error_tracking"`
12-
Middleware MiddlewareConfig `mapstructure:"middleware"`
13-
CORS CORSConfig `mapstructure:"cors"`
14-
EventBroker EventBrokerConfig `mapstructure:"event_broker"`
15-
DBManager DBManagerConfig `mapstructure:"dbmanager"`
16-
}
17-
18-
// ServerConfig holds server-related configuration
19-
type ServerConfig struct {
20-
Addr string `mapstructure:"addr"`
7+
Servers ServersConfig `mapstructure:"servers"`
8+
Tracing TracingConfig `mapstructure:"tracing"`
9+
Cache CacheConfig `mapstructure:"cache"`
10+
Logger LoggerConfig `mapstructure:"logger"`
11+
ErrorTracking ErrorTrackingConfig `mapstructure:"error_tracking"`
12+
Middleware MiddlewareConfig `mapstructure:"middleware"`
13+
CORS CORSConfig `mapstructure:"cors"`
14+
EventBroker EventBrokerConfig `mapstructure:"event_broker"`
15+
DBManager DBManagerConfig `mapstructure:"dbmanager"`
16+
Paths PathsConfig `mapstructure:"paths"`
17+
Extensions map[string]interface{} `mapstructure:"extensions"`
18+
}
19+
20+
// ServersConfig contains configuration for the server manager
21+
type ServersConfig struct {
22+
// DefaultServer is the name of the default server to use
23+
DefaultServer string `mapstructure:"default_server"`
24+
25+
// Instances is a map of server name to server configuration
26+
Instances map[string]ServerInstanceConfig `mapstructure:"instances"`
27+
28+
// Global timeout defaults (can be overridden per instance)
2129
ShutdownTimeout time.Duration `mapstructure:"shutdown_timeout"`
2230
DrainTimeout time.Duration `mapstructure:"drain_timeout"`
2331
ReadTimeout time.Duration `mapstructure:"read_timeout"`
2432
WriteTimeout time.Duration `mapstructure:"write_timeout"`
2533
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
2634
}
2735

36+
// ServerInstanceConfig defines configuration for a single server instance
37+
type ServerInstanceConfig struct {
38+
// Name is the unique name of this server instance
39+
Name string `mapstructure:"name"`
40+
41+
// Host is the host to bind to (e.g., "localhost", "0.0.0.0", "")
42+
Host string `mapstructure:"host"`
43+
44+
// Port is the port number to listen on
45+
Port int `mapstructure:"port"`
46+
47+
// Description is a human-readable description of this server
48+
Description string `mapstructure:"description"`
49+
50+
// GZIP enables GZIP compression middleware
51+
GZIP bool `mapstructure:"gzip"`
52+
53+
// TLS/HTTPS configuration options (mutually exclusive)
54+
// Option 1: Provide certificate and key files directly
55+
SSLCert string `mapstructure:"ssl_cert"`
56+
SSLKey string `mapstructure:"ssl_key"`
57+
58+
// Option 2: Use self-signed certificate (for development/testing)
59+
SelfSignedSSL bool `mapstructure:"self_signed_ssl"`
60+
61+
// Option 3: Use Let's Encrypt / AutoTLS
62+
AutoTLS bool `mapstructure:"auto_tls"`
63+
AutoTLSDomains []string `mapstructure:"auto_tls_domains"`
64+
AutoTLSCacheDir string `mapstructure:"auto_tls_cache_dir"`
65+
AutoTLSEmail string `mapstructure:"auto_tls_email"`
66+
67+
// Timeout configurations (overrides global defaults)
68+
ShutdownTimeout *time.Duration `mapstructure:"shutdown_timeout"`
69+
DrainTimeout *time.Duration `mapstructure:"drain_timeout"`
70+
ReadTimeout *time.Duration `mapstructure:"read_timeout"`
71+
WriteTimeout *time.Duration `mapstructure:"write_timeout"`
72+
IdleTimeout *time.Duration `mapstructure:"idle_timeout"`
73+
74+
// Tags for organization and filtering
75+
Tags map[string]string `mapstructure:"tags"`
76+
}
77+
2878
// TracingConfig holds OpenTelemetry tracing configuration
2979
type TracingConfig struct {
3080
Enabled bool `mapstructure:"enabled"`
@@ -136,3 +186,8 @@ type EventBrokerRetryPolicyConfig struct {
136186
MaxDelay time.Duration `mapstructure:"max_delay"`
137187
BackoffFactor float64 `mapstructure:"backoff_factor"`
138188
}
189+
190+
// PathsConfig contains configuration for named file system paths
191+
// This is a map of path name to file system path
192+
// Example: "data_dir": "/var/lib/myapp/data"
193+
type PathsConfig map[string]string

pkg/config/manager.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func (m *Manager) SetConfig(cfg *Config) error {
107107
}
108108

109109
// Use viper's merge to apply the config
110-
m.v.Set("server", cfg.Server)
110+
m.v.Set("servers", cfg.Servers)
111111
m.v.Set("tracing", cfg.Tracing)
112112
m.v.Set("cache", cfg.Cache)
113113
m.v.Set("logger", cfg.Logger)
@@ -116,6 +116,8 @@ func (m *Manager) SetConfig(cfg *Config) error {
116116
m.v.Set("cors", cfg.CORS)
117117
m.v.Set("event_broker", cfg.EventBroker)
118118
m.v.Set("dbmanager", cfg.DBManager)
119+
m.v.Set("paths", cfg.Paths)
120+
m.v.Set("extensions", cfg.Extensions)
119121

120122
return nil
121123
}
@@ -155,13 +157,22 @@ func (m *Manager) SaveConfig(path string) error {
155157

156158
// setDefaults sets default configuration values
157159
func setDefaults(v *viper.Viper) {
158-
// Server defaults
159-
v.SetDefault("server.addr", ":8080")
160-
v.SetDefault("server.shutdown_timeout", "30s")
161-
v.SetDefault("server.drain_timeout", "25s")
162-
v.SetDefault("server.read_timeout", "10s")
163-
v.SetDefault("server.write_timeout", "10s")
164-
v.SetDefault("server.idle_timeout", "120s")
160+
// Server defaults - new structure
161+
v.SetDefault("servers.default_server", "default")
162+
163+
// Global server timeout defaults
164+
v.SetDefault("servers.shutdown_timeout", "30s")
165+
v.SetDefault("servers.drain_timeout", "25s")
166+
v.SetDefault("servers.read_timeout", "10s")
167+
v.SetDefault("servers.write_timeout", "10s")
168+
v.SetDefault("servers.idle_timeout", "120s")
169+
170+
// Default server instance
171+
v.SetDefault("servers.instances.default.name", "default")
172+
v.SetDefault("servers.instances.default.host", "")
173+
v.SetDefault("servers.instances.default.port", 8080)
174+
v.SetDefault("servers.instances.default.description", "Default HTTP server")
175+
v.SetDefault("servers.instances.default.gzip", false)
165176

166177
// Tracing defaults
167178
v.SetDefault("tracing.enabled", false)
@@ -259,4 +270,13 @@ func setDefaults(v *viper.Viper) {
259270
v.SetDefault("event_broker.retry_policy.initial_delay", "1s")
260271
v.SetDefault("event_broker.retry_policy.max_delay", "30s")
261272
v.SetDefault("event_broker.retry_policy.backoff_factor", 2.0)
273+
274+
// Paths defaults (common directory paths)
275+
v.SetDefault("paths.data_dir", "./data")
276+
v.SetDefault("paths.config_dir", "./config")
277+
v.SetDefault("paths.logs_dir", "./logs")
278+
v.SetDefault("paths.temp_dir", "./tmp")
279+
280+
// Extensions defaults (empty map)
281+
v.SetDefault("extensions", map[string]interface{}{})
262282
}

0 commit comments

Comments
 (0)