This package adds a REST API to dynamically update the upstream proxy configuration at runtime without restarting the forwarder service.
| File | Description |
|---|---|
upstream.go |
Thread-safe upstream proxy manager |
api_upstream.go |
REST API handlers for proxy management |
upstream_test.go |
Unit tests for the manager |
api_upstream_test.go |
Unit tests for the API handlers |
| File | Description |
|---|---|
docs/api-upstream.md |
API documentation |
patches/http_proxy.go.patch |
Optional changes to http_proxy.go |
patches/run.go.patch |
Changes needed for command/run/run.go |
Copy the following files to the root of the forwarder repository:
cp upstream.go api_upstream.go upstream_test.go api_upstream_test.go /path/to/forwarder/In command/run/run.go, you need to:
- Create the upstream proxy manager
- Wire it to the proxy config
- Add the API endpoint
Here's the integration code to add:
import (
// ... existing imports ...
"github.com/saucelabs/forwarder"
)
func runProxy(ctx context.Context, cfg *Config, logger log.StructuredLogger) error {
// ... existing setup code ...
// === NEW: Create upstream proxy manager ===
upstreamManager := forwarder.NewUpstreamProxyManager(cfg.Proxy.UpstreamProxy)
// === NEW: Configure proxy to use the manager ===
proxyConfig := forwarder.DefaultHTTPProxyConfig()
// ... other config settings ...
// Replace static upstream proxy with dynamic manager
proxyConfig.UpstreamProxyFunc = upstreamManager.ProxyFunc()
// Note: Don't set proxyConfig.UpstreamProxy when using the manager
// === NEW: Add API endpoint ===
extraEndpoints := []forwarder.APIEndpoint{
forwarder.UpstreamProxyAPIEndpoint("/api/v1/upstream", upstreamManager, logger),
}
// Modify APIHandler creation to include extra endpoints
apiHandler := forwarder.NewAPIHandler(
"forwarder",
promRegistry,
readyFunc,
extraEndpoints...,
)
// ... rest of existing code ...
}go test -v ./... -run "Upstream"curl http://localhost:10000/api/v1/upstreamResponse:
{"proxy": "http://proxy.example.com:8080"}Or when in direct mode:
{"proxy": ""}curl -X PUT http://localhost:10000/api/v1/upstream \
-H "Content-Type: application/json" \
-d '{"proxy": "http://newproxy.example.com:8080"}'Response:
{
"proxy": "http://newproxy.example.com:8080",
"message": "upstream proxy updated successfully"
}http://- HTTP proxyhttps://- HTTPS proxysocks5://- SOCKS5 proxysocks5h://- SOCKS5 proxy with DNS resolution on proxy
curl -X PUT http://localhost:10000/api/v1/upstream \
-H "Content-Type: application/json" \
-d '{"proxy": "http://user:password@proxy.example.com:8080"}'Note: Credentials are redacted in GET responses for security.
curl -X DELETE http://localhost:10000/api/v1/upstreamOr:
curl -X PUT http://localhost:10000/api/v1/upstream \
-H "Content-Type: application/json" \
-d '{"proxy": ""}'The UpstreamProxyManager uses Go's atomic.Pointer for lock-free reads, ensuring:
- No blocking during proxy lookups (critical path)
- Immediate visibility of updates to all goroutines
- Safe concurrent access from multiple request handlers
┌─────────────────┐ ┌──────────────────────┐
│ API Server │────▶│ UpstreamProxyManager │
│ (port 10000) │ │ (atomic.Pointer) │
└─────────────────┘ └──────────────────────┘
│
▼
┌──────────────────────┐
│ HTTPProxy │
│ (uses ProxyFunc) │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ Upstream Proxy │
│ (or direct) │
└──────────────────────┘
If you're using PAC files for proxy selection, use ProxyFuncWithFallback:
upstreamManager := forwarder.NewUpstreamProxyManager(nil)
// PAC resolver as fallback
pacResolver := loadPACResolver()
proxyConfig.UpstreamProxyFunc = upstreamManager.ProxyFuncWithFallback(
func(r *http.Request) (*url.URL, error) {
result, err := pacResolver.FindProxyForURL(r.URL, r.URL.Hostname())
if err != nil {
return nil, err
}
return parseProxyResult(result)
},
)This way:
- If the manager has a proxy set → use it
- If the manager is in direct mode (nil) → fall back to PAC
You can also update the proxy programmatically:
manager := forwarder.NewUpstreamProxyManager(nil)
// Later, update the proxy
newProxy, _ := url.Parse("http://newproxy:8080")
manager.Set(newProxy)
// Or remove it
manager.Set(nil)Same as forwarder - MPL-2.0