upstream

package
v1.0.126 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package upstream implements parent-proxy chaining with failover and circuit-breaker protection (ADR-0002 extraction; engine was upstream.go in package main).

Culvert can route traffic through one or more parent HTTP proxies with automatic failover. When all upstreams are down the proxy falls back to direct connections. The package owns the pool state machine (round-robin selection, health flags, per-proxy circuit breakers) and the health-check loop; package main keeps the singleton, the transport wiring (applyUpstreamProxy), and persistence via admin_settings.

Configuration (config.yaml):

upstream:
  proxies:
    - url: "http://parent1.corp.com:3128"
    - url: "http://parent2.corp.com:3128"
  health_interval: "30s"
  circuit_breaker:
    threshold: 5      # failures before opening circuit
    timeout: "60s"    # how long circuit stays open before half-open probe

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatSummary

func FormatSummary(entries []Entry) string

FormatSummary returns a log-friendly summary like "2 proxies (parent1:3128, parent2:3128)".

func RunHealthCheckLoop

func RunHealthCheckLoop(ctx context.Context, pool *Pool, interval time.Duration)

RunHealthCheckLoop runs pool.HealthCheck at the given interval until ctx is cancelled, stopping the underlying ticker on exit. Extracted so the shutdown invariant — "the loop must exit on context cancellation" — is unit-testable without spinning up the rest of initUpstreamPool.

Defensive contract: returns immediately for a nil pool or a non-positive interval. The production caller (initUpstreamPool) already validates these, but the standalone helper guards itself so future callers cannot panic (nil-deref) or wedge on a zero-interval ticker. P1.3 / S4.UpstreamHealth.

Types

type CircuitBreaker

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

CircuitBreaker tracks consecutive failures for an upstream proxy.

func (*CircuitBreaker) Allow

func (cb *CircuitBreaker) Allow() bool

Allow returns true if the circuit permits a request.

func (*CircuitBreaker) Failures added in v1.0.48

func (cb *CircuitBreaker) Failures() int64

Failures returns the current consecutive-failure count. Exported for admin API / diagnostics surfacing; not used on the request path.

func (*CircuitBreaker) OpenedAt added in v1.0.48

func (cb *CircuitBreaker) OpenedAt() time.Time

OpenedAt returns when the circuit last tripped open (zero Time if it has never opened, or has since been reset by RecordSuccess). Exported for admin API / diagnostics surfacing; not used on the request path.

func (*CircuitBreaker) Params

func (cb *CircuitBreaker) Params() (threshold int, timeout time.Duration)

Params returns the breaker's configured threshold and timeout. Exported for the main-side persistence-contract tests (SetProxies must inherit Configure's params) and for diagnostics; not used on the request path.

func (*CircuitBreaker) RecordFailure

func (cb *CircuitBreaker) RecordFailure()

RecordFailure increments the failure count and opens the circuit if threshold is reached.

func (*CircuitBreaker) RecordSuccess

func (cb *CircuitBreaker) RecordSuccess()

RecordSuccess resets the failure count and closes the circuit.

func (*CircuitBreaker) State

func (cb *CircuitBreaker) State() string

State returns the current circuit state name.

type Config

type Config struct {
	Proxies        []Entry `yaml:"proxies" json:"proxies"`
	HealthInterval string  `yaml:"health_interval" json:"healthInterval"` // Go duration
	CircuitBreaker struct {
		Threshold int    `yaml:"threshold" json:"threshold"` // failures before open
		Timeout   string `yaml:"timeout" json:"timeout"`     // Go duration
	} `yaml:"circuit_breaker" json:"circuitBreaker"`
}

Config is the "upstream" section of config.yaml.

type Entry

type Entry struct {
	URL string `yaml:"url" json:"url"`
}

Entry is one parent proxy from config.yaml.

type Pool

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

Pool manages a set of parent proxies with failover. The zero value is a usable empty pool.

func (*Pool) CBParams

func (p *Pool) CBParams() (threshold int, timeout time.Duration)

CBParams returns the circuit-breaker parameters remembered from the last Configure. Exported for the main-side snapshot/restore test helper and for diagnostics; not used on the request path.

func (*Pool) Configure

func (p *Pool) Configure(entries []Entry, cbThreshold int, cbTimeout time.Duration)

Configure sets the list of upstream proxies and the circuit-breaker parameters (startup / YAML-reload / import path).

func (*Pool) Enabled

func (p *Pool) Enabled() bool

Enabled returns true if any upstream proxies are configured.

func (*Pool) Entries

func (p *Pool) Entries() []Entry

Entries returns a copy of the raw accepted entries. URLs may embed inline credentials — this is for persistence (admin_settings.json, mode 0600) only; use List() for anything user-facing.

func (*Pool) HealthCheck

func (p *Pool) HealthCheck()

HealthCheck runs a connectivity check against each upstream proxy. Called periodically from a background goroutine.

func (*Pool) List

func (p *Pool) List() []Status

List returns the current upstream proxy statuses for the UI/API. URLs are redacted (inline credentials stripped).

func (*Pool) Next

func (p *Pool) Next() *Proxy

Next returns the next healthy upstream proxy using round-robin selection. Returns nil if no healthy proxy is available (caller should fall back to direct).

func (*Pool) ProxyFunc

func (p *Pool) ProxyFunc() func(*http.Request) (*url.URL, error)

ProxyFunc returns an http.Transport-compatible proxy selector. When upstreams are configured, it returns the next healthy proxy URL. Falls back to nil (direct connection) when no upstream is available.

func (*Pool) SetProxies

func (p *Pool) SetProxies(entries []Entry)

SetProxies replaces the proxy list while keeping the circuit-breaker parameters from the last Configure (admin API / persisted-settings path).

type Proxy

type Proxy struct {
	URL     *url.URL
	Healthy atomic.Bool
	CB      *CircuitBreaker
}

Proxy represents one parent proxy in the chain.

type Status

type Status struct {
	URL     string `json:"url"`
	Healthy bool   `json:"healthy"`
	Circuit string `json:"circuit"`
	// Failures is the current consecutive-failure count tracked by the
	// circuit breaker (resets to 0 on RecordSuccess).
	Failures int64 `json:"failures"`
	// OpenedAtMs is when the circuit last tripped open, as Unix
	// milliseconds; 0 when the circuit is closed.
	OpenedAtMs int64 `json:"openedAtMs,omitempty"`
	// RetryAfterMs is the remaining time (ms) until the breaker allows a
	// half-open probe; 0 when the circuit is not open.
	RetryAfterMs int64 `json:"retryAfterMs,omitempty"`
}

Status is returned by the admin API.

Jump to

Keyboard shortcuts

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