proxypool

package module
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: MIT Imports: 20 Imported by: 0

README

proxypool

A high-performance, modular Go proxy pool library designed for public and private proxy management. Built to handle the harsh realities of public proxy scraping: extreme mortality rates, ephemeral flapping, and zombie server accumulation.


Features

  • Two-Phase Fast Verification: Per-protocol tunnel handshakes (raw CONNECT for HTTP/S, in-house deadline-bounded SOCKS4/4a and SOCKS5 with optional user:pass auth) to weed out dead nodes, followed by dual-endpoint probing through each proxy's own transport.
  • Auto-Discovery of Geolocation: Discovers proxy exit countries on-the-fly using Cloudflare's trace diagnostic endpoint without external GeoIP databases.
  • Continuous Time-Normalized Scoring ($\Delta t$): Immunity against rapid refresh spam and long pauses; reputation scales strictly with elapsed observation time.
  • Grace Buffering for Veteran Proxies: A reputation credit system shields reliable servers from being exiled over transient network drops or short outages.
  • Truncated Exponential Backoff with Jitter: De-synchronizes dead "zombie" revival spikes and pushes offline servers into sleep intervals of up to 48 hours.
  • Pluggable Architecture: Fully decoupled ProxySource, CacheSource, and structured Reporter contracts with four scheduler lanes.

Installation

go get github.com/TheSlopMachine/proxypool

Quickstart

package main

import (
	"fmt"
	"time"

	"github.com/TheSlopMachine/proxypool"
)

func main() {
	// 1. Initialize pool with a storage backend (nil cache is an error)
	pool, err := proxypool.NewPool(proxypool.NewMemoryCache())
	if err != nil {
		panic(err)
	}

	// 2. Register one or more proxy sources
	pool.RegisterProxySource(NewMyProxySource())

	// 3. Trigger verification cycle
	pool.Refresh()

	// 4. Query working proxies with optional criteria
	targetCountry := "DE"
	maxLatency := 600 * time.Millisecond
	minScore := 5.0
	limit := 10

	proxies := pool.ListProxies(proxypool.ProxyFilter{
		Location:   &targetCountry,
		MaxLatency: &maxLatency,
		MinScore:   &minScore,
		Limit:      &limit,
	})

	for _, p := range proxies {
		fmt.Printf("Proxy: %s | Country: %s | Latency: %v | Score: %.2f\n",
			p.URL, p.Location, p.Latency, p.Score)
	}
}

Architecture & Extensibility

The library delegates storage, feed acquisition, and telemetry to clean interfaces, while retaining internal control over proxy validation and scoring.

1. ProxySource Contract

A ProxySource provides raw proxy URLs to the pool. It is source-agnostic—whether it scrapes an HTML table, reads a local file, or polls an API with conditional headers:

type ProxySource interface {
	Name() string
	FetchList() []string // Return empty slice if no changes occurred
}
Traffic Optimization (200 OK vs. 304 Not Modified)

Because the pool is completely agnostic to upstream transport protocols, conditional HTTP caching is the source's responsibility. If your upstream provider supports ETag or If-Modified-Since, track these headers inside your ProxySource:

type MyAPISource struct {
	url     string
	etag    string
	lastMod string
}

func (s *MyAPISource) FetchList() []string {
	req, _ := http.NewRequest("GET", s.url, nil)
	if s.etag != "" {
		req.Header.Set("If-None-Match", s.etag)
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil || resp.StatusCode == http.StatusNotModified {
		return nil // 304: zero JSON downloaded or parsed
	}
	defer resp.Body.Close()

	s.etag = resp.Header.Get("ETag")
	// parse and return []string...
}

When an empty slice is returned, the pool bypasses ingestion and evaluates only local proxies due for health checks or revivals.

Incoming raw URLs are automatically canonicalized by the pool (lowercase http/https/socks4/socks5 schemes, preserved user[:pass]@ credentials, default ports 80/443/1080, and standard host:port extraction), ensuring deduplication across multiple sources.


2. CacheSource Contract

CacheSource is a passive key-value state store. It must not alter, mutate, or validate incoming data.

type CacheSource interface {
	Get(url string) (ProxyState, bool)
	Set(state ProxyState)
	All() []ProxyState
	Delete(url string)
	Clear()
}
  • Storage: NewPool(cache) takes the storage backend as a constructor argument and returns an error on a nil cache. NewMemoryCache() supplies the default concurrent-safe in-memory backend.
  • Custom backends: Pass any CacheSource (e.g., Redis, SQLite, PostgreSQL) directly to NewPool; the pool wraps it without runtime migration between backends.

3. Reporter Contract

The pool exposes structured telemetry hooks for completed checks, periodic pool statistics, and state transitions:

type Reporter interface {
	ReportProxy(report ProxyReport)
	ReportStats(stats PoolStats)
	ReportEvent(event PoolEvent)
}

ReportProxy receives one event per completed proxy check. ReportStats receives aggregate state snapshots on Config.StatsInterval. ReportEvent receives mode, network, limiter, ingest, and breaker transitions.

4. Scheduler and Modes

Start(ctx) launches four independent lanes: foreground and background consume the candidate queue, liveness handles due alive or suspect proxies, and revival handles forced checks and due banned proxies. A single adaptive limiter caps total concurrency across all lanes.

The pool starts in foreground. It switches to background when the alive count reaches HighWater, and returns to foreground below LowWater. When foreground is starved of candidates, revival can use the majority of the concurrency budget to recover banned proxies.

RecheckBanned(BanFilter) queues explicit revival checks without extending or shortening ReviveAt on failed forced checks.

5. TimeoutConfig Contract

Network budgets live in one struct — the single source of truth (defaults: 3s handshake, 5s probe). Concurrency is controlled by the adaptive limiter:

pool.SetTimeout(proxypool.TimeoutConfig{Handshake: 3 * time.Second, Probe: 5 * time.Second})
pool.SetLimits(10, 50, 500)

Supported schemes are centralized too: feeds filter with proxypool.IsSupportedScheme (socks4a canonicalizes to socks4).


Demands: Require

Require(ctx, RequireOptions) hands out alive proxies for callers that need a specific exit country.

  • Countries is an OR list of ISO codes; empty accepts any country. Invalid codes return ErrInvalidCountry.
  • Exclude lists proxy URLs that are never returned to this caller. Limit caps the number of distinct proxies returned.
  • A matching alive proxy is returned immediately. Otherwise the call registers a demand and waits until a match becomes alive, Timeout (default Config.RequireTimeout, 60s) elapses, or ctx is done.
  • Timeout returns TimedOut=true with a nil error. The caller decides the fallback; the pool never falls back to a direct connection.
  • Waiters with the same country set share one demand. Cancelling ctx releases only the waiter; the demand stays registered for Config.DemandTTL (default 5m). Config.MaxDemands caps distinct demands (ErrDemandLimit).
  • Registered demands appear in PoolStats.Demands; demand_created, demand_satisfied, demand_expired and require_timeout are reported as PoolEvents.
Demand scheduling

Registered demands steer the scheduler without changing the pool modes:

  • A demand is hot while it has waiters, or while nothing matches inside Config.DemandTTL. A hot demand switches lane gating to the foreground profile (candidates 65%, liveness 15%, revival 20% of the limit).
  • A demand is replenishing while fewer than Want (default Config.CountryTarget) alive proxies match, until Config.ReplenishTTL after its last use. In background mode this multiplies candidate intensity by 4; at the target it returns to 1x.
  • Banned proxies whose last known Location matches are force-rechecked first (soft failures first, at most 20 per 2s, once per 2 minutes each). Failed forced rechecks leave Penalty and ReviveAt unchanged.
  • Candidates whose source hint (TaggedURL.Country) matches a demanded country are checked before the rest. The hint only orders the queue; the probe sets the real Location.
  • demand_priority events report idle, hot and replenish transitions; PoolStats.Demands carries per-demand State, Alive and Target.

The Mathematics & Design

Public proxy lists exhibit extreme behavior: ~98% of scraped proxies are dead servers, newly discovered proxies have a 75%+ first-minute mortality rate, and fewer than 1% remain viable over 8+ hours.

The engine uses several targeted techniques to stabilize this environment:

1. Two-Phase Dual-Endpoint Probing

Supported proxy schemes: http, https, socks4 (socks4a canonicalizes to socks4 with remote hostname resolution), socks5. Credentials embedded as user[:pass]@ apply to every scheme. Omitted ports default to 80 / 443 / 1080 (SOCKS).

  1. Phase 1: Tunnel Handshake (Fast Filter) Attempts a tunnel to 1.1.1.1:443 through the proxy with a strict 4-second timeout: a raw TCP CONNECT handshake for HTTP/S (first status line read from a stack-allocated buffer without heap allocations or TLS overhead), a SOCKS4 CONNECT request expecting grant 0x5A, or a SOCKS5 CONNECT (RFC 1928, RFC 1929 auth when userinfo is present). Fails dead nodes in milliseconds.
  2. Phase 2: Dual-Endpoint Probe
    • If Location == "" (New Node): Probes https://www.cloudflare.com/cdn-cgi/trace. Measures real HTTPS round-trip time and parses loc=XX to discover the exit country.
    • If Location != "" (Known Node): Probes http://cp.cloudflare.com/generate_204. A zero-body HTTP 204 check that updates latency and uptime scores with minimal bandwidth.

2. Continuous Time-Normalized Scaling ($\Delta t$)

To prevent rapid polling loops (e.g., a 5-second tick) from inflating scores or penalizing momentary drops too heavily, state transitions scale with elapsed time:

$$\Delta t = \frac{\text{now} - \text{lastCheckedAt}}{1\text{ minute}}$$

$$\delta = \begin{cases} 1.0 & \text{if first check (lastCheckedAt is zero)} \ \min(1.0, ; \max(0.01, ; \Delta t)) & \text{otherwise} \end{cases}$$

  • Anti-Spam ($\Delta t < 1.0$): Twelve consecutive 5-second checks only accumulate $\mathbf{1.0}$ point total.
  • Stale Absence Cap ($\Delta t > 1.0$): Capping $\delta$ at $1.0$ guarantees that after an offline weekend, a single check cannot award or deduct thousands of points.

3. The Score vs. Penalty Ratio Model

A penalty-only backoff punishes good proxies unfairly during momentary router restarts or transient Cloudflare rate limits.

We decouple health into two interacting variables:

  • Score $[0.0, 10.0]$: Earned incrementally on successful checks.
  • Penalty $[0.0, \infty)$: Incremented on failures.

$$\text{baseMinutes} = \max\left(1.0, ; \min\left(2880.0, ; 15.0 \cdot \frac{2^{\text{Penalty}}}{2.5^{\text{Score}}}\right)\right)$$

Because $2.5 > 2.0$, score is weighted more heavily than penalty:

  • The Veteran Shield: A proxy with Score = 10.0 that fails will evaluate to a 1-minute ban ($\text{base} \approx 0.007\text{m}$, clamped to $1.0\text{m}$). It stays in the active retry loop to catch quick recoveries. If the outage continues, its score gradually depletes until the exponential penalty takes over.
  • The Zombie Hammer: A dead proxy with Score = 0.0 ramps exponentially on consecutive failures ($30\text{m} \to 60\text{m} \to 120\text{m} \to 240\text{m}$) up to a 48-hour ceiling (2,880m).

4. Randomized Jitter Dispersion

When thousands of dead proxies are imported in an initial batch, a deterministic backoff causes them to revive at the exact same minute ($+30\text{m}$, $+60\text{m}$, $+120\text{m}$), producing massive network spikes.

The engine applies a continuous $\pm 15%$ uniform random jitter to all bans over 1 minute:

$$\text{jitter} = 0.85 + (0.30 \times \text{rand}())$$

$$\text{banMinutes} = \max(1.0, ; \text{baseMinutes} \times \text{jitter})$$

This smooths out periodic surges into a continuous, low-overhead background stream.


5. Manual Exclusion (MarkDead)

Probes cannot observe every fault: a proxy passing TCP handshakes and liveness checks may still break a specific upstream (e.g., TLS interception that only fails certificate verification). MarkDead records such upstream-observed faults on a channel orthogonal to probe scoring: it flips IsDead, extends ReviveAt on an escalating schedule ($15\text{m} \to 4\text{h}$ cap), and tracks its own counter in metadata. It never touches Score or Penalty, so neither the Veteran Shield nor probe healing can undo a manual mark, and manual marks never corrupt earned reputation. Repeat marks escalate; a full idle day forgives. Refresh write-back merges newer manual marks over stale pipeline snapshots instead of overwriting them.


Research: Proxy Lab & Visualizer

The repository includes a companion research tool in cmd/proxy_lab/ used to run empirical benchmarks and tune scoring parameters.

It streams pipe-delimited telemetry rows into a CSV file, assigning persistent sequential 3-letter identifiers (AAA, AAB ... ZZZ) to individual proxies to monitor long-term behavior.

go run .\cmd\proxy_lab\main.go -interval 5m -samples 100 -table table.csv

You can inspect the generated telemetry using the standalone web dashboard:

👉 Interactive Dashboard Visualizer: theslopmachine.github.io/proxypool/web/proxy_viewer.html

(The visualizer runs client-side in your browser; drop your generated CSV into the page to plot latency distributions, pool churn, and stability curves).


License

This project is licensed under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidCountry reports a country that is not a two-letter ISO code.
	ErrInvalidCountry = errors.New("invalid country code")
	// ErrDemandLimit reports that every demand slot is held by a demand with waiters.
	ErrDemandLimit = errors.New("too many active proxy demands")
)

Functions

func IsSupportedScheme added in v1.0.5

func IsSupportedScheme(scheme string) bool

IsSupportedScheme reports whether scheme names a probed protocol, canonicalizing case and the socks4a alias.

func TransportFor added in v1.0.4

func TransportFor(proxyURL string, timeout time.Duration) (*http.Transport, error)

TransportFor builds an HTTP transport routing through the given proxy URL. Supported schemes: http, https, socks4 (socks4a canonicalizes to socks4), socks5. Credentials embedded as userinfo apply to every scheme.

Every network phase is bounded by timeout: TCP connect (Dialer.Timeout), TLS handshake (TLSHandshakeTimeout), first response byte (ResponseHeaderTimeout), and the SOCKS greeting/CONNECT exchange (absolute conn deadline in dialContext). This closes the hang where a proxy accepts TCP then stalls forever — previously severing the host network (WireGuard flap) was the only thing that unblocked parked workers.

Types

type BanFilter added in v1.0.6

type BanFilter struct {
	Reason FailReason
	Source string
}

ProxyFilter defines search criteria used when querying the pool. All fields are optional pointers; nil allows all values. BanFilter selects banned proxies for manual revival checks. Empty fields match any value.

type CacheSource

type CacheSource interface {
	Get(url string) (ProxyState, bool)
	Set(state ProxyState)
	All() []ProxyState
	Delete(url string)
	Clear()
}

CacheSource defines passive storage for proxy states. Implementations must not alter or mutate incoming data.

func NewMemoryCache added in v1.2.0

func NewMemoryCache() CacheSource

NewMemoryCache returns the default thread-safe in-memory CacheSource. Pass it to NewPool when no custom storage backend is needed.

type Config added in v1.0.6

type Config struct {
	MinLimit         int
	InitialLimit     int
	MaxLimit         int
	LowWater         int
	HighWater        int
	LivenessInterval time.Duration
	IngestInterval   time.Duration
	StatsInterval    time.Duration
	SoftBanBase      time.Duration
	SoftBanMax       time.Duration
	NetProbeInterval time.Duration
	NetDegradedRTT   time.Duration
	SourceGCAge      time.Duration
	// RequireTimeout is the default overall deadline of one Require call.
	RequireTimeout time.Duration
	// DemandTTL is how long a country demand stays registered after its last use.
	DemandTTL time.Duration
	// MaxDemands caps the number of distinct registered demands.
	MaxDemands int
	// CountryTarget is the default number of alive proxies the pool keeps for a demanded country set.
	CountryTarget int
	// ReplenishTTL is how long a country target is maintained after the last Require call for it.
	ReplenishTTL time.Duration
}

Config controls scheduler cadence, queue thresholds, and verification limits.

func DefaultConfig added in v1.0.6

func DefaultConfig() Config

DefaultConfig returns the scheduler defaults defined by the rollout plan.

func (Config) Resolve added in v1.0.6

func (c Config) Resolve() Config

Resolve fills non-positive fields from DefaultConfig without mutating the receiver.

type DemandStat added in v1.0.6

type DemandStat struct {
	Countries       []string // Sorted ISO codes; empty means any country
	Waiters         int
	Served          int64  // Proxies handed out through this demand
	Target          int    // Alive proxies the pool maintains for this country set
	Alive           int    // Alive proxies currently matching; set by Snapshot only
	State           string // "waiting", "hunting", "replenishing" or "satisfied"; set by Snapshot only
	CreatedAt       time.Time
	ExpiresAt       time.Time // End of the hunting window while nothing matches
	ReplenishUntil  time.Time // End of target maintenance and of the registration
	LastSatisfiedAt time.Time
}

DemandStat reports one registered country demand.

type FailReason added in v1.0.6

type FailReason string

FailReason classifies a failed proxy check.

const (
	FailNone      FailReason = ""
	FailTimeout   FailReason = "timeout"
	FailRefused   FailReason = "refused"
	FailRejected  FailReason = "rejected" // CONNECT reply non-2xx, SOCKS reply != success
	FailEOF       FailReason = "eof_reset"
	FailDNS       FailReason = "dns"
	FailTLS       FailReason = "tls"
	FailBadStatus FailReason = "bad_status" // Probe HTTP status outside 200..399
	FailLocalNet  FailReason = "local_net"
	FailOther     FailReason = "other"
)

func (FailReason) Soft added in v1.0.6

func (r FailReason) Soft() bool

Soft reports whether the reason is a transient local or upstream condition that must not hard-ban the proxy.

func (FailReason) Valid added in v1.0.6

func (r FailReason) Valid() bool

Valid reports whether the reason is a known non-empty constant.

type LaneStats added in v1.0.6

type LaneStats struct {
	Lane     string
	Inflight int
	Queued   int // Due items
}

LaneStats reports one scheduler lane's load.

type Mode added in v1.0.6

type Mode string // "foreground", "background"

Mode is the pool scheduling mode.

const (
	ModeForeground Mode = "foreground"
	ModeBackground Mode = "background"
)

type NetSnapshot added in v1.0.6

type NetSnapshot struct {
	State     NetState      // "good", "degraded", "down"
	RTT       time.Duration // Median of the last 3 successful samples, 0 if none
	UpdatedAt time.Time     // UTC timestamp of the last sample
}

NetSnapshot is the last published network health sample. Defined here because PoolStats references it; the publishing prober lives in nethealth.go.

type NetState added in v1.0.6

type NetState string // "good", "degraded", "down"

NetState is the state of the local network connection of the host.

const (
	NetGood     NetState = "good"
	NetDegraded NetState = "degraded"
	NetDown     NetState = "down"
)

type ParsedEndpoint added in v1.0.5

type ParsedEndpoint struct {
	Canonical string // e.g. "socks5://user:pass@1.2.3.4:1080"
	Host      string // target IP or hostname
	Port      int    // target port
}

ParsedEndpoint is a normalized proxy endpoint: canonical URL plus the dial-relevant host and port.

type PoolEvent added in v1.0.6

type PoolEvent struct {
	Time   time.Time         // UTC timestamp of the event
	Kind   string            // "mode_change", "net_change", "limit_change", "ingest_done", "breaker_open", "breaker_close", "demand_created", "demand_satisfied", "demand_expired", "require_timeout"
	Fields map[string]string // Stringified details, e.g. {"from":"foreground","to":"background"}
}

PoolEvent records a pool state transition.

type PoolStats added in v1.0.6

type PoolStats struct {
	Time         time.Time
	Mode         Mode
	Net          NetSnapshot
	Limit        int
	Inflight     int
	Alive        int
	Suspect      int
	Banned       int
	Queued       int // Candidate queue length
	Lanes        []LaneStats
	Sources      []SourceStats // Sorted by Source
	BanReasons   map[FailReason]int
	LastIngestAt time.Time
	Demands      []DemandStat // Registered country demands, sorted by key

	// ForcedForeground reports that ForceForeground is active: the candidate
	// queue is processed by the foreground lane regardless of Mode.
	ForcedForeground bool
}

PoolStats is a point-in-time pool snapshot reported on StatsInterval.

type ProxyFilter

type ProxyFilter struct {
	Location   *string        // ISO country code (e.g., "DE", "US")
	MaxLatency *time.Duration // Upper latency threshold
	MinScore   *float64       // Minimum reputation threshold (e.g., 5.0)
	Limit      *int           // Cap the maximum number of results returned
}

type ProxyInfo

type ProxyInfo struct {
	URL         string        `json:"url"`
	IP          string        `json:"ip"`
	Port        int           `json:"port"`
	Location    string        `json:"location"`
	Latency     time.Duration `json:"latency"`
	Score       float64       `json:"score"`
	LastChecked time.Time     `json:"last_checked"`
}

ProxyInfo represents a validated, consumer-ready proxy returned by ListProxies.

type ProxyPool

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

ProxyPool coordinates proxy discovery, lifecycle health, scheduling, and consumer queries.

func NewPool

func NewPool(cache CacheSource) (*ProxyPool, error)

NewPool initializes a pool with the given storage backend and no-op reporting. The cache argument is required; a nil cache returns an error.

func (*ProxyPool) ForceForeground added in v1.3.0

func (p *ProxyPool) ForceForeground() int

ForceForeground makes the foreground lane work through the candidate queue at full speed regardless of the pool mode. In background mode candidates are otherwise checked slowly (a fifth of the limit, rate-limited).

The override ends by itself once the candidate queue is empty and no candidate check is in flight. Candidates added by a later ingest are not covered. It returns the number of candidates waiting at the call; zero means there is nothing to force and no override is set.

func (*ProxyPool) GetMetadata

func (p *ProxyPool) GetMetadata(url string) map[string]string

GetMetadata returns a detached copy of custom metadata for the given proxy URL.

func (*ProxyPool) ListProxies

func (p *ProxyPool) ListProxies(filter ProxyFilter) []ProxyInfo

ListProxies reads only the in-memory alive index.

func (*ProxyPool) MarkDead added in v1.0.3

func (p *ProxyPool) MarkDead(url, reason string) bool

MarkDead excludes the proxy with the given canonical URL until an escalating ban expires.

func (*ProxyPool) Mode added in v1.0.6

func (p *ProxyPool) Mode() Mode

func (*ProxyPool) NetHealth added in v1.0.6

func (p *ProxyPool) NetHealth() NetSnapshot

func (*ProxyPool) RecheckBanned added in v1.0.6

func (p *ProxyPool) RecheckBanned(f BanFilter) int

RecheckBanned queues banned proxies for the revival lane without changing their ban schedule.

func (*ProxyPool) Refresh

func (p *ProxyPool) Refresh()

Refresh performs one blocking scheduler drain without starting background goroutines. It ingests all sources, then checks candidates and due cached proxies using the same queues, limiter, and checkOneForced implementation used by Start. It returns when candQ is empty and no live or banned items are due at the current time.

func (*ProxyPool) RegisterProxySource

func (p *ProxyPool) RegisterProxySource(source ProxySource)

RegisterProxySource adds an upstream proxy feed provider.

func (*ProxyPool) RegisterReporter

func (p *ProxyPool) RegisterReporter(reporter Reporter)

RegisterReporter registers a structured telemetry handler.

func (*ProxyPool) RequestIngest added in v1.0.6

func (p *ProxyPool) RequestIngest()

RequestIngest schedules an asynchronous source ingest when the pool is running.

func (*ProxyPool) Require added in v1.0.6

func (p *ProxyPool) Require(ctx context.Context, opts RequireOptions) (RequireResult, error)

Require returns alive proxies matching the options. When none match, it registers a country demand and waits until a match becomes alive, the timeout elapses, or ctx is done.

Cancelling ctx stops only this caller. The demand stays registered until its TTL expires so the pool keeps working on it. On timeout Require returns TimedOut=true with a nil error; the caller decides the fallback policy.

func (*ProxyPool) Reset added in v1.3.0

func (p *ProxyPool) Reset()

Reset drops every in-memory structure derived from the cache: the alive index, the candidate and revival queues and the force override. Call it after the cache was cleared externally. The pool must be stopped; a running scheduler would keep working on tasks it already holds.

func (*ProxyPool) SetLimits added in v1.0.6

func (p *ProxyPool) SetLimits(min, initial, max int)

SetLimits sets the dynamic concurrency range and current initial limit.

func (*ProxyPool) SetTimeout

func (p *ProxyPool) SetTimeout(c TimeoutConfig)

SetTimeout replaces the network budgets for handshakes and latency probes.

func (*ProxyPool) Snapshot added in v1.0.6

func (p *ProxyPool) Snapshot() PoolStats

Snapshot returns the current pool state without performing network I/O.

func (*ProxyPool) Start added in v1.0.6

func (p *ProxyPool) Start(ctx context.Context)

Start initializes persisted scheduler state and starts background ingest and dispatch. The method is non-blocking and is idempotent.

func (*ProxyPool) Starved added in v1.0.6

func (p *ProxyPool) Starved() bool

func (*ProxyPool) Stop added in v1.0.6

func (p *ProxyPool) Stop()

Stop cancels all background scheduler goroutines and waits for them to exit.

func (*ProxyPool) Suspect added in v1.0.6

func (p *ProxyPool) Suspect(url string, reason FailReason) bool

Suspect marks a known alive proxy for an immediate liveness recheck without changing its reputation. The proxy remains out of ListProxies until a check succeeds.

func (*ProxyPool) UpdateMetadata

func (p *ProxyPool) UpdateMetadata(url string, update func(map[string]string))

UpdateMetadata runs update against the custom metadata map for the given proxy URL.

type ProxyReport

type ProxyReport struct {
	Timestamp  time.Time     // UTC timestamp of the event
	URL        string        // Normalized proxy URL
	Source     string        // Name of the source that first reported the proxy
	Location   string        // Discovered ISO country code
	FailReason FailReason    // Classification of the failed check; empty on success
	Lane       string        // Scheduler lane that ran the check; empty before the lane scheduler
	IsDead     bool          // Operational status after check
	Score      float64       // Reputation score [0.0 - 10.0]
	Penalty    float64       // Current backoff penalty weight
	ReviveAt   time.Time     // Scheduled revival timestamp if dead
	Latency    time.Duration // Measured latency during check (0 if failed/skipped)
	Died       bool          // True if transitioned from Alive -> Dead
	Revived    bool          // True if transitioned from Dead -> Probed/Alive
}

ProxyReport contains telemetry for a single proxy evaluated during a cycle.

type ProxySource

type ProxySource interface {
	Name() string        // Unique identifier for logging/metrics
	FetchList() []string // Returns raw proxy URLs; empty slice means no changes (e.g., 304)
}

ProxySource provides raw proxy URLs from any upstream feed. Upstream rate-limiting, ETag/Last-Modified caching, and format decoding belong entirely inside the source implementation.

type ProxyState

type ProxyState struct {
	URL      string // Canonical proxy URL (e.g., "http://1.2.3.4:8080", "socks5://user:pass@1.2.3.4:1080")
	IP       string // Target IP or hostname
	Port     int    // Target port
	Location string // 2-letter ISO country code discovered via Cloudflare trace (e.g., "DE")
	IsDead   bool   // Operational status
	// Source is the name of the first source that reported this proxy.
	Source string
	// FailReason classifies the last failed check; empty when the last check passed.
	FailReason FailReason
	// SoftFails counts consecutive soft failures; reset on success.
	SoftFails int
	// Suspect excludes the proxy from listings and queues an immediate recheck.
	Suspect bool
	// LastSeenInSource is the last time any source reported this URL.
	LastSeenInSource time.Time
	Penalty          float64           // Accumulated backoff penalty weight
	Score            float64           // Reputation credit [0.0 - 10.0]
	ReviveAt         time.Time         // Timestamp when dead proxy can be re-tested
	LastCheckedAt    time.Time         // Timestamp of last probe (used for delta normalization)
	Latency          time.Duration     // Last measured round-trip time
	Metadata         map[string]string // Optional user-attached custom metadata (nil = none)
}

ProxyState holds internal lifecycle, scoring, and operational metadata. Stored inside CacheSource and managed solely by ProxyPool.

type Reporter added in v1.0.6

type Reporter interface {
	ReportProxy(ProxyReport) // One call per finished check
	ReportStats(PoolStats)   // Every Config.StatsInterval
	ReportEvent(PoolEvent)   // State transitions
}

Reporter receives structured pool telemetry.

type RequireOptions added in v1.0.6

type RequireOptions struct {
	// Countries is an OR list of ISO country codes. Empty accepts any country.
	Countries []string
	// Exclude lists proxy URLs that must not be returned to this caller.
	Exclude []string
	// Limit is the maximum number of distinct proxies returned. Values below 1 mean 1.
	Limit int
	// Timeout is the overall deadline of the call. Zero uses Config.RequireTimeout.
	Timeout time.Duration
	// Want is the number of alive proxies the pool should keep for Countries after
	// this call. Zero uses Config.CountryTarget. It has no effect without Countries.
	Want int
}

RequireOptions describes one Require call.

type RequireResult added in v1.0.6

type RequireResult struct {
	Proxies  []ProxyInfo
	TimedOut bool
}

RequireResult is the outcome of Require. Proxies is empty when TimedOut is true.

type SourceStats added in v1.0.6

type SourceStats struct {
	Source     string
	Alive      int
	Suspect    int
	Banned     int
	Queued     int                // Candidates of this source still in the candidate queue
	BanReasons map[FailReason]int // Among banned
}

SourceStats aggregates pool composition per source.

type TaggedSource added in v1.0.6

type TaggedSource interface {
	FetchTagged() []TaggedURL
}

TaggedSource optionally supplies per-URL source tags during ingest.

type TaggedURL added in v1.0.6

type TaggedURL struct {
	URL    string
	Source string
	// Country is an optional two-letter hint from the source. It only orders
	// candidates; the real location is confirmed by the probe.
	Country string
}

TaggedURL associates one source identity with a raw URL.

type TimeoutConfig added in v1.0.5

type TimeoutConfig struct {
	Handshake time.Duration
	Probe     time.Duration
}

TimeoutConfig bounds the two probe phases independently. It is the single source of truth for network budgets.

func DefaultTimeoutConfig added in v1.0.5

func DefaultTimeoutConfig() TimeoutConfig

DefaultTimeoutConfig returns the default handshake and probe budgets.

func (TimeoutConfig) Resolve added in v1.0.5

func (c TimeoutConfig) Resolve() TimeoutConfig

Resolve fills non-positive timeout fields with defaults.

Directories

Path Synopsis
cmd
proxy_lab command
proxypoold command
Command proxypoold is the bundled sidecar for opencode's proxy pool.
Command proxypoold is the bundled sidecar for opencode's proxy pool.

Jump to

Keyboard shortcuts

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