Skip to main content

Overview

The maintenance middleware enables maintenance mode for your application, returning a 503 Service Unavailable response with optional whitelist support for IPs and paths.

Installation

import "github.com/go-mizu/mizu/middlewares/maintenance"

Quick Start

app := mizu.New()

// Enable maintenance mode
app.Use(maintenance.New(true))

Configuration

OptionTypeDefaultDescription
EnabledboolfalseEnable maintenance mode
Messagestring"Service is under maintenance"Response message
RetryAfterint3600Retry-After header in seconds
StatusCodeint503HTTP status code
Handlermizu.Handler-Custom maintenance handler
Whitelist[]string-Allowed IP addresses
WhitelistPaths[]string-Paths that bypass maintenance
Checkfunc() bool-Dynamic maintenance check

Examples

Basic Maintenance Mode

// Enabled from start
app.Use(maintenance.New(true))
// All requests return 503

Custom Message

app.Use(maintenance.WithOptions(maintenance.Options{
    Enabled: true,
    Message: "We're upgrading our systems. Back soon!",
}))

Custom Handler

app.Use(maintenance.WithOptions(maintenance.Options{
    Enabled: true,
    Handler: func(c *mizu.Ctx) error {
        return c.HTML(503, `
            <html>
                <head><title>Maintenance</title></head>
                <body>
                    <h1>Under Maintenance</h1>
                    <p>We'll be back shortly.</p>
                </body>
            </html>
        `)
    },
}))

Whitelist IPs

app.Use(maintenance.WithOptions(maintenance.Options{
    Enabled: true,
    Whitelist: []string{
        "192.168.1.100",    // Admin workstation
        "10.0.0.0/8",       // Internal network
    },
}))

Whitelist Paths

app.Use(maintenance.WithOptions(maintenance.Options{
    Enabled: true,
    WhitelistPaths: []string{
        "/health",          // Health checks
        "/status",          // Status endpoint
        "/api/webhooks",    // Critical webhooks
    },
}))

Dynamic Control

mode := maintenance.NewMode(maintenance.Options{
    Message: "Maintenance in progress",
})

app.Use(mode.Middleware())

// Enable/disable dynamically
app.Post("/admin/maintenance/enable", func(c *mizu.Ctx) error {
    mode.Enable()
    return c.Text(200, "Maintenance enabled")
})

app.Post("/admin/maintenance/disable", func(c *mizu.Ctx) error {
    mode.Disable()
    return c.Text(200, "Maintenance disabled")
})

app.Get("/admin/maintenance/status", func(c *mizu.Ctx) error {
    return c.JSON(200, map[string]bool{
        "enabled": mode.IsEnabled(),
    })
})

Toggle Maintenance

mode := maintenance.NewMode(maintenance.Options{})

app.Post("/admin/maintenance/toggle", func(c *mizu.Ctx) error {
    mode.Toggle()
    return c.JSON(200, map[string]bool{
        "enabled": mode.IsEnabled(),
    })
})

Scheduled Maintenance

// Maintenance window: Dec 15, 2024 2:00 AM - 4:00 AM UTC
start := time.Date(2024, 12, 15, 2, 0, 0, 0, time.UTC)
end := time.Date(2024, 12, 15, 4, 0, 0, 0, time.UTC)

app.Use(maintenance.ScheduledMaintenance(start, end))

Check Function

app.Use(maintenance.WithOptions(maintenance.Options{
    Check: func() bool {
        // Check external flag (e.g., from database or config)
        return config.IsMaintenanceMode()
    },
}))

Environment-Based

app.Use(maintenance.WithOptions(maintenance.Options{
    Check: func() bool {
        return os.Getenv("MAINTENANCE_MODE") == "true"
    },
    WhitelistPaths: []string{"/health"},
}))

With Retry-After

app.Use(maintenance.WithOptions(maintenance.Options{
    Enabled:    true,
    RetryAfter: 1800, // 30 minutes
}))
// Response includes: Retry-After: 1800

JSON Response

app.Use(maintenance.WithOptions(maintenance.Options{
    Enabled: true,
    Handler: func(c *mizu.Ctx) error {
        c.Writer().Header().Set("Retry-After", "3600")
        return c.JSON(503, map[string]any{
            "status":     "maintenance",
            "message":    "Service is under maintenance",
            "retry_after": 3600,
        })
    },
}))

API Reference

func New(enabled bool) mizu.Middleware
func WithOptions(opts Options) mizu.Middleware
func ScheduledMaintenance(start, end time.Time) mizu.Middleware
func NewMode(opts Options) *Mode

// Mode methods
func (m *Mode) Enable()
func (m *Mode) Disable()
func (m *Mode) Toggle()
func (m *Mode) IsEnabled() bool
func (m *Mode) Middleware() mizu.Middleware

Response Headers

HTTP/1.1 503 Service Unavailable
Retry-After: 3600
Content-Type: text/plain

Service is under maintenance

Technical Details

Implementation Architecture

The maintenance middleware is implemented with a layered approach for flexibility and performance:
  1. Static Configuration - Uses Options struct to configure enabled state, messages, status codes, and retry headers
  2. Dynamic Mode Control - Mode type provides thread-safe atomic operations for runtime enable/disable toggling
  3. Bypass Mechanisms - Implements two bypass layers:
    • IP-based whitelisting via Whitelist option (checks X-Forwarded-For, X-Real-IP, and RemoteAddr)
    • Path-based whitelisting via WhitelistPaths option (exact path matching)
  4. Custom Check Function - Supports dynamic maintenance state via Check function, which takes precedence over static Enabled flag
  5. Scheduled Maintenance - Uses time-based checks to automatically enable/disable maintenance during specific windows

Key Components

  • Options: Configuration struct with fields for enabled state, messages, status codes, handlers, and whitelists
  • Mode: Thread-safe maintenance mode controller using atomic.Int32 for concurrent read/write operations
  • WithOptions: Core middleware factory that processes configuration and returns the middleware function
  • ScheduledMaintenance: Helper function that creates time-windowed maintenance middleware
  • getClientIP: Helper function to extract client IP from various headers (X-Forwarded-For, X-Real-IP, RemoteAddr)
  • itoa: Custom integer-to-string conversion for Retry-After header (avoids fmt package overhead)

Execution Flow

  1. Check if maintenance is enabled (via Check function or Enabled flag)
  2. If disabled, pass request to next handler
  3. If enabled, check IP whitelist (bypass if matched)
  4. Check path whitelist (bypass if matched)
  5. Execute custom handler if provided, or return default 503 response with Retry-After header

Thread Safety

The Mode type uses atomic operations for thread-safe state management:
  • Enable() and Disable() use atomic.StoreInt32
  • IsEnabled() uses atomic.LoadInt32
  • Toggle() uses atomic.CompareAndSwapInt32 for lock-free toggling

Best Practices

  • Whitelist health check endpoints for monitoring
  • Use scheduled maintenance for planned downtime
  • Whitelist admin IPs for debugging
  • Set appropriate Retry-After values
  • Provide helpful maintenance messages
  • Use dynamic control for emergency maintenance

Testing

The maintenance middleware includes comprehensive test coverage for all scenarios:
Test CaseDescriptionExpected Behavior
TestNewBasic maintenance mode enabledReturns 503 with default message “Service is under maintenance”
TestNew_DisabledMaintenance mode disabledReturns 200 and passes through to handler
TestWithOptions_CustomMessageCustom maintenance messageReturns 503 with custom message “We’ll be back soon!”
TestWithOptions_RetryAfterCustom retry-after valueSets Retry-After header to 7200 seconds
TestWithOptions_Whitelist (whitelisted IP)IP in whitelist accesses endpointReturns 200 and bypasses maintenance mode
TestWithOptions_Whitelist (non-whitelisted IP)IP not in whitelist accesses endpointReturns 503 in maintenance mode
TestWithOptions_WhitelistPaths (whitelisted path)Access to /health whitelisted pathReturns 200 and bypasses maintenance mode
TestWithOptions_WhitelistPaths (non-whitelisted path)Access to /api non-whitelisted pathReturns 503 in maintenance mode
TestWithOptions_CustomHandlerCustom handler for maintenance responseReturns JSON response with custom handler
TestModeMode creation, enable, disable, toggleCorrectly changes state: disabled by default, enabled after Enable(), disabled after Disable(), enabled after Toggle()
TestMode_Middleware (disabled)Middleware with Mode disabledReturns 200 and passes through
TestMode_Middleware (enabled)Middleware with Mode enabledReturns 503 in maintenance mode
TestScheduledMaintenanceScheduled maintenance in the pastReturns 200 as maintenance period has ended