Skip to content

Service Discovery

BackendPool maintains a thread-safe list of "host:port" addresses and can refresh that list on a background thread. All clones of a BackendPool share the same underlying RwLock<Vec<String>>, so the live backend list is always consistent across the process.

Discovery sources

SourceConstructorRefresh
Fixed listBackendPool::static(...)Never
Environment variablesBackendPool::env_prefix(...)On poll interval
FileBackendPool::file(...)On poll interval
DNS A-recordBackendPool::dns(...)On poll interval

Static list

use rust_web_server::service_discovery::BackendPool;
let pool = BackendPool::r#static(vec![
"10.0.0.1:8080".into(),
"10.0.0.2:8080".into(),
]);
// Immediately available — no .start() required
println!("{:?}", pool.backends());

Environment variable prefix

Scans PREFIX_0, PREFIX_1, PREFIX_2, … in order, stopping at the first variable that is absent:

// Reads MY_SVC_BACKEND_0, MY_SVC_BACKEND_1, etc.
let pool = BackendPool::env_prefix("MY_SVC_BACKEND")
.poll_interval_secs(60);
pool.start();

Set the environment variables before starting:

Terminal window
export MY_SVC_BACKEND_0=api-1.internal:8080
export MY_SVC_BACKEND_1=api-2.internal:8080

File-based discovery

One host:port per line. Blank lines and lines starting with # are ignored:

backends.txt
api-1.internal:8080
api-2.internal:8080
# api-3.internal:8080 <- temporarily disabled
let pool = BackendPool::file("backends.txt")
.poll_interval_secs(30);
pool.start();

DNS A-record lookup

Resolves the hostname to all IP addresses returned by the OS (via ToSocketAddrs). Each IP becomes a "ip:port" entry:

// Resolves api.service.consul every 30 s
let pool = BackendPool::dns("api.service.consul", 8080)
.poll_interval_secs(30);
pool.start();

Starting background refresh

.start() performs an immediate synchronous refresh, then spawns a daemon thread that calls .refresh() every poll_interval_secs seconds. For Static sources .start() is a no-op.

pool.start(); // call once at application startup

Reading the current backend list

let backends: Vec<String> = pool.backends();

backends() takes a read lock and returns a snapshot. It never blocks for longer than the lock acquisition.

Manual updates

For control planes that push backend lists externally:

pool.update(vec![
"new-backend-1:8080".into(),
"new-backend-2:8080".into(),
]);

Integrating with ReverseProxy

use rust_web_server::app::App;
use rust_web_server::core::New;
use rust_web_server::service_discovery::BackendPool;
use rust_web_server::proxy::ReverseProxy;
let pool = BackendPool::dns("api.internal", 8080)
.poll_interval_secs(30);
pool.start();
// Snapshot the backend list at startup; wire the pool into a handler
// or refresh the proxy on each request for dynamic resolution.
let backends = pool.backends();
let app = App::new()
.wrap(ReverseProxy::new(backends));

Poll interval

pool.poll_interval_secs(30) // default: 30 s; only meaningful for File and Dns sources

The poll interval is set before .start(). Changing it after .start() has no effect on the running background thread.