httpserver

package
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 28, 2025 License: Apache-2.0 Imports: 14 Imported by: 0

README

HTTP Server Runnable

This package provides a ready-to-use HTTP server implementation that integrates with the go-supervisor framework. The HTTP server runnable supports configuration reloading, state management, and graceful shutdown.

Features

  • Configurable HTTP routes with path matching
  • Middleware support for request processing
  • Dynamic route configuration and hot reloading
  • Graceful shutdown with configurable timeout
  • State reporting and monitoring
  • Wildcard route support for catch-all handlers
  • Built-in middlewares for common tasks

Basic Usage

package main

import (
    "context"
    "fmt"
    "net/http"
    "time"

    "github.com/robbyt/go-supervisor"
    "github.com/robbyt/go-supervisor/runnables/httpserver"
)

func main() {
    // Create HTTP handlers
    indexHandler := func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Welcome to the home page!")
    }
    
    statusHandler := func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Status: OK")
    }
    
    // Create routes
    indexRoute, _ := httpserver.NewRoute("index", "/", indexHandler)
    statusRoute, _ := httpserver.NewRoute("status", "/status", statusHandler)
    
    routes := httpserver.Routes{*indexRoute, *statusRoute}
    
    // Create config callback
    configCallback := func() (*httpserver.Config, error) {
        return httpserver.NewConfig(":8080", 5*time.Second, routes)
    }
    
    // Create HTTP server runner
    hRunner, _ := httpserver.NewRunner(
        httpserver.WithConfigCallback(configCallback),
    )
    
    // create a supervisor instance and add the runner
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    super, _ := supervisor.New(
        supervisor.WithRunnables(hRunner), // Remove the spread operator '...'
        supervisor.WithContext(ctx),
    )
    
    // blocks until the supervisor receives a signal
    if err := super.Run(); err != nil {
        // Handle error appropriately
        panic(err)
    }
}

Using Middleware

The HTTP server supports middleware chains for request processing:

// Import the middleware package
import "github.com/robbyt/go-supervisor/runnables/httpserver/middleware"

// Create a route with middleware
route, _ := httpserver.NewRouteWithMiddlewares(
    "index",
    "/",
    indexHandler,
    middleware.LoggingMiddleware,
    middleware.RecoveryMiddleware,
    middleware.MetricsMiddleware,
)
Built-in Middlewares

All middlewares are in the middleware package (runnables/httpserver/middleware):

  • LoggingMiddleware: Logs request method, path, status code, and duration
  • RecoveryMiddleware: Recovers from panics in handlers with stack trace logging
  • MetricsMiddleware: Tracks request counts and response codes
  • StateMiddleware: Adds server state information to response headers
  • WildcardMiddleware: Provides prefix-based routing for catch-all handlers

All middleware components have 100% test coverage and follow a consistent design pattern with wrapped response writers.

Custom Middleware

You can create custom middleware functions following the same pattern as the built-in middlewares:

import (
    "net/http"
    
    "github.com/robbyt/go-supervisor/runnables/httpserver/middleware"
)

// For simple middleware without response writer wrapping
func MyCustomMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Do something before request handling
        next(w, r)
        // Do something after request handling
    }
}

// For middleware that needs to capture response details
func MyAdvancedMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Wrap the response writer to capture status code and written bytes
        rw := middleware.NewResponseWriter(w)
        
        // Do something before request handling
        next(rw, r)
        
        // After handling, we can access status code and written bytes
        statusCode := rw.Status()
        bytesWritten := rw.BytesWritten()
        
        // ...
    }
}

Configuration Reloading

The HTTP server runnable implements the supervisor.Reloadable interface. The go-supervisor instance managing this runnable will automatically trigger its Reload() method when the supervisor receives a SIGHUP signal.

When Reload() is called (either by the supervisor via a HUP signal or programmatically), the runner calls the configuration callback function to fetch the latest configuration. If the configuration has changed, the underlying HTTP server may be gracefully shut down if ports changed, or the routes will be reloaded if the configuration is the same.

// Trigger a reload
if runner, ok := service.(*httpserver.Runner); ok {
    runner.Reload()
}

State Management

The HTTP server implements the Stateable interface and reports its state as one of:

  • "New"
  • "Booting"
  • "Running"
  • "Stopping"
  • "Stopped"
  • "Error"

You can monitor state changes:

stateChan := runner.GetStateChan(ctx)
go func() {
    for state := range stateChan {
        fmt.Printf("HTTP server state: %s\n", state)
    }
}()

Configuration Options

The HTTP server runner uses a functional options pattern - see godoc for complete documentation of each option. Common usage patterns:

// Dynamic configuration with a callback function (supports hot reloading)
runner, _ := httpserver.NewRunner(
    httpserver.WithContext(context.Background()),
    httpserver.WithConfigCallback(configCallback),
)

// Static configuration (simpler when hot reloading isn't needed)
config, _ := httpserver.NewConfig(
    ":8080",            // Listen address
    routes,              // HTTP routes
    httpserver.WithDrainTimeout(5*time.Second), // Optional settings
)
runner, _ := httpserver.NewRunner(
    httpserver.WithContext(context.Background()),
    httpserver.WithConfig(config),
)

Server Configuration with Functional Options

The HTTP server config uses the Functional Options pattern, which provides a clean and flexible way to configure server settings. The pattern allows for sensible defaults while making it easy to customize specific settings.

Basic Configuration

The NewConfig function now accepts an address, routes, and optional configuration options:

// Create a config with just the required parameters (uses default timeouts)
config, _ := httpserver.NewConfig(":8080", routes)

// Create a config with custom drain timeout
config, _ := httpserver.NewConfig(
    ":8080",            // Listen address
    routes,              // HTTP routes
    httpserver.WithDrainTimeout(10*time.Second),
)

// Create a config with multiple custom settings
config, _ := httpserver.NewConfig(
    ":8080",
    routes,
    httpserver.WithDrainTimeout(10*time.Second),
    httpserver.WithReadTimeout(30*time.Second),
    httpserver.WithWriteTimeout(30*time.Second),
    httpserver.WithIdleTimeout(2*time.Minute),
)
Available Configuration Options

The following functional options can be used with NewConfig:

  • WithDrainTimeout(timeout time.Duration): Sets the drain timeout for graceful shutdown
  • WithReadTimeout(timeout time.Duration): Sets the read timeout for the HTTP server
  • WithWriteTimeout(timeout time.Duration): Sets the write timeout for the HTTP server
  • WithIdleTimeout(timeout time.Duration): Sets the idle connection timeout for the HTTP server
  • WithServerCreator(creator ServerCreator): Sets a custom server creator function
Custom Server Creator

You can provide a custom server creation function to configure advanced HTTP server settings:

// Create a custom server creator
customServerCreator := func(addr string, handler http.Handler, cfg *httpserver.Config) httpserver.HttpServer {
    return &http.Server{
        Addr:         addr,
        Handler:      handler,
        ReadTimeout:  cfg.ReadTimeout,
        WriteTimeout: cfg.WriteTimeout,
        IdleTimeout:  cfg.IdleTimeout,
        // Add any additional custom settings here
    }
}

// Use the custom server creator in the config
config, _ := httpserver.NewConfig(
    ":8080",
    routes,
    httpserver.WithServerCreator(customServerCreator),
)
TLS Configuration Example
// Create a server with TLS configuration
tlsServerCreator := func(addr string, handler http.Handler, cfg *httpserver.Config) httpserver.HttpServer {
    return &http.Server{
        Addr:         addr,
        Handler:      handler,
        ReadTimeout:  cfg.ReadTimeout,
        WriteTimeout: cfg.WriteTimeout,
        IdleTimeout:  cfg.IdleTimeout,
        TLSConfig: &tls.Config{
            MinVersion: tls.VersionTLS12,
            CurvePreferences: []tls.CurveID{
                tls.CurveP256,
                tls.X25519,
            },
        },
    }
}

// Use the TLS server creator
runner, _ := httpserver.NewRunner(
    httpserver.WithConfigCallback(configCallback),
    httpserver.WithServerCreator(tlsServerCreator),
)
HTTP/2 Support Example
// Create a server with HTTP/2 support
http2ServerCreator := func(addr string, handler http.Handler) httpserver.HttpServer {
    server := &http.Server{
        Addr:    addr,
        Handler: handler,
    }
    
    // Enable HTTP/2 support
    http2.ConfigureServer(server, &http2.Server{})
    
    return server
}

// Use the HTTP/2 server creator
runner, _ := httpserver.NewRunner(
    httpserver.WithConfigCallback(configCallback),
    httpserver.WithServerCreator(http2ServerCreator),
)

Full Example

See examples/http/main.go for a complete example.

Documentation

Overview

Package httpserver provides a configurable, reloadable HTTP server implementation that can be managed by the supervisor package.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNoConfig                = errors.New("no config provided")
	ErrNoHandlers              = errors.New("no handlers provided")
	ErrGracefulShutdown        = errors.New("graceful shutdown failed")
	ErrGracefulShutdownTimeout = errors.New("graceful shutdown deadline reached")
	ErrHttpServer              = errors.New("http server error")
	ErrOldConfig               = errors.New("config hasn't changed since last update")
	ErrRetrieveConfig          = errors.New("failed to retrieve server configuration")
	ErrCreateConfig            = errors.New("failed to create server configuration")
	ErrServerNotRunning        = errors.New("http server is not running")
	ErrServerReadinessTimeout  = errors.New("server readiness check timed out")
	ErrServerBoot              = errors.New("failed to start HTTP server")
	ErrConfigCallbackNil       = errors.New("config callback returned nil")
	ErrConfigCallback          = errors.New("failed to load configuration from callback")
	ErrStateTransition         = errors.New("state transition failed")
)

Functions

This section is empty.

Types

type Config

type Config struct {
	// Core configuration
	ListenAddr   string
	DrainTimeout time.Duration
	Routes       Routes

	// Server settings
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
	IdleTimeout  time.Duration

	// Server creation callback function
	ServerCreator ServerCreator
	// contains filtered or unexported fields
}

Config is the main configuration struct for the HTTP server

func NewConfig

func NewConfig(addr string, routes Routes, opts ...ConfigOption) (*Config, error)

NewConfig creates a new Config with the required address and routes plus any optional configuration via functional options

func (*Config) Equal

func (c *Config) Equal(other *Config) bool

Equal compares this Config with another and returns true if they are equivalent.

func (*Config) String

func (c *Config) String() string

String returns a human-readable representation of the Config

type ConfigCallback

type ConfigCallback func() (*Config, error)

ConfigCallback is the function type signature for the callback used to load initial config, and new config during Reload()

type ConfigOption

type ConfigOption func(*Config)

ConfigOption defines a functional option for configuring Config

func WithConfigCopy

func WithConfigCopy(src *Config) ConfigOption

WithConfigCopy creates a ConfigOption that copies most settings from the source config except for ListenAddr and Routes which must be provided directly to NewConfig.

func WithDrainTimeout

func WithDrainTimeout(timeout time.Duration) ConfigOption

WithDrainTimeout sets the drain timeout for graceful shutdown

func WithIdleTimeout

func WithIdleTimeout(timeout time.Duration) ConfigOption

WithIdleTimeout sets the idle timeout for the HTTP server

func WithReadTimeout

func WithReadTimeout(timeout time.Duration) ConfigOption

WithReadTimeout sets the read timeout for the HTTP server

func WithRequestContext

func WithRequestContext(ctx context.Context) ConfigOption

WithRequestContext sets the context that will be propagated to all request handlers via http.Server's BaseContext. This allows handlers to be aware of server shutdown.

func WithServerCreator

func WithServerCreator(creator ServerCreator) ConfigOption

WithServerCreator sets a custom server creator for the HTTP server

func WithWriteTimeout

func WithWriteTimeout(timeout time.Duration) ConfigOption

WithWriteTimeout sets the write timeout for the HTTP server

type HttpServer

type HttpServer interface {
	ListenAndServe() error
	Shutdown(ctx context.Context) error
}

HttpServer is the interface for the HTTP server

func DefaultServerCreator

func DefaultServerCreator(addr string, handler http.Handler, cfg *Config) HttpServer

DefaultServerCreator creates a standard http.Server instance with the settings from Config

type Option

type Option func(*Runner)

Option represents a functional option for configuring Runner.

func WithConfig

func WithConfig(cfg *Config) Option

WithConfig sets the initial configuration for the Runner instance. This option is a simple wrapper for the WithConfigCallback option, allowing you to pass a Config instance directly instead of a callback function. This is useful when you have a static configuration that doesn't require dynamic loading or reloading.

func WithConfigCallback

func WithConfigCallback(callback ConfigCallback) Option

WithConfigCallback sets the function that will be called to load or reload configuration. Using this option or WithConfig is required to initialize the Runner instance, because it provides the configuration for the HTTP server managed by the Runner.

func WithContext

func WithContext(ctx context.Context) Option

WithContext sets a custom context for the Runner instance. This allows for more granular control over cancellation and timeouts.

func WithLogHandler

func WithLogHandler(handler slog.Handler) Option

WithLogHandler sets a custom slog handler for the Runner instance. For example, to use a custom JSON handler with debug level:

handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})
runner, err := httpserver.NewRunner(ctx, httpserver.WithConfigCallback(configCallback), httpserver.WithLogHandler(handler))

func WithName

func WithName(name string) Option

WithName sets the name of the Runner instance.

type Route

type Route struct {
	Path    string
	Handler http.HandlerFunc
	// contains filtered or unexported fields
}

Route represents a single HTTP route with a name, path, and handler function.

func NewRoute

func NewRoute(name string, path string, handler http.HandlerFunc) (*Route, error)

NewRoute creates a new Route with the given name, path, and handler. The name must be unique, the path must be non-empty, and the handler must not be nil.

func NewRouteWithMiddleware

func NewRouteWithMiddleware(
	name string,
	path string,
	handler http.HandlerFunc,
	middlewares ...middleware.Middleware,
) (*Route, error)

NewRouteWithMiddleware creates a new Route with the given name, path, handler, and applies the provided middlewares to the handler in the order they are provided.

func NewWildcardRoute

func NewWildcardRoute(
	prefix string,
	handler http.HandlerFunc,
	middlewares ...middleware.Middleware,
) (*Route, error)

NewWildcardRoute creates a route that handles everything under /prefix/*. Clients calling /prefix/foo/bar will match this route.

func (Route) Equal

func (r Route) Equal(other Route) bool

type Routes

type Routes []Route

Routes is a map of paths as strings, that route to http.HandlerFuncs

func (Routes) Equal

func (r Routes) Equal(other Routes) bool

Equal compares two routes and returns true if they are equal, false otherwise. This works because we assume the route names uniquely identify the route. For example, the route name could be based on a content hash or other unique identifier.

func (Routes) String

func (r Routes) String() string

String returns a string representation of all routes, including their versions and paths.

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner implements a configurable HTTP server that supports graceful shutdown, dynamic reconfiguration, and state monitoring. It meets the Runnable, Reloadable, and Stateable interfaces from the supervisor package.

func NewRunner

func NewRunner(opts ...Option) (*Runner, error)

NewRunner initializes a new HTTPServer runner instance.

func (*Runner) GetState

func (r *Runner) GetState() string

GetState returns the status of the HTTP server

func (*Runner) GetStateChan

func (r *Runner) GetStateChan(ctx context.Context) <-chan string

GetStateChan returns a channel that emits the HTTP server's state whenever it changes. The channel is closed when the provided context is canceled.

func (*Runner) IsRunning

func (r *Runner) IsRunning() bool

IsRunning returns true if the HTTP server is currently running.

func (*Runner) Reload

func (r *Runner) Reload()

Reload refreshes the server configuration and restarts the HTTP server if necessary. This method is safe to call while the server is running and will handle graceful shutdown and restart.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context) error

Run starts the HTTP server and listens for incoming requests

func (*Runner) Stop

func (r *Runner) Stop()

Stop will cancel the parent context, which will close the HTTP server

func (*Runner) String

func (r *Runner) String() string

String returns a string representation of the HTTPServer instance

type ServerCreator

type ServerCreator func(addr string, handler http.Handler, cfg *Config) HttpServer

ServerCreator is a function type that creates an HttpServer instance

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL