fix(proxy): separate upstream target resolution from proxy dispatch and add site-level proxy URLs

This commit is contained in:
Brucents
2026-03-20 17:40:02 +08:00
parent 112191c5bc
commit a66cbafeb8
11 changed files with 111 additions and 40 deletions
+32
View File
@@ -57,6 +57,22 @@ describe('siteProxy', () => {
.toBeNull();
});
it('prefers site-specific proxy url over the shared system proxy', async () => {
await db.insert(schema.settings).values({
key: 'system_proxy_url',
value: JSON.stringify('http://127.0.0.1:7890'),
}).run();
await db.run(sql`
INSERT INTO sites (name, url, platform, proxy_url, use_system_proxy)
VALUES ('proxy-site', 'https://proxy-site.example.com', 'new-api', 'socks5://127.0.0.1:1080', 1)
`);
const { resolveSiteProxyUrlByRequestUrl } = await import('./siteProxy.js');
expect(await resolveSiteProxyUrlByRequestUrl('https://proxy-site.example.com/v1/models'))
.toBe('socks5://127.0.0.1:1080');
});
it('injects dispatcher when a site opts into the configured system proxy', async () => {
await db.insert(schema.settings).values({
key: 'system_proxy_url',
@@ -75,6 +91,20 @@ describe('siteProxy', () => {
expect('dispatcher' in requestInit).toBe(true);
});
it('injects dispatcher when a site defines its own proxy url', async () => {
await db.run(sql`
INSERT INTO sites (name, url, platform, proxy_url, use_system_proxy)
VALUES ('proxy-site', 'https://proxy-site.example.com', 'new-api', 'http://127.0.0.1:7890', 0)
`);
const { withSiteProxyRequestInit } = await import('./siteProxy.js');
const requestInit = await withSiteProxyRequestInit('https://proxy-site.example.com/v1/chat/completions', {
method: 'POST',
});
expect('dispatcher' in requestInit).toBe(true);
});
it('injects a working dispatcher for socks5 system proxies', async () => {
const upstreamServer = createServer((_request, response) => {
response.writeHead(200, { 'content-type': 'application/json' });
@@ -170,6 +200,7 @@ describe('siteProxy', () => {
it('merges site custom headers from site records even without cache lookup', async () => {
const { withSiteRecordProxyRequestInit } = await import('./siteProxy.js');
const requestInit = withSiteRecordProxyRequestInit({
proxyUrl: 'http://127.0.0.1:7890',
useSystemProxy: false,
customHeaders: JSON.stringify({
'x-site-scope': 'site-level',
@@ -184,5 +215,6 @@ describe('siteProxy', () => {
expect(headers.get('x-site-scope')).toBe('site-level');
expect(headers.get('x-request-id')).toBe('req-1');
expect('dispatcher' in requestInit).toBe(true);
});
});
+8 -1
View File
@@ -31,6 +31,7 @@ const DEFAULT_PROXY_KEEPALIVE_INITIAL_DELAY_MS = 60_000;
type SiteProxyRow = {
siteUrl: string;
proxyUrl: string | null;
useSystemProxy: boolean;
customHeaders: string | null;
};
@@ -42,6 +43,7 @@ type ParsedSiteProxyInput = {
};
type SiteProxyConfigLike = {
proxyUrl?: string | null;
useSystemProxy?: boolean | null;
customHeaders?: string | null;
};
@@ -103,6 +105,7 @@ async function getCachedSiteProxyRows(nowMs = Date.now()): Promise<SiteProxyRow[
db
.select({
siteUrl: schema.sites.url,
proxyUrl: schema.sites.proxyUrl,
useSystemProxy: schema.sites.useSystemProxy,
customHeaders: schema.sites.customHeaders,
})
@@ -129,6 +132,7 @@ async function getCachedSiteProxyRows(nowMs = Date.now()): Promise<SiteProxyRow[
loadedAt: nowMs,
rows: rows.map((row) => ({
siteUrl: normalizeSiteUrl(row.siteUrl),
proxyUrl: normalizeSiteProxyUrl(row.proxyUrl),
useSystemProxy: !!row.useSystemProxy,
customHeaders: typeof row.customHeaders === 'string' ? row.customHeaders : null,
})),
@@ -368,7 +372,8 @@ async function resolveSiteRequestConfigByRequestUrl(requestUrl: string): Promise
const rows = await getCachedSiteProxyRows();
const matchedRow = findBestMatchingSiteRow(rows, normalizedRequestUrl);
const proxyUrl = matchedRow?.useSystemProxy ? siteProxyCache.systemProxyUrl : null;
const proxyUrl = matchedRow?.proxyUrl
|| (matchedRow?.useSystemProxy ? siteProxyCache.systemProxyUrl : null);
return {
proxyUrl: proxyUrl || null,
customHeaders: matchedRow?.customHeaders ?? null,
@@ -425,6 +430,8 @@ export function withExplicitProxyRequestInit(
}
export function resolveProxyUrlForSite(site: SiteProxyConfigLike | null | undefined): string | null {
const explicitProxyUrl = normalizeSiteProxyUrl(site?.proxyUrl);
if (explicitProxyUrl) return explicitProxyUrl;
if (!site?.useSystemProxy) return null;
return normalizeSiteProxyUrl(config.systemProxyUrl);
}