fix: proxy pool now actively distributes IPs across sessions
- fetchWithRetry: use proxy on every attempt (not just fallback after direct) - fetchWithSessionRetry: call acquire() on attempt 0 so new sessions get a least-loaded proxy assigned, enabling IP spread across concurrent users - Update docstrings to reflect new strategy Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+28
-17
@@ -105,9 +105,10 @@ export interface FetchWithRetryResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute an upstream `fetch` with automatic retry and proxy fallback.
|
* Execute an upstream `fetch` with automatic retry and proxy rotation.
|
||||||
*
|
*
|
||||||
* Strategy: direct first → proxy-1 on failure → rotate proxy on failure.
|
* Strategy: proxy pool on every attempt (rotate each retry).
|
||||||
|
* Falls back to direct when no pool is available.
|
||||||
* Logs every failure to `console.warn` so the operator can diagnose without
|
* Logs every failure to `console.warn` so the operator can diagnose without
|
||||||
* the error body leaking to the downstream client.
|
* the error body leaking to the downstream client.
|
||||||
*/
|
*/
|
||||||
@@ -122,18 +123,21 @@ export async function fetchWithRetry(
|
|||||||
let usedProxy = false;
|
let usedProxy = false;
|
||||||
|
|
||||||
for (let attempt = 0; attempt < 3; attempt++) {
|
for (let attempt = 0; attempt < 3; attempt++) {
|
||||||
if (attempt === 0) {
|
if (proxyPool && proxyPool.size > 0) {
|
||||||
init.proxy = undefined; // direct
|
// Proactive proxy usage on every attempt, rotating each time
|
||||||
} else if (attempt === 1 && proxyPool && proxyPool.size > 0) {
|
if (attempt === 0) {
|
||||||
usedProxy = true;
|
init.proxy = proxyPool.getProxyUrl()!;
|
||||||
init.proxy = proxyPool.getProxyUrl()!;
|
usedProxy = true;
|
||||||
} else if (attempt === 2 && proxyPool && proxyPool.size > 0) {
|
} else {
|
||||||
const next = proxyPool.rotate();
|
const next = proxyPool.rotate();
|
||||||
if (!next) break;
|
if (!next) break;
|
||||||
usedProxy = true;
|
init.proxy = proxyPool.getProxyUrl()!;
|
||||||
init.proxy = proxyPool.getProxyUrl()!;
|
usedProxy = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
// No proxy pool — direct only
|
||||||
|
init.proxy = undefined;
|
||||||
|
usedProxy = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -213,9 +217,11 @@ function classifyFetchErrorSafe(error: unknown): {
|
|||||||
/**
|
/**
|
||||||
* Execute an upstream `fetch` using a session-sticky proxy with retry.
|
* Execute an upstream `fetch` using a session-sticky proxy with retry.
|
||||||
*
|
*
|
||||||
* Strategy: session-sticky proxy (via SessionProxyPool) on attempt 1; on
|
* Strategy: acquire() on attempt 0 (least-loaded assignment), then
|
||||||
* failure the session's proxy is marked failed — which auto-rotates if the
|
* getProxyUrl() on subsequent attempts (reads the existing or rotated proxy).
|
||||||
* failure threshold is exceeded — and the request retries with the new proxy.
|
* On persistent failure the session's proxy is marked failed — which
|
||||||
|
* auto-rotates if the failure threshold is exceeded — and the request
|
||||||
|
* retries with the new proxy.
|
||||||
*
|
*
|
||||||
* SSE streams: the initial request is retried normally. Once the response body
|
* SSE streams: the initial request is retried normally. Once the response body
|
||||||
* starts streaming, mid-stream errors are **not** retried; the session is
|
* starts streaming, mid-stream errors are **not** retried; the session is
|
||||||
@@ -242,7 +248,12 @@ export async function fetchWithSessionRetry(
|
|||||||
let lastError: unknown;
|
let lastError: unknown;
|
||||||
|
|
||||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||||
const proxyUrl = sessionPool.getProxyUrl(sessionId);
|
// First attempt: acquire() assigns a proxy to this session (least-loaded
|
||||||
|
// distribution so many concurrent users spread across different IPs).
|
||||||
|
// Subsequent attempts: getProxyUrl() reads the existing (or rotated) proxy.
|
||||||
|
const proxyUrl = attempt === 0
|
||||||
|
? sessionPool.acquire(sessionId)
|
||||||
|
: sessionPool.getProxyUrl(sessionId);
|
||||||
if (proxyUrl) {
|
if (proxyUrl) {
|
||||||
init.proxy = proxyUrl;
|
init.proxy = proxyUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user