From 774f139beff0c8fdcdbf578b046a9c803f35b911 Mon Sep 17 00:00:00 2001 From: Tatyana Kostromskaya <32135588+takost@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:56:21 +0000 Subject: [PATCH] . --- packages/http-client/__tests__/proxy.test.ts | 18 ++++ packages/http-client/src/index.ts | 101 +++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/packages/http-client/__tests__/proxy.test.ts b/packages/http-client/__tests__/proxy.test.ts index ad0c55bd..10e1dda5 100644 --- a/packages/http-client/__tests__/proxy.test.ts +++ b/packages/http-client/__tests__/proxy.test.ts @@ -3,6 +3,7 @@ import * as http from 'http' import * as httpm from '../lib/' import * as pm from '../lib/proxy' +import { ProxyAgent, Agent, fetch as undiciFetch } from "undici"; // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports const proxy = require('proxy') @@ -294,6 +295,23 @@ describe('proxy', () => { expect(agent.proxyOptions.port).toBe('8080') expect(agent.proxyOptions.proxyAuth).toBe('user:password') }) + + it('proxy settings return ProxyAgent', async () => { + process.env['https_proxy'] = 'http://127.0.0.1:8080' + const httpClient = new httpm.HttpClient() + const agent: Agent | ProxyAgent = httpClient.getAgentDispatcher('https://some-url') + // eslint-disable-next-line no-console + console.log(agent) + expect(agent instanceof ProxyAgent).toBe(true) + }) + + it('proxyAuth is set in tunnel agent when authentication is provided', async () => { + const httpClient = new httpm.HttpClient() + const agent: Agent | ProxyAgent = httpClient.getAgentDispatcher('https://some-url') + // eslint-disable-next-line no-console + console.log(agent) + expect(agent instanceof Agent).toBe(true) + }) }) function _clearVars(): void { diff --git a/packages/http-client/src/index.ts b/packages/http-client/src/index.ts index 406b7f89..9566ad78 100644 --- a/packages/http-client/src/index.ts +++ b/packages/http-client/src/index.ts @@ -138,6 +138,8 @@ export class HttpClient { private _maxRetries = 1 private _agent: any private _proxyAgent: any + private _agentDispatcher: any + private _proxyAgentDispatcher: any private _keepAlive = false private _disposed = false @@ -565,6 +567,18 @@ export class HttpClient { return this._getAgent(parsedUrl) } + getAgentDispatcher(serverUrl: string): ProxyAgent | Agent { + const parsedUrl = new URL(serverUrl) + const proxyUrl = pm.getProxyUrl(parsedUrl) + const useProxy = proxyUrl && proxyUrl.hostname + if (useProxy) { + return this._getProxyAgentDispatcher(parsedUrl, proxyUrl) + } + else { + return this._getAgentDispatcher(parsedUrl) + } + } + private _prepareRequest( method: string, requestUrl: URL, @@ -702,6 +716,93 @@ export class HttpClient { return agent } + private _getProxyAgentDispatcher(parsedUrl: URL, proxyUrl: URL): ProxyAgent { + let proxyAgent + const useProxy = proxyUrl && proxyUrl.hostname + + if (this._keepAlive && useProxy) { + proxyAgent = this._proxyAgentDispatcher + } + + if (this._keepAlive && !useProxy) { + proxyAgent = this._agentDispatcher + } + + // if agent is already assigned use that agent. + if (proxyAgent) { + return proxyAgent + } + + const usingSsl = parsedUrl.protocol === 'https:' + let maxSockets = 100 + if (this.requestOptions) { + maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets + } + + // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis. + if (proxyUrl && proxyUrl.hostname) { + proxyAgent = new ProxyAgent({ + uri: proxyUrl.href, + pipelining: (!this._keepAlive ? 0 : 1), + ...((proxyUrl.username || proxyUrl.password) && { + token: `${proxyUrl.username}:${proxyUrl.password}` + }), + }) + this._proxyAgentDispatcher = proxyAgent + } + + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + proxyAgent.options = Object.assign(proxyAgent.options.requestTls || {}, { + rejectUnauthorized: false + }) + } + + return proxyAgent + } + + private _getAgentDispatcher(parsedUrl: URL): Agent { + let agent; + + if (this._keepAlive) { + agent = this._agentDispatcher + } + + // if agent is already assigned use that agent. + if (agent) { + return agent + } + + const usingSsl = parsedUrl.protocol === 'https:' + let maxSockets = 100 + if (this.requestOptions) { + maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets + } + + // if reusing agent across request and tunneling agent isn't assigned create a new agent + if (!agent) { + agent = new Agent( + { + pipelining: (!this._keepAlive ? 0 : 1), + } + ) + this._agentDispatcher = agent + } + + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + agent.options = Object.assign(agent.options.connect || {}, { + rejectUnauthorized: false + }) + } + + return agent + } + private async _performExponentialBackoff(retryNumber: number): Promise { retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber) const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber)