httpserver

package
v0.0.16 Latest Latest
Warning

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

Go to latest
Published: Sep 18, 2025 License: Apache-2.0 Imports: 13 Imported by: 0

README

HTTP Server Runnable

An HTTP server that integrates with go-supervisor for lifecycle management.

Why Use This?

When a process receives a termination signal, it needs to stop accepting new connections while allowing in-flight requests to complete. This package provides an HTTP server that:

  • Integrates with go-supervisor's shutdown coordination
  • Reloads configuration without dropping connections
  • Reports its internal state for monitoring

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 from standard HTTP handlers
    indexRoute, _ := httpserver.NewRouteFromHandlerFunc("index", "/", indexHandler)
    statusRoute, _ := httpserver.NewRouteFromHandlerFunc("status", "/status", statusHandler)
    
    routes := httpserver.Routes{*indexRoute, *statusRoute}
    
    // Create config callback
    configCallback := func() (*httpserver.Config, error) {
        return httpserver.NewConfig(":8080", routes, httpserver.WithDrainTimeout(5*time.Second))
    }
    
    // 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),
        supervisor.WithContext(ctx),
    )
    
    // blocks until the supervisor receives a signal
    if err := super.Run(); err != nil {
        // Handle error appropriately
        panic(err)
    }
}

Middleware

The HTTP server uses a middleware pattern where handlers process requests in a chain. Middleware can intercept requests, modify responses, or abort processing.

Why Middleware Order Matters

Middleware forms a processing pipeline where each step can affect subsequent steps. The order determines both request flow (first → last) and response flow (last → first).

// Recovery must be first to catch panics from all subsequent middleware
// Headers set early ensure they're present even if later middleware fails
// Logging captures the actual request after security filtering
// Content headers set last prevent handlers from overriding them

import (
    "github.com/robbyt/go-supervisor/runnables/httpserver/middleware/recovery"
    "github.com/robbyt/go-supervisor/runnables/httpserver/middleware/headers"
    "github.com/robbyt/go-supervisor/runnables/httpserver/middleware/logger"
    "github.com/robbyt/go-supervisor/runnables/httpserver/middleware/metrics"
)

middlewares := []httpserver.HandlerFunc{
    recovery.New(lgr),        // Catches panics - must wrap everything
    headers.Security(),       // Security headers - always applied
    logger.New(lgr),          // Logs what actually gets processed
    metrics.New(),            // Measures performance
    headers.JSON(),           // Sets content type - easily overridden
}
Creating Custom Middleware

Middleware functions receive a RequestProcessor that controls the middleware chain. Each middleware must call either Next() or Abort().

Two-Phase Execution

Middleware runs in two phases:

  • Request phase: Code before Next() - runs while processing the incoming request
  • Response phase: Code after Next() - runs after the final handler and all subsequent middleware complete
func New() httpserver.HandlerFunc {
    return func(rp *httpserver.RequestProcessor) {
        // REQUEST PHASE: Runs before handler
        start := time.Now()
        
        // Continue to next middleware/handler
        rp.Next()
        
        // RESPONSE PHASE: Runs after handler completes
        duration := time.Since(start)
        rp.Writer().Header().Set("X-Process-Time", duration.String())
    }
}
Control Flow Methods
  • Next() - Continue to the next middleware in the chain (or final handler if last middleware)
  • Abort() - Stop processing immediately, skip all remaining middleware and handler
When to Abort

Use Abort() when requests should not continue processing:

func AuthMiddleware(requiredRole string) httpserver.HandlerFunc {
    return func(rp *httpserver.RequestProcessor) {
        if !isAuthorized(rp.Request(), requiredRole) {
            http.Error(rp.Writer(), "Forbidden", http.StatusForbidden)
            rp.Abort() // Stop here - don't call remaining middleware
            return
        }
        rp.Next() // Continue processing
    }
}
Available Methods
  • Request() - Access the HTTP request
  • Writer() - Access the enhanced response writer
  • IsAborted() - Check if processing was aborted by earlier middleware

The ResponseWriter extends http.ResponseWriter with:

  • Status() - Get the HTTP status code after writing
  • Size() - Get the number of bytes written
  • Written() - Check if response has been written

Configuration Reloading

The server implements hot reloading for configuration changes. When the supervisor receives SIGHUP or Reload() is called, the server:

  1. Fetches new configuration via the callback
  2. Updates routes without dropping connections
  3. Restarts the server only if the listen address changes

Configuration changes take effect without dropping existing connections.

State Monitoring

The server reports its lifecycle state through the Stateable interface. States progress through:

"New" -> "Booting" -> "Running" -> "Stopping" -> "Stopped"

Error states can occur at any point. Monitor state changes to coordinate with other services or implement health checks:

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

Configuration Options

The server supports various timeouts and custom server creation:

// Basic configuration with defaults
config, _ := httpserver.NewConfig(":8080", routes)

// Custom timeouts
config, _ := httpserver.NewConfig(
    ":8080",
    routes,
    httpserver.WithDrainTimeout(10*time.Second),  // Graceful shutdown period
    httpserver.WithReadTimeout(30*time.Second),   // Prevent slow clients
    httpserver.WithWriteTimeout(30*time.Second),  // Prevent slow writes
    httpserver.WithIdleTimeout(2*time.Minute),    // Clean up idle connections
)

// Custom server for TLS or HTTP/2
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,
        TLSConfig:    &tls.Config{
            MinVersion: tls.VersionTLS12,
        },
    }
}

config, _ := httpserver.NewConfig(
    ":8443",
    routes,
    httpserver.WithServerCreator(customServerCreator),
)

Examples

See examples/http/ or examples/custom_middleware/ for usage examples.

Documentation

Overview

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

This file defines the core middleware execution types.

RequestProcessor manages the middleware chain execution and provides access to request/response data. It handles the control flow ("when" middleware runs) while ResponseWriter (response_writer.go) handles data capture ("what" data is available).

When writing custom middleware, use RequestProcessor methods to: - Continue processing: rp.Next() - Stop processing: rp.Abort() - Access request: rp.Request() - Access response: rp.Writer()

Package httpserver provides HTTP server functionality with middleware support.

This file defines ResponseWriter, which wraps the standard http.ResponseWriter to capture response metadata that the standard interface doesn't expose.

Why This Wrapper Exists

The standard http.ResponseWriter doesn't provide access to the status code or byte count after they've been written. Middleware needs this information for logging, metrics, and conditional processing.

Relationship to Middleware

Middleware functions receive a RequestProcessor that contains this ResponseWriter. The wrapper allows middleware to inspect response state after handlers execute.

Relationship to RequestProcessor (context.go)

RequestProcessor manages middleware execution flow and provides access to this ResponseWriter through its Writer() method. The RequestProcessor handles the "when" of middleware execution, while ResponseWriter handles the "what" of response data capture.

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 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 are 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 HandlerFunc added in v0.0.10

type HandlerFunc func(*RequestProcessor)

HandlerFunc is the middleware/handler signature

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 wraps 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. Either this option or WithConfig initializes the Runner instance by providing the configuration for the HTTP server managed by the Runner.

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 RequestProcessor added in v0.0.10

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

RequestProcessor carries the request/response and middleware chain

func (*RequestProcessor) Abort added in v0.0.10

func (rp *RequestProcessor) Abort()

Abort prevents remaining handlers from being called

func (*RequestProcessor) IsAborted added in v0.0.10

func (rp *RequestProcessor) IsAborted() bool

IsAborted returns true if the request processing was aborted

func (*RequestProcessor) Next added in v0.0.10

func (rp *RequestProcessor) Next()

Next executes the remaining handlers in the chain

func (*RequestProcessor) Request added in v0.0.10

func (rp *RequestProcessor) Request() *http.Request

Request returns the HTTP request

func (*RequestProcessor) SetWriter added in v0.0.10

func (rp *RequestProcessor) SetWriter(w ResponseWriter)

SetWriter replaces the ResponseWriter for the request. This allows middleware to intercept and transform responses.

func (*RequestProcessor) Writer added in v0.0.10

func (rp *RequestProcessor) Writer() ResponseWriter

Writer returns the ResponseWriter for the request

type ResponseWriter added in v0.0.10

type ResponseWriter interface {
	http.ResponseWriter

	// Status returns the HTTP status code
	Status() int

	// Written returns true if the response has been written
	Written() bool

	// Size returns the number of bytes written
	Size() int
}

ResponseWriter wraps http.ResponseWriter with additional functionality

type Route

type Route struct {
	Path     string
	Handlers []HandlerFunc
	// contains filtered or unexported fields
}

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

func NewRouteFromHandlerFunc added in v0.0.10

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

NewRouteFromHandlerFunc creates a new Route with the given name, path, and handler. Optionally, it can include middleware functions. This is the preferred way to create routes in the httpserver package.

func (Route) Equal

func (r Route) Equal(other Route) bool

func (*Route) ServeHTTP added in v0.0.10

func (r *Route) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP adapts the route to work with standard http.Handler

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 an HTTP server with graceful shutdown, dynamic reconfiguration, and state monitoring. It implements the Runnable, Reloadable, and Stateable interfaces from the supervisor package.

func NewRunner

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

NewRunner creates a new HTTP server runner instance with the provided options.

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) GetStateChanWithTimeout added in v0.0.12

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

GetStateChanWithTimeout returns a channel that emits state 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 handles its lifecycle. It transitions through FSM states and returns when the server is stopped or encounters an error.

func (*Runner) Stop

func (r *Runner) Stop()

Stop signals the HTTP server to shut down by canceling its context.

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
middleware

Jump to

Keyboard shortcuts

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