mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
This reverts commit 55b952f958601b3177607a067d74352e6a4a6cd4.
This commit is contained in:
@@ -1,24 +0,0 @@
|
|||||||
import http from 'node:http';
|
|
||||||
import { fastLookup } from './fast-lookup';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shared agent configuration for HTTP/HTTPS agents across the application.
|
|
||||||
*
|
|
||||||
* - keepAlive: Reuse TCP connections to avoid repeated handshakes.
|
|
||||||
* - maxSockets: 100 concurrent sockets per host — high enough for parallel
|
|
||||||
* collection runs, low enough to avoid file-descriptor exhaustion.
|
|
||||||
* - maxFreeSockets: 10 idle sockets kept alive for reuse between bursts.
|
|
||||||
* - scheduling: 'fifo' distributes requests across connections evenly,
|
|
||||||
* which avoids head-of-line blocking that 'lifo' (Node's default) can
|
|
||||||
* cause when one connection stalls.
|
|
||||||
* - lookup: fastLookup uses async c-ares (dns.resolve4/6) to bypass the
|
|
||||||
* libuv thread pool bottleneck, falling back to dns.lookup for /etc/hosts
|
|
||||||
* and mDNS hostnames.
|
|
||||||
*/
|
|
||||||
export const defaultAgentOptions: http.AgentOptions = {
|
|
||||||
keepAlive: true,
|
|
||||||
maxSockets: 100,
|
|
||||||
maxFreeSockets: 10,
|
|
||||||
scheduling: 'fifo',
|
|
||||||
lookup: fastLookup as http.AgentOptions['lookup']
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { default as axios, AxiosRequestConfig, AxiosRequestHeaders, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
import { default as axios, AxiosRequestConfig, AxiosRequestHeaders, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import https from 'node:https';
|
import https from 'node:https';
|
||||||
import { defaultAgentOptions } from './agent-defaults';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -30,8 +29,8 @@ type ModifiedAxiosResponse = AxiosResponse & {
|
|||||||
|
|
||||||
const baseRequestConfig: Partial<AxiosRequestConfig> = {
|
const baseRequestConfig: Partial<AxiosRequestConfig> = {
|
||||||
proxy: false,
|
proxy: false,
|
||||||
httpAgent: new http.Agent(defaultAgentOptions),
|
httpAgent: new http.Agent({ keepAlive: true }),
|
||||||
httpsAgent: new https.Agent(defaultAgentOptions),
|
httpsAgent: new https.Agent({ keepAlive: true }),
|
||||||
transformRequest: function transformRequest(data: any, headers: AxiosRequestHeaders) {
|
transformRequest: function transformRequest(data: any, headers: AxiosRequestHeaders) {
|
||||||
const contentType = headers.getContentType() || '';
|
const contentType = headers.getContentType() || '';
|
||||||
const hasJSONContentType = contentType.includes('json');
|
const hasJSONContentType = contentType.includes('json');
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
import dns from 'node:dns';
|
|
||||||
import { fastLookup } from './fast-lookup';
|
|
||||||
|
|
||||||
type DnsMethod = 'resolve4' | 'resolve6';
|
|
||||||
|
|
||||||
function mockResolve(method: DnsMethod, result: string[], err: Error | null = null): void {
|
|
||||||
(jest.spyOn(dns, method) as any).mockImplementation((_hostname: string, cb: Function) => {
|
|
||||||
cb(err, result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockLookup(address: string, family: number): void {
|
|
||||||
(jest.spyOn(dns, 'lookup') as any).mockImplementation((_hostname: string, _options: dns.LookupOptions, cb: Function) => {
|
|
||||||
cb(null, address, family);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('fastLookup', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve a public hostname via dns.resolve4', (done) => {
|
|
||||||
mockResolve('resolve4', ['93.184.216.34']);
|
|
||||||
|
|
||||||
fastLookup('example.com', {}, (err, address, family) => {
|
|
||||||
expect(err).toBeNull();
|
|
||||||
expect(address).toBe('93.184.216.34');
|
|
||||||
expect(family).toBe(4);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use dns.lookup directly for .local hostnames (mDNS)', (done) => {
|
|
||||||
mockResolve('resolve4', ['192.0.78.134']); // bogus public-DNS result
|
|
||||||
mockLookup('192.168.33.254', 4); // correct mDNS result
|
|
||||||
|
|
||||||
fastLookup('fhir.local', {}, (err, address, family) => {
|
|
||||||
expect(err).toBeNull();
|
|
||||||
expect(address).toBe('192.168.33.254');
|
|
||||||
expect(family).toBe(4);
|
|
||||||
expect(dns.resolve4).not.toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use dns.lookup for mixed-case .LOCAL hostnames', (done) => {
|
|
||||||
mockResolve('resolve4', ['192.0.78.134']);
|
|
||||||
mockLookup('192.168.33.254', 4);
|
|
||||||
|
|
||||||
fastLookup('FHIR.LOCAL', {}, (err, address, family) => {
|
|
||||||
expect(err).toBeNull();
|
|
||||||
expect(address).toBe('192.168.33.254');
|
|
||||||
expect(family).toBe(4);
|
|
||||||
expect(dns.resolve4).not.toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use dns.lookup directly for localhost (RFC 6761)', (done) => {
|
|
||||||
mockResolve('resolve4', ['93.184.216.34']); // hijacked result
|
|
||||||
mockLookup('127.0.0.1', 4);
|
|
||||||
|
|
||||||
fastLookup('localhost', {}, (err, address, family) => {
|
|
||||||
expect(err).toBeNull();
|
|
||||||
expect(address).toBe('127.0.0.1');
|
|
||||||
expect(family).toBe(4);
|
|
||||||
expect(dns.resolve4).not.toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use dns.lookup for .localhost subdomains', (done) => {
|
|
||||||
mockResolve('resolve4', ['93.184.216.34']);
|
|
||||||
mockLookup('127.0.0.1', 4);
|
|
||||||
|
|
||||||
fastLookup('api.localhost', {}, (err, address, family) => {
|
|
||||||
expect(err).toBeNull();
|
|
||||||
expect(address).toBe('127.0.0.1');
|
|
||||||
expect(family).toBe(4);
|
|
||||||
expect(dns.resolve4).not.toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fall back to dns.lookup when both resolvers fail', (done) => {
|
|
||||||
mockResolve('resolve4', [], new Error('ENOTFOUND'));
|
|
||||||
mockResolve('resolve6', [], new Error('ENOTFOUND'));
|
|
||||||
mockLookup('127.0.0.1', 4);
|
|
||||||
|
|
||||||
fastLookup('my-local-host', {}, (err, address, family) => {
|
|
||||||
expect(err).toBeNull();
|
|
||||||
expect(address).toBe('127.0.0.1');
|
|
||||||
expect(family).toBe(4);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return all addresses when options.all is true', (done) => {
|
|
||||||
mockResolve('resolve4', ['1.2.3.4', '5.6.7.8']);
|
|
||||||
|
|
||||||
fastLookup('example.com', { all: true }, (err, addresses) => {
|
|
||||||
expect(err).toBeNull();
|
|
||||||
expect(Array.isArray(addresses)).toBe(true);
|
|
||||||
expect(addresses).toEqual([
|
|
||||||
{ address: '1.2.3.4', family: 4 },
|
|
||||||
{ address: '5.6.7.8', family: 4 }
|
|
||||||
]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import dns from 'node:dns';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fast DNS lookup that bypasses the libuv thread pool.
|
|
||||||
*
|
|
||||||
* Tries dns.resolve4 then dns.resolve6 (async, c-ares based),
|
|
||||||
* falls back to dns.lookup for /etc/hosts and mDNS hostnames.
|
|
||||||
*
|
|
||||||
* .local hostnames are reserved for mDNS (RFC 6762) and must always use the
|
|
||||||
* OS resolver (dns.lookup / getaddrinfo) — c-ares doesn't speak mDNS, so
|
|
||||||
* dns.resolve4 can return bogus public-DNS results for .local names.
|
|
||||||
*
|
|
||||||
* NOTE: `options.family` is not currently respected — the function always
|
|
||||||
* tries IPv4 first regardless of the caller's preference. This is safe today
|
|
||||||
* because Bruno's HTTP agents use the default family (0), but should be
|
|
||||||
* addressed if any code path starts specifying a family.
|
|
||||||
*/
|
|
||||||
export function fastLookup(
|
|
||||||
hostname: string,
|
|
||||||
options: dns.LookupOptions | undefined,
|
|
||||||
callback: (err: Error | null, address: string | dns.LookupAddress[], family?: number) => void
|
|
||||||
): void {
|
|
||||||
// .local domains use mDNS (RFC 6762 — https://datatracker.ietf.org/doc/html/rfc6762)
|
|
||||||
// which only the OS resolver understands. c-ares queries public DNS and may
|
|
||||||
// return wrong results.
|
|
||||||
// localhost is reserved (RFC 6761 — https://datatracker.ietf.org/doc/html/rfc6761)
|
|
||||||
// and must always resolve via the OS — c-ares could return hijacked results.
|
|
||||||
const lower = hostname.toLowerCase();
|
|
||||||
if (lower.endsWith('.local') || lower === 'localhost' || lower.endsWith('.localhost')) {
|
|
||||||
return dns.lookup(hostname, options ?? {}, (err, address, family) => {
|
|
||||||
callback(err, address, family);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dns.resolve4(hostname, (err4, addresses4) => {
|
|
||||||
if (!err4 && addresses4?.length) {
|
|
||||||
return options?.all
|
|
||||||
? callback(null, addresses4.map((a) => ({ address: a, family: 4 })))
|
|
||||||
: callback(null, addresses4[0], 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward to standard dns.lookup for /etc/hosts, mDNS, and other
|
|
||||||
// non-public hostnames that c-ares cannot resolve.
|
|
||||||
dns.lookup(hostname, options ?? {}, (err, address, family) => {
|
|
||||||
callback(err, address, family);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ import tls from 'node:tls';
|
|||||||
import type { Agent as HttpAgent } from 'node:http';
|
import type { Agent as HttpAgent } from 'node:http';
|
||||||
import type { Agent as HttpsAgent } from 'node:https';
|
import type { Agent as HttpsAgent } from 'node:https';
|
||||||
import { createTimelineAgentClass, createTimelineHttpAgentClass, type TimelineEntry, type AgentOptions, type HttpAgentOptions, type AgentClass, type HttpAgentClass } from './timeline-agent';
|
import { createTimelineAgentClass, createTimelineHttpAgentClass, type TimelineEntry, type AgentOptions, type HttpAgentOptions, type AgentClass, type HttpAgentClass } from './timeline-agent';
|
||||||
import { defaultAgentOptions } from '../network/agent-defaults';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent cache for SSL session reuse.
|
* Agent cache for SSL session reuse.
|
||||||
@@ -268,16 +267,8 @@ function getOrCreateAgentInternal<TOptions extends HttpAgentOptions>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AgentClass = timeline ? getTimelineClass(BaseAgentClass) : BaseAgentClass;
|
const AgentClass = timeline ? getTimelineClass(BaseAgentClass) : BaseAgentClass;
|
||||||
|
|
||||||
// Inject shared agent defaults (DNS lookup, socket pool settings), then
|
|
||||||
// layer on the caller's options so per-agent overrides still take effect.
|
|
||||||
const optimizedOptions = {
|
|
||||||
...defaultAgentOptions,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert raw `ca` to a secureContext that adds CAs on top of OpenSSL defaults
|
// Convert raw `ca` to a secureContext that adds CAs on top of OpenSSL defaults
|
||||||
const resolvedOptions = applySecureContext(optimizedOptions);
|
const resolvedOptions = applySecureContext(options);
|
||||||
|
|
||||||
let agent: HttpAgent | HttpsAgent;
|
let agent: HttpAgent | HttpsAgent;
|
||||||
if (timeline) {
|
if (timeline) {
|
||||||
|
|||||||
Reference in New Issue
Block a user