Files
go-omni/config/config.go

163 lines
5.4 KiB
Go

package config
import (
"strings"
"time"
"git.yuntuo.hk/k2safe/go-omni/httpx"
"git.yuntuo.hk/k2safe/go-omni/logx"
)
const defaultRequestIDHeader = "X-Request-ID"
const (
TransportNetHTTP = "nethttp"
TransportNative = "native"
TransportHTTP2 = "http2"
)
// TLSConfig enables HTTPS when both certificate paths are provided.
type TLSConfig struct {
CertFile string `json:"certFile"`
KeyFile string `json:"keyFile"`
}
// Config controls the default runtime behavior of an App.
type Config struct {
Address string `json:"address"`
Transport string `json:"transport"`
TLS TLSConfig `json:"tls"`
RequestIDHeader string `json:"requestIdHeader"`
EnableLogger bool `json:"enableLogger"`
SlowRequestThreshold time.Duration `json:"slowRequestThreshold"`
TrustedProxies []string `json:"trustedProxies"`
ShutdownTimeout time.Duration `json:"shutdownTimeout"`
Log logx.Config `json:"log"`
}
func Default() Config {
return Config{
Address: ":8080",
Transport: TransportNetHTTP,
RequestIDHeader: defaultRequestIDHeader,
ShutdownTimeout: 10 * time.Second,
Log: logx.Default(),
}
}
// Validate checks configuration values before an application starts.
func (c Config) Validate() error {
if c.SlowRequestThreshold < 0 {
return &ConfigError{Field: "slowRequestThreshold", Message: "must be nonnegative"}
}
if strings.TrimSpace(c.Address) == "" {
return &ConfigError{Field: "address", Message: "is required"}
}
if strings.TrimSpace(c.RequestIDHeader) == "" {
return &ConfigError{Field: "request_id_header", Message: "is required"}
}
if c.ShutdownTimeout <= 0 {
return &ConfigError{Field: "shutdown_timeout", Message: "must be positive"}
}
if _, err := httpx.NewClientIPResolver(c.TrustedProxies...); err != nil {
return &ConfigError{Field: "trusted_proxies", Message: err.Error()}
}
return c.ValidateTransport()
}
// ValidateTransport checks built-in transport selection without opening files
// or sockets. Empty Transport preserves the standard net/http default.
func (c Config) ValidateTransport() error {
switch c.Transport {
case "", TransportNetHTTP, TransportNative, TransportHTTP2:
default:
return &ConfigError{Field: "transport", Message: "must be nethttp, native or http2"}
}
cert, key := strings.TrimSpace(c.TLS.CertFile), strings.TrimSpace(c.TLS.KeyFile)
if (cert == "") != (key == "") {
return &ConfigError{Field: "tls", Message: "requires both certFile and keyFile"}
}
if c.Transport == TransportHTTP2 && cert == "" {
return &ConfigError{Field: "tls", Message: "is required for the experimental http2 transport"}
}
return nil
}
// ConfigError describes an invalid application configuration field.
type ConfigError struct {
Field string
Message string
}
func (e *ConfigError) Error() string { return "config " + e.Field + " " + e.Message }
// Option changes an App's configuration.
type Option func(*Config)
// WithTransport selects a built-in server. HTTP/2 is experimental and requires TLS.
func WithTransport(name string) Option {
return func(cfg *Config) { cfg.Transport = name }
}
// WithTLS enables HTTPS. Certificates are loaded before Run opens its listener.
func WithTLS(certFile, keyFile string) Option {
return func(cfg *Config) { cfg.TLS = TLSConfig{CertFile: certFile, KeyFile: keyFile} }
}
// WithRequestIDHeader changes the header used to read and return request IDs.
func WithRequestIDHeader(header string) Option {
return func(cfg *Config) {
if value := strings.TrimSpace(header); value != "" {
cfg.RequestIDHeader = value
}
}
}
// WithLogger enables GoOmni's structured access logger.
func WithLogger(enabled bool) Option {
return func(cfg *Config) { cfg.EnableLogger = enabled }
}
// WithSlowRequestThreshold enables access logs and warns after this duration.
func WithSlowRequestThreshold(threshold time.Duration) Option {
return func(cfg *Config) { cfg.SlowRequestThreshold = threshold; cfg.EnableLogger = true }
}
// WithTrustedProxies allows only these IPs/CIDRs to supply X-Forwarded-For.
// An empty list disables forwarding-header trust. Invalid entries are rejected
// by Config.Validate and panic when passed directly to App.New.
func WithTrustedProxies(proxies ...string) Option {
proxies = append([]string(nil), proxies...)
return func(cfg *Config) { cfg.TrustedProxies = append([]string(nil), proxies...) }
}
// WithConfig applies a complete configuration loaded from a file or
// environment. Later options can still override individual fields.
func WithConfig(settings Config) Option {
settings.TrustedProxies = append([]string(nil), settings.TrustedProxies...)
settings.Log.RedactKeys = append([]string(nil), settings.Log.RedactKeys...)
return func(cfg *Config) {
*cfg = settings
cfg.TrustedProxies = append([]string(nil), settings.TrustedProxies...)
cfg.Log.RedactKeys = append([]string(nil), settings.Log.RedactKeys...)
}
}
// WithLogConfig configures the logger used by the application runtime.
func WithLogConfig(settings logx.Config) Option {
settings.RedactKeys = append([]string(nil), settings.RedactKeys...)
return func(cfg *Config) {
cfg.Log = settings
cfg.Log.RedactKeys = append([]string(nil), settings.RedactKeys...)
}
}
// WithShutdownTimeout controls how long RunContext waits for active requests.
func WithShutdownTimeout(timeout time.Duration) Option {
return func(cfg *Config) {
if timeout > 0 {
cfg.ShutdownTimeout = timeout
}
}
}