Documentation
¶
Overview ¶
Mogoly Core ¶
The core package is the main load balancing engine of the Mogoly software. It provides comprehensive functionality for:
- Server Pooling: Manage backend server pools with automatic health monitoring
- Load Balancing: Round-robin distribution with automatic failover
- Reverse Proxy: HTTP/HTTPS request forwarding with proper header handling
- Health Checking: Periodic health checks with configurable intervals
- Middleware System: Extensible middleware chain for rate limiting, logging, and more
- Configuration Management: YAML/JSON config file support with hot-reloading
- TLS/SSL Support: Automatic certificate management via CertMagic
- DNS Support: Custom DNS resolver for local domain resolution
- Dynamic Routing: Host-based routing with multiple server pools
Architecture ¶
The core package implements a sophisticated load balancing architecture:
┌─────────────┐
│ Client │
└──────┬──────┘
│
▼
┌─────────────────┐
│ Router │ (Host-based routing)
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌────────┐ ┌────────┐
│Server 1│ │Server 2│ (Each can be a load balancer)
└───┬────┘ └───┬────┘
│ │
▼ ▼
Backend Backend
Pools Pools
Quick Start ¶
## Basic Configuration
Create a configuration file (config.yaml):
server:
- name: api-gateway
protocol: http
host: localhost
port: 3000
balance:
- name: backend-1
url: http://localhost:8081
- name: backend-2
url: http://localhost:8082
middlewares:
- name: ratelimit
config:
request_per_minute: 100
limit_window: 60s
health_check_interval: 30
stream: true
## Loading and Running
package main
import (
"log"
"github.com/DoniLite/Mogoly/core"
)
func main() {
// Load configuration
content, _ := core.LoadConfigFile("config.yaml")
format, _ := core.DiscoverConfigFormat("config.yaml")
config, _ := core.ParseConfig(content, format)
// Build router
core.BuildRouter(config)
// Start HTTP server
server := core.ServeHTTP(":8080")
defer server.Close()
// Keep running
select {}
}
Server Management ¶
## Creating Servers Programmatically
server := &core.Server{
Name: "backend-1",
Protocol: "http",
Host: "localhost",
Port: 8080,
}
// Initialize reverse proxy
err := server.UpgradeProxy()
if err != nil {
log.Fatal(err)
}
// Add to balancing pool
backend := &core.Server{
Name: "backend-2",
URL: "http://localhost:8081",
}
server.AddNewBalancingServer(backend)
// Remove from pool
server.DelBalancingServer("backend-2")
## Health Checking
// Check all servers in pool
status, err := server.CheckHealthAll()
if err != nil {
log.Fatal(err)
}
for _, s := range status.Pass {
log.Printf("✓ %s is healthy", s.Name)
}
for _, s := range status.Fail {
log.Printf("✗ %s is down", s.Name)
}
// Check specific server
serverStatus, _ := server.CheckHealthAny("backend-1")
// Check self
selfStatus, _ := server.CheckHealthSelf()
## Load Balancing
The core package uses a round-robin strategy with automatic failover:
// Get next healthy server (automatically skips unhealthy servers)
nextServer, err := server.GetNextServer()
if err != nil {
log.Fatal("No healthy servers available")
}
// The server can be used as an HTTP handler
http.Handle("/", server)
Middleware System ¶
## Built-in Middlewares
### Rate Limiter
Limits the number of requests per IP address:
config := core.RateLimitMiddlewareConfig{
ReqPerMinute: 60,
LimitWindow: time.Minute,
}
middleware := core.RateLimiterMiddleware(config)
## Chaining Middlewares
handler := core.ChainMiddleware(
baseHandler,
authMiddleware,
rateLimitMiddleware,
loggingMiddleware,
)
## Creating Custom Middlewares
type CustomConfig struct {
Value string
}
func CustomMiddleware(config any) func(next http.Handler) http.Handler {
cfg := config.(CustomConfig)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Pre-processing
log.Printf("Custom: %s", cfg.Value)
// Call next handler
next.ServeHTTP(w, r)
// Post-processing
})
}
}
// Register middleware
core.MiddlewaresList["custom"] = core.MiddlewareSets{
Fn: CustomMiddleware,
Conf: CustomConfig{Value: "example"},
}
Configuration Management ¶
## Configuration File Formats
Supports both YAML and JSON formats:
### YAML Configuration
server:
- name: api-gateway
protocol: http
host: localhost
port: 3000
balance:
- name: backend-1
protocol: http
host: localhost
port: 8081
- name: backend-2
url: http://localhost:8082
middlewares:
- name: ratelimit
config:
request_per_minute: 100
limit_window: 60s
health_check_interval: 30
log_output: /var/log/mogoly.log
stream: true
### JSON Configuration
{
"server": [
{
"name": "api-gateway",
"protocol": "http",
"host": "localhost",
"port": 3000,
"balance": [
{
"name": "backend-1",
"url": "http://localhost:8081"
}
],
"middlewares": [
{
"name": "ratelimit",
"config": {
"request_per_minute": 100
}
}
]
}
],
"health_check_interval": 30,
"stream": true
}
## Loading Configuration
// Load config file
content, err := core.LoadConfigFile("config.yaml")
if err != nil {
log.Fatal(err)
}
// Auto-detect format from extension
format, err := core.DiscoverConfigFormat("config.yaml")
if err != nil {
log.Fatal(err)
}
// Parse configuration
config, err := core.ParseConfig(content, format)
if err != nil {
log.Fatal(err)
}
TLS/SSL Support ¶
## HTTPS Server with Certificate Manager
package main
import (
"github.com/DoniLite/Mogoly/core"
)
func main() {
// Create certificate manager
certManager := core.NewCertManager(
"[email protected]",
[]string{"example.com", "*.example.com"},
)
// Build router
core.BuildRouter(config)
// Start HTTPS server
httpsServer := core.ServeHTTPS(":443", certManager)
defer httpsServer.Close()
// Optionally start HTTP server for redirects
httpServer := core.ServeHTTP(":80")
defer httpServer.Close()
select {}
}
The certificate manager automatically:
- Obtains certificates from Let's Encrypt
- Renews certificates before expiration
- Handles ACME challenges
- Caches certificates on disk
Router System ¶
## Dynamic Routing
The router maps hostnames to server handlers:
// Build router from config
core.BuildRouter(config)
// Get current router
router := core.GetRouter()
// Add server dynamically
newServer := &core.Server{
Name: "new-api",
URL: "http://localhost:9000",
}
router.AddServer(newServer)
// Remove server
router.RemoveServer(newServer)
## Host-Based Routing
Requests are routed based on the Host header:
example.com -> Server "example.com" api.example.com -> Server "api.example.com" *.example.com -> Server "wildcard.example.com" (if configured)
Best Practices ¶
1. **Use Health Checks**: Configure appropriate health check intervals (30-60 seconds recommended)
2. **Configure Timeouts**: Set reasonable HTTP client timeouts for health checks (3-5 seconds)
3. **Initialize Proxies**: Always call UpgradeProxy() on servers before using them as load balancers
4. **Middleware Order**: Chain middlewares in the correct order:
- Authentication first
- Rate limiting second
- Logging last
5. **TLS in Production**: Use CertMagic for automatic certificate management
6. **Monitor Health**: Set up alerts for when servers become unhealthy
7. **Graceful Shutdown**: Properly close HTTP servers on application exit
8. **Configuration Validation**: Validate configuration files before deploying
9. **Resource Cleanup**: Always defer Close() on servers and managers
10. **Load Distribution**: Use multiple backend servers for better distribution
Types Reference ¶
## Server
type Server struct {
ID string // Server ID
Name string // Server name (required)
Protocol string // "http" or "https"
Host string // Hostname or IP
Port int // Port number
URL string // Full URL (alternative to host+port)
IsHealthy bool // Health status
BalancingServers []*Server // Backend pool for load balancing
Middlewares []Middleware // Applied middlewares
LastHealthCheck *time.Time // Last health check timestamp
}
## Config
type Config struct {
Servers []*Server // Server definitions
HealthCheckInterval int // Interval in seconds
LogOutput string // Log file path
Stream bool // Enable log streaming
Services []*cloud.ServiceConfig // Cloud services
}
## HealthCheckStatus
type HealthCheckStatus struct {
Pass []ServerStatus // Healthy servers
Fail []ServerStatus // Unhealthy servers
CheckTime time.Time // Check timestamp
Duration time.Duration // Check duration
}
Integration with Cloud Package ¶
The core package can automatically provision database services:
config := &core.Config{
Servers: []*core.Server{
{
Name: "api",
URL: "http://localhost:3000",
},
},
Services: []*cloud.ServiceConfig{
{
Type: cloud.PostgreSQL,
Name: "app-database",
Username: "admin",
Password: "secure",
},
},
}
The database service will be automatically created and managed alongside your load balancer.
Performance Considerations ¶
- **Connection Pooling**: The reverse proxy reuses connections to backend servers - **Health Check Optimization**: Failed servers are temporarily marked unhealthy - **Middleware Efficiency**: Keep middleware processing lightweight - **Router Locks**: The router uses RWMutex for efficient concurrent access - **Memory Usage**: Each server maintains its own proxy and connection pool
Troubleshooting ¶
## No healthy servers available
- Check if backend servers are running - Verify health check URLs are accessible - Review health check interval settings - Check server logs for connection errors
## Certificate errors
- Verify domain DNS records point to your server - Ensure ports 80 and 443 are accessible - Check email address is valid for ACME - Review certificate manager logs
## High memory usage
- Reduce number of concurrent connections - Adjust health check intervals - Monitor middleware memory consumption - Check for connection leaks in custom handlers
See Also ¶
- Package sync: Real-time communication between components
- Package cloud: Docker service management and provisioning
Index ¶
- Variables
- func AddEventHandler(event *goevents.Event, handler goevents.EventHandler)
- func BuildRouter(config *Config)
- func ChainMiddleware(handler http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler
- func CleanupVisitors()
- func DiscoverConfigFormat(configPath string) (string, error)
- func HealthChecker(server *Server) (bool, error)
- func LoadConfigFile(configPath string) ([]byte, error)
- func NewProxy(target *url.URL) *httputil.ReverseProxy
- func Ping(w http.ResponseWriter, r *http.Request)
- func RateLimiterMiddleware(config any) func(next http.Handler) http.Handler
- func SerializeHealthCheckStatus(status *HealthCheckStatus) (string, error)
- func ServeDNS(bind string, isLocal func(string) bool, forwardTo string)
- func ServeHTTP(addr string) *http.Server
- func ServeHTTPS(addr string, cm *CertManager) *http.Server
- func SetLogger(lgr Logger)
- func StartDNSServer(bind string, isLocal func(string) bool, forwardTo string) (stop func(), err error)
- func SubscribeToEvent(handler goevents.EventHandler, events ...*goevents.Event)
- func WatchConfig(path string, onReload func(*Config)) error
- type BalancerStrategy
- type CertManager
- type Config
- type ConfigRules
- type ConfigRulesTarget
- type DNSServer
- type HealthCheckStatus
- type LogType
- type Logger
- type Logs
- type MiddleWareName
- type Middleware
- type MiddlewareSets
- type MogolyMiddleware
- type RateLimitMiddlewareConfig
- type RouterState
- type Server
- func (server *Server) AddNewBalancingServer(bs *Server)
- func (server *Server) CheckHealthAll() (*HealthCheckStatus, error)
- func (server *Server) CheckHealthAny(name string) (*ServerStatus, error)
- func (server *Server) CheckHealthSelf() (*ServerStatus, error)
- func (server *Server) DelBalancingServer(name string)
- func (server *Server) GetNextServer() (*Server, error)
- func (server *Server) GetServer(name string) *Server
- func (server *Server) RollBack(servers []*Server)
- func (server *Server) RollBackAny(name string, newServer *Server) error
- func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (server *Server) UpgradeProxy() error
- type ServerStatus
Constants ¶
This section is empty.
Variables ¶
var ( ServerStartedEvent *goevents.Event ErrorDroppedEvent *goevents.Event CertManagerActionEvent *goevents.Event )
var EventBus *goevents.EventFactory
Functions ¶
func AddEventHandler ¶ added in v0.3.2
func AddEventHandler(event *goevents.Event, handler goevents.EventHandler)
func BuildRouter ¶ added in v0.3.2
func BuildRouter(config *Config)
func ChainMiddleware ¶
func CleanupVisitors ¶
func CleanupVisitors()
func DiscoverConfigFormat ¶
func HealthChecker ¶
func LoadConfigFile ¶ added in v0.3.2
func Ping ¶
func Ping(w http.ResponseWriter, r *http.Request)
ping returns a "pong" message consider registering this Handler for the health checking logic
func RateLimiterMiddleware ¶
func SerializeHealthCheckStatus ¶
func SerializeHealthCheckStatus(status *HealthCheckStatus) (string, error)
func ServeHTTPS ¶ added in v0.3.2
func ServeHTTPS(addr string, cm *CertManager) *http.Server
func StartDNSServer ¶ added in v0.3.2
func StartDNSServer(bind string, isLocal func(string) bool, forwardTo string) (stop func(), err error)
Starts UDP+TCP DNS servers and returns a stop function.
func SubscribeToEvent ¶ added in v0.3.2
func SubscribeToEvent(handler goevents.EventHandler, events ...*goevents.Event)
func WatchConfig ¶ added in v0.3.2
Types ¶
type BalancerStrategy ¶
type CertManager ¶ added in v0.3.2
type CertManager struct {
// contains filtered or unexported fields
}
func NewCertManager ¶ added in v0.3.2
func NewCertManager(cacheDir, email, envKey string) *CertManager
func (*CertManager) GetCertificate ¶ added in v0.3.2
func (m *CertManager) GetCertificate(chi *tls.ClientHelloInfo) (*tls.Certificate, error)
type Config ¶
type Config struct {
Servers []*Server `json:"server" yaml:"server"` // The servers instances
HealthCheckInterval int `json:"healthcheck_interval,omitempty" yaml:"healthcheck_interval,omitempty"`
LogOutput string `json:"log_output,omitempty" yaml:"log_output,omitempty"`
Stream bool `json:"stream,omitempty" yaml:"stream,omitempty"`
Services []*cloud.ServiceConfig `json:"services,omitempty" yaml:"services,omitempty"`
}
type ConfigRules ¶ added in v0.3.2
type ConfigRules map[string]ConfigRulesTarget
type ConfigRulesTarget ¶ added in v0.3.2
type DNSServer ¶ added in v0.3.2
type DNSServer struct {
// contains filtered or unexported fields
}
type HealthCheckStatus ¶
type HealthCheckStatus struct {
Pass []ServerStatus `json:"pass" yaml:"pass"` // Array of successful HealthCheck result
Fail []ServerStatus `json:"fail" yaml:"fail"` // Array of failure HealthCheck Result
CheckTime time.Time
Duration time.Duration
}
type MiddleWareName ¶ added in v0.3.1
type MiddleWareName string
const (
MogolyRatelimiter MiddleWareName = "mogoly:ratelimiter"
)
type Middleware ¶
type MiddlewareSets ¶ added in v0.3.1
type MiddlewareSets map[MiddleWareName]struct { Fn MogolyMiddleware Conf any }
var MiddlewaresList MiddlewareSets = MiddlewareSets{ MogolyRatelimiter: struct { Fn MogolyMiddleware Conf any }{ Fn: RateLimiterMiddleware, Conf: RateLimitMiddlewareConfig{}, }, }
type MogolyMiddleware ¶ added in v0.3.1
type RouterState ¶ added in v0.3.2
type RouterState struct {
// contains filtered or unexported fields
}
func GetRouter ¶ added in v0.3.2
func GetRouter() *RouterState
func (*RouterState) AddServer ¶ added in v0.3.2
func (rs *RouterState) AddServer(server *Server)
func (*RouterState) RemoveServer ¶ added in v0.3.2
func (rs *RouterState) RemoveServer(server *Server)
type Server ¶
type Server struct {
ID string // THe server ID based on its registration order
Name string `json:"name,omitempty" yaml:"name,omitempty"` // The server name
Protocol string `json:"protocol,omitempty" yaml:"protocol,omitempty"` // The protocol for the server this field can be `http` or `https`
Host string `json:"host,omitempty" yaml:"host,omitempty"` // The server host
Port int `json:"port,omitempty" yaml:"port,omitempty"` // The port on which the server is running
URL string `json:"url,omitempty" yaml:"url,omitempty"` // If this field is provided the URL will be used for request forwarding
IsHealthy bool `json:"is_healthy,omitempty" yaml:"is_healthy,omitempty"` // Specifying the server health check state
BalancingServers []*Server `json:"balance,omitempty" yaml:"balance,omitempty"` // If specified these servers will be used for load balancing request
Middlewares []Middleware `json:"middlewares,omitempty" yaml:"middlewares,omitempty"`
LastHealthCheck *time.Time
// contains filtered or unexported fields
}
func (*Server) AddNewBalancingServer ¶
func (*Server) CheckHealthAll ¶
func (server *Server) CheckHealthAll() (*HealthCheckStatus, error)
CheckHealthAll performs health checks without holding the lock during network I/O.
func (*Server) CheckHealthAny ¶
func (server *Server) CheckHealthAny(name string) (*ServerStatus, error)
func (*Server) CheckHealthSelf ¶
func (server *Server) CheckHealthSelf() (*ServerStatus, error)
func (*Server) DelBalancingServer ¶
func (*Server) GetNextServer ¶
GetNextServer returns the next healthy server using round-robin. If all are unhealthy or list is empty, an error is returned.
func (*Server) RollBack ¶
RollBack replaces the current balancing set with the provided list atomically.
func (*Server) RollBackAny ¶
RollBackAny replaces a named server with a new one, or appends when name is empty.
func (*Server) ServeHTTP ¶
func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements a minimal LB+proxy. It avoids mutating the receiver on retry and constructs the target URL using ResolveReference to handle paths and queries correctly.
func (*Server) UpgradeProxy ¶
UpgradeProxy ensures server.Proxy is initialized for this server.
type ServerStatus ¶
type ServerStatus struct {
Name string `json:"name" yaml:"name"` // The server name
Url string `json:"url" yaml:"url"` // HealthCheck url
Healthy bool `json:"healthy" yaml:"healthy"` // healthCheck status
}
The result of a health checking process for a server