Skip to content

Commit 0f8a5e6

Browse files
committed
Cache agent instances
1 parent d546fd2 commit 0f8a5e6

3 files changed

Lines changed: 152 additions & 74 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Change Log
22
Notable changes will be documented here.
33

4+
## [0.34.0]
5+
- Cache agent instances ([microsoft/vscode-proxy-agent#77](https://github.com/microsoft/vscode-proxy-agent/pull/77))
6+
47
## [0.33.0]
58
- Add certificate path for Fedora 43+ ([microsoft/vscode#261433](https://github.com/microsoft/vscode/issues/261433))
69

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vscode/proxy-agent",
3-
"version": "0.33.0",
3+
"version": "0.34.0",
44
"description": "NodeJS http(s) agent implementation for VS Code",
55
"main": "out/index.js",
66
"types": "out/index.d.ts",

src/index.ts

Lines changed: 148 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -589,92 +589,167 @@ export function createFetchPatch(params: ProxyAgentParams, originalFetch: typeof
589589
if (!proxyURL) {
590590
const modifiedInit = {
591591
...init,
592-
dispatcher: new undici.Agent({
593-
allowH2,
594-
connect: { ca: requestCA },
595-
})
592+
dispatcher: getAgent(agentOptions.dispatcher, allowH2, requestCA, addCerts),
596593
};
597594
return originalFetch(input, modifiedInit);
598595
}
599596

600597
const state: Record<string, any> = {};
601-
const proxyAuthorization = await params.lookupProxyAuthorization?.(proxyURL, undefined, state);
602598
const modifiedInit = {
603599
...init,
604-
dispatcher: new undici.ProxyAgent({
605-
uri: proxyURL,
606-
allowH2,
607-
headers: proxyAuthorization ? { 'Proxy-Authorization': proxyAuthorization } : undefined,
608-
requestTls: requestCA ? { allowH2, ca: requestCA } : { allowH2 },
609-
proxyTls: proxyCA ? { allowH2, ca: proxyCA } : { allowH2 },
610-
clientFactory: (origin: URL, opts: object): undici.Dispatcher => (new undici.Pool(origin, opts) as any).compose((dispatch: undici.Dispatcher['dispatch']) => {
611-
class ProxyAuthHandler extends undici.DecoratorHandler {
612-
constructor(private dispatch: undici.Dispatcher['dispatch'], private options: undici.Dispatcher.DispatchOptions, private handler: undici.Dispatcher.DispatchHandler) {
613-
super(handler);
614-
}
615-
onResponseError(controller: undici.Dispatcher.DispatchController, err: Error): void {
616-
if (!(err instanceof ProxyAuthError)) {
617-
return this.handler.onResponseError?.(controller, err);
618-
}
619-
(async () => {
620-
try {
621-
const proxyAuthorization = await params.lookupProxyAuthorization?.(proxyURL!, err.proxyAuthenticate, state);
622-
if (proxyAuthorization) {
623-
if (!this.options.headers) {
624-
this.options.headers = ['Proxy-Authorization', proxyAuthorization];
625-
} else if (Array.isArray(this.options.headers)) {
626-
const i = this.options.headers.findIndex((value, index) => index % 2 === 0 && value.toLowerCase() === 'proxy-authorization');
627-
if (i === -1) {
628-
this.options.headers.push('Proxy-Authorization', proxyAuthorization);
629-
} else {
630-
this.options.headers[i + 1] = proxyAuthorization;
631-
}
632-
} else if (typeof (this.options.headers as any)[Symbol.iterator] === 'function') {
633-
const headers = [...(this.options.headers as Iterable<[string, string | string[] | undefined]>)];
634-
const i = headers.findIndex(value => value[0].toLowerCase() === 'proxy-authorization');
635-
if (i === -1) {
636-
headers.push(['Proxy-Authorization', proxyAuthorization]);
637-
} else {
638-
headers[i][1] = proxyAuthorization;
639-
}
640-
this.options.headers = headers;
641-
} else {
642-
(this.options.headers as Record<string, string | string[] | undefined>)['Proxy-Authorization'] = proxyAuthorization;
643-
}
644-
this.dispatch(this.options, this);
600+
dispatcher: await getProxyAgent(params, agentOptions.dispatcher, proxyURL, allowH2, requestCA, proxyCA, addCerts, state),
601+
};
602+
return originalFetch(input, modifiedInit);
603+
};
604+
}
605+
606+
let previousAddCertsAgent: boolean | undefined = undefined;
607+
let defaultAgent: undici.Agent | undefined = undefined;
608+
let agentCache = new WeakMap<undici.Dispatcher, undici.Agent>();
609+
function getAgent(originalDispatcher: undici.Dispatcher | undefined, allowH2: boolean | undefined, requestCA: string | Buffer | (string | Buffer)[] | undefined, currentAddCerts: boolean): undici.Agent | undefined {
610+
if (previousAddCertsAgent !== currentAddCerts) {
611+
previousAddCertsAgent = currentAddCerts;
612+
defaultAgent = undefined;
613+
agentCache = new WeakMap<undici.Dispatcher, undici.Agent>();
614+
}
615+
if (!originalDispatcher) {
616+
if (!defaultAgent) {
617+
defaultAgent = createAgent(allowH2, requestCA);
618+
}
619+
return defaultAgent;
620+
}
621+
622+
if (!agentCache.has(originalDispatcher)) {
623+
agentCache.set(originalDispatcher, createAgent(allowH2, requestCA));
624+
}
625+
return agentCache.get(originalDispatcher);
626+
}
627+
628+
function createAgent(allowH2: boolean | undefined, requestCA: string | Buffer | (string | Buffer)[] | undefined) {
629+
return new undici.Agent({
630+
allowH2,
631+
connect: { ca: requestCA },
632+
});
633+
}
634+
635+
let previousAddCertsProxyAgent: boolean | undefined = undefined;
636+
let defaultProxyAgent: undici.ProxyAgent | undefined = undefined;
637+
let proxyAgentCache = new WeakMap<undici.Dispatcher, Map<string, undici.ProxyAgent>>();
638+
async function getProxyAgent(
639+
params: ProxyAgentParams,
640+
originalDispatcher: undici.Dispatcher | undefined,
641+
proxyURL: string,
642+
allowH2: boolean | undefined,
643+
requestCA: string | Buffer | (string | Buffer)[] | undefined,
644+
proxyCA: string | Buffer | (string | Buffer)[] | undefined,
645+
currentAddCerts: boolean,
646+
state: Record<string, any>
647+
): Promise<undici.ProxyAgent> {
648+
if (previousAddCertsProxyAgent !== currentAddCerts) {
649+
previousAddCertsProxyAgent = currentAddCerts;
650+
defaultProxyAgent = undefined;
651+
proxyAgentCache = new WeakMap<undici.Dispatcher, Map<string, undici.ProxyAgent>>();
652+
}
653+
654+
if (!originalDispatcher) {
655+
if (!defaultProxyAgent) {
656+
defaultProxyAgent = await createProxyAgent(params, proxyURL, allowH2, requestCA, proxyCA, state);
657+
}
658+
return defaultProxyAgent;
659+
}
660+
661+
let dispatcherCache = proxyAgentCache.get(originalDispatcher);
662+
if (!dispatcherCache) {
663+
dispatcherCache = new Map<string, undici.ProxyAgent>();
664+
proxyAgentCache.set(originalDispatcher, dispatcherCache);
665+
}
666+
667+
if (!dispatcherCache.has(proxyURL)) {
668+
dispatcherCache.set(proxyURL, await createProxyAgent(params, proxyURL, allowH2, requestCA, proxyCA, state));
669+
}
670+
return dispatcherCache.get(proxyURL)!;
671+
}
672+
673+
async function createProxyAgent(
674+
params: ProxyAgentParams,
675+
proxyURL: string,
676+
allowH2: boolean | undefined,
677+
requestCA: string | Buffer | (string | Buffer)[] | undefined,
678+
proxyCA: string | Buffer | (string | Buffer)[] | undefined,
679+
state: Record<string, any>
680+
): Promise<undici.ProxyAgent> {
681+
const proxyAuthorization = await params.lookupProxyAuthorization?.(proxyURL, undefined, state);
682+
return new undici.ProxyAgent({
683+
uri: proxyURL,
684+
allowH2,
685+
headers: proxyAuthorization ? { 'Proxy-Authorization': proxyAuthorization } : undefined,
686+
requestTls: requestCA ? { allowH2, ca: requestCA } : { allowH2 },
687+
proxyTls: proxyCA ? { allowH2, ca: proxyCA } : { allowH2 },
688+
clientFactory: (origin: URL, opts: object): undici.Dispatcher => (new undici.Pool(origin, opts) as any).compose((dispatch: undici.Dispatcher['dispatch']) => {
689+
class ProxyAuthHandler extends undici.DecoratorHandler {
690+
constructor(private dispatch: undici.Dispatcher['dispatch'], private options: undici.Dispatcher.DispatchOptions, private handler: undici.Dispatcher.DispatchHandler) {
691+
super(handler);
692+
}
693+
onResponseError(controller: undici.Dispatcher.DispatchController, err: Error): void {
694+
if (!(err instanceof ProxyAuthError)) {
695+
return this.handler.onResponseError?.(controller, err);
696+
}
697+
(async () => {
698+
try {
699+
const proxyAuthorization = await params.lookupProxyAuthorization?.(proxyURL!, err.proxyAuthenticate, state);
700+
if (proxyAuthorization) {
701+
if (!this.options.headers) {
702+
this.options.headers = ['Proxy-Authorization', proxyAuthorization];
703+
} else if (Array.isArray(this.options.headers)) {
704+
const i = this.options.headers.findIndex((value, index) => index % 2 === 0 && value.toLowerCase() === 'proxy-authorization');
705+
if (i === -1) {
706+
this.options.headers.push('Proxy-Authorization', proxyAuthorization);
645707
} else {
646-
this.handler.onResponseError?.(controller, new undici.errors.RequestAbortedError(`Proxy response (407) ?.== 200 when HTTP Tunneling`)); // Mimick undici's behavior
708+
this.options.headers[i + 1] = proxyAuthorization;
647709
}
648-
} catch (err: any) {
649-
this.handler.onResponseError?.(controller, err);
650-
}
651-
})();
652-
}
653-
onRequestUpgrade?(controller: undici.Dispatcher.DispatchController, statusCode: number, headers: IncomingHttpHeaders, socket: stream.Duplex): void {
654-
if (statusCode === 407 && headers) {
655-
let proxyAuthenticate: string | string[] | undefined;
656-
for (const header in headers) {
657-
if (header.toLowerCase() === 'proxy-authenticate') {
658-
proxyAuthenticate = headers[header];
659-
break;
710+
} else if (typeof (this.options.headers as any)[Symbol.iterator] === 'function') {
711+
const headers = [...(this.options.headers as Iterable<[string, string | string[] | undefined]>)];
712+
const i = headers.findIndex(value => value[0].toLowerCase() === 'proxy-authorization');
713+
if (i === -1) {
714+
headers.push(['Proxy-Authorization', proxyAuthorization]);
715+
} else {
716+
headers[i][1] = proxyAuthorization;
660717
}
718+
this.options.headers = headers;
719+
} else {
720+
(this.options.headers as Record<string, string | string[] | undefined>)['Proxy-Authorization'] = proxyAuthorization;
661721
}
662-
if (proxyAuthenticate) {
663-
controller.abort(new ProxyAuthError(proxyAuthenticate));
664-
return;
665-
}
722+
this.dispatch(this.options, this);
723+
} else {
724+
this.handler.onResponseError?.(controller, new undici.errors.RequestAbortedError(`Proxy response (407) ?.== 200 when HTTP Tunneling`)); // Mimick undici's behavior
725+
}
726+
} catch (err: any) {
727+
this.handler.onResponseError?.(controller, err);
728+
}
729+
})();
730+
}
731+
onRequestUpgrade?(controller: undici.Dispatcher.DispatchController, statusCode: number, headers: IncomingHttpHeaders, socket: stream.Duplex): void {
732+
if (statusCode === 407 && headers) {
733+
let proxyAuthenticate: string | string[] | undefined;
734+
for (const header in headers) {
735+
if (header.toLowerCase() === 'proxy-authenticate') {
736+
proxyAuthenticate = headers[header];
737+
break;
666738
}
667-
this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket);
739+
}
740+
if (proxyAuthenticate) {
741+
controller.abort(new ProxyAuthError(proxyAuthenticate));
742+
return;
668743
}
669744
}
670-
return function proxyAuthDispatch(options: undici.Dispatcher.DispatchOptions, handler: undici.Dispatcher.DispatchHandler) {
671-
return dispatch(options, new ProxyAuthHandler(dispatch, options, handler));
672-
};
673-
}),
674-
})
675-
};
676-
return originalFetch(input, modifiedInit);
677-
};
745+
this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket);
746+
}
747+
}
748+
return function proxyAuthDispatch(options: undici.Dispatcher.DispatchOptions, handler: undici.Dispatcher.DispatchHandler) {
749+
return dispatch(options, new ProxyAuthHandler(dispatch, options, handler));
750+
};
751+
}),
752+
});
678753
}
679754

680755
class ProxyAuthError extends Error {
@@ -752,7 +827,7 @@ function getAgentOptions(requestInit: RequestInit | undefined) {
752827
requestCA = originalProxyAgentOptions.requestTls && 'ca' in originalProxyAgentOptions.requestTls && originalProxyAgentOptions.requestTls.ca || undefined;
753828
proxyCA = originalProxyAgentOptions.proxyTls && 'ca' in originalProxyAgentOptions.proxyTls && originalProxyAgentOptions.proxyTls.ca || undefined;
754829
}
755-
return { allowH2, requestCA, proxyCA, socketPath };
830+
return { dispatcher, allowH2, requestCA, proxyCA, socketPath };
756831
}
757832

758833
function addCertificatesToOptionsV1(params: ProxyAgentParams, addCertificatesV1: boolean, opts: http.RequestOptions | tls.ConnectionOptions, callback: () => void) {

0 commit comments

Comments
 (0)