Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/packages/teeny-request/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# teeny-request

Like `request`, but much smaller - and with less options. Uses `node-fetch` under the hood.
Like `request`, but much smaller - and with less options. Uses `undici` under the hood.
Pop it in where you would use `request`. Improves load and parse time of modules.

```js
Expand Down
11 changes: 4 additions & 7 deletions core/packages/teeny-request/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"keywords": [
"request",
"node-fetch",
"undici",
"fetch"
],
"author": "fhinkel",
Expand All @@ -40,15 +40,13 @@
},
"homepage": "https://github.com/googleapis/google-cloud-node/tree/main/core/packages/teeny-request",
"dependencies": {
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.1",
"node-fetch": "^3.3.2",
"stream-events": "^1.0.5"
"stream-events": "^1.0.5",
"undici": "^8.10.1"
},
"devDependencies": {
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@types/mocha": "^10.0.10",
"@types/node-fetch": "^2.6.12",
"@types/node": "^24.0.0",
"@types/sinon": "^17.0.3",
"c8": "^10.1.3",
"codecov": "^3.8.3",
Expand All @@ -57,7 +55,6 @@
"jsdoc-fresh": "^6.0.0",
"jsdoc-region-tag": "^5.0.0",
"mocha": "^11.1.0",
"nock": "^14.0.1",
"sinon": "^19.0.2",
"typescript": "^5.7.3"
},
Expand Down
79 changes: 50 additions & 29 deletions core/packages/teeny-request/src/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,31 @@
* limitations under the License.
*/

import {Agent as HTTPAgent} from 'http';
import {Agent as HTTPSAgent} from 'https';
import {
Agent,
Dispatcher,
ProxyAgent,
getGlobalDispatcher,
interceptors,
} from 'undici';
import {Options} from './';

export const pool = new Map<string, HTTPAgent>();
export const pool = new Map<string, Dispatcher>();

export type HttpAnyAgent = HTTPAgent | HTTPSAgent;
// undici only follows redirects through an interceptor; node-fetch
// followed up to 20, so preserve that
const redirect = interceptors.redirect({maxRedirections: 20});

const composed = new WeakMap<Dispatcher, Dispatcher>();

function withRedirects(dispatcher: Dispatcher): Dispatcher {
let dispatcherWithRedirects = composed.get(dispatcher);
if (!dispatcherWithRedirects) {
dispatcherWithRedirects = dispatcher.compose(redirect);
composed.set(dispatcher, dispatcherWithRedirects);
}
return dispatcherWithRedirects;
}

/**
* Determines if a proxy should be considered based on the environment.
Expand Down Expand Up @@ -55,50 +73,53 @@ function shouldUseProxyForURI(uri: string): boolean {
}

/**
* Returns a custom request Agent if one is found, otherwise returns undefined
* which will result in the global http(s) Agent being used.
* Returns a dispatcher for the given request. Proxied requests and requests
* with a socket limit get a cached dedicated dispatcher; everything else
* uses undici's global dispatcher, which pools and keeps connections alive
* by default.
* @private
* @param {string} uri The request uri
* @param {Options} reqOpts The request options
* @returns {HttpAnyAgent|undefined}
* @returns {Dispatcher}
*/
export function getAgent(
uri: string,
reqOpts: Options,
): HttpAnyAgent | undefined {
const isHttp = uri.startsWith('http://');
export function getDispatcher(uri: string, reqOpts: Options): Dispatcher {
const proxy =
reqOpts.proxy ||
process.env.HTTP_PROXY ||
process.env.http_proxy ||
process.env.HTTPS_PROXY ||
process.env.https_proxy;

const poolOptions = Object.assign({}, reqOpts.pool);

const manuallyProvidedProxy = !!reqOpts.proxy;
const shouldUseProxy = manuallyProvidedProxy || shouldUseProxyForURI(uri);

if (proxy && shouldUseProxy) {
// tslint:disable-next-line variable-name
const {HttpProxyAgent} = require('http-proxy-agent');
const {HttpsProxyAgent} = require('https-proxy-agent');
// `pool.maxSockets` historically only took effect for proxied requests
// and keep-alive (`forever`) agents; other agent options have no undici
// equivalent and are ignored
const maxSockets = reqOpts.pool?.maxSockets;
const connections =
typeof maxSockets === 'number' && Number.isFinite(maxSockets)
? maxSockets
: null;

const Agent = isHttp ? HttpProxyAgent : HttpsProxyAgent;
return new Agent(proxy, poolOptions);
if (proxy && shouldUseProxy) {
const key = `proxy:${proxy}:${connections}`;
if (!pool.has(key)) {
pool.set(
key,
new ProxyAgent({uri: proxy, ...(connections !== null && {connections})})
);
}
return withRedirects(pool.get(key)!);
}

let key = isHttp ? 'http' : 'https';

if (reqOpts.forever) {
key += ':forever';

if (reqOpts.forever && connections !== null) {
const key = `agent:${connections}`;
if (!pool.has(key)) {
// tslint:disable-next-line variable-name
const Agent = isHttp ? HTTPAgent : HTTPSAgent;
pool.set(key, new Agent({...poolOptions, keepAlive: true}));
pool.set(key, new Agent({connections}));
}
return withRedirects(pool.get(key)!);
}

return pool.get(key);
return withRedirects(getGlobalDispatcher());
}
Loading
Loading