fix: try direct connection first, fall back to proxy

Change proxy strategy: first attempt goes direct (no proxy).
Only if direct fails, fall back to proxy pool with rotation.
This way healthy upstreams don't pay the proxy latency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-10 22:15:47 +07:00
co-authored by Claude Fable 5
parent 03427cae8c
commit c61f8d9db6
+38 -30
View File
@@ -352,44 +352,52 @@ async function handleRelay(
const targetUrlString = targetUrl.toString(); const targetUrlString = targetUrl.toString();
// ── Attach proxy (if pool is loaded) ──────────────────────────── // ── Execute upstream fetch ──────────────────────────────────────
const proxyUrl = proxyPool.getProxyUrl(); // Strategy: direct first → proxy on failure → rotate on failure
if (proxyUrl) fetchOptions.proxy = proxyUrl; let response: Response | undefined;
let usedProxy = false;
// ── Execute upstream fetch (with proxy retry on failure) ───────── for (let attempts = 0; attempts < 3; attempts++) {
let response: Response; // Clear proxy on first attempt (direct)
let retried = false; if (attempts === 0) {
delete fetchOptions.proxy;
} else if (attempts === 1 && proxyPool.size > 0) {
// Second attempt: use first proxy
usedProxy = true;
fetchOptions.proxy = proxyPool.getProxyUrl()!;
} else if (attempts === 2 && proxyPool.size > 0) {
// Third attempt: rotate to next proxy
const next = proxyPool.markFailed();
if (!next) break;
fetchOptions.proxy = proxyPool.getProxyUrl()!;
} else {
break;
}
for (;;) {
try { try {
response = await fetch(targetUrlString, fetchOptions); response = await fetch(targetUrlString, fetchOptions);
if (usedProxy) proxyPool.markSuccess();
break; break;
} catch (err) { } catch {
// Rotate proxy on network failure and retry once // Fall through to next attempt
if (proxyPool.size > 0 && !retried) {
retried = true;
const next = proxyPool.markFailed();
if (next) {
fetchOptions.proxy = proxyPool.getProxyUrl()!;
continue;
}
}
const classified = classifyFetchError(err);
logRelayEvent({
method,
url: requestUrl,
status: classified.status,
durationMs: Math.round(performance.now() - startTime),
error: classified.message,
targetUrl: targetUrlString,
ip: clientIP,
});
return createErrorResponse(classified);
} }
} }
if (proxyUrl) proxyPool.markSuccess(); // All attempts failed — classify the last error
if (!response) {
const lastErr = new Error("All connection attempts failed");
const classified = classifyFetchError(lastErr);
logRelayEvent({
method,
url: requestUrl,
status: classified.status,
durationMs: Math.round(performance.now() - startTime),
error: classified.message,
targetUrl: targetUrlString,
ip: clientIP,
});
return createErrorResponse(classified);
}
// ── Build relay response ───────────────────────────────────────── // ── Build relay response ─────────────────────────────────────────
const relayedResponse = createRelayResponse(response); const relayedResponse = createRelayResponse(response);