Path: blob/master/node_modules/agent-base/src/index.ts
1129 views
import net from 'net';1import http from 'http';2import https from 'https';3import { Duplex } from 'stream';4import { EventEmitter } from 'events';5import createDebug from 'debug';6import promisify from './promisify';78const debug = createDebug('agent-base');910function isAgent(v: any): v is createAgent.AgentLike {11return Boolean(v) && typeof v.addRequest === 'function';12}1314function isSecureEndpoint(): boolean {15const { stack } = new Error();16if (typeof stack !== 'string') return false;17return stack.split('\n').some(l => l.indexOf('(https.js:') !== -1 || l.indexOf('node:https:') !== -1);18}1920function createAgent(opts?: createAgent.AgentOptions): createAgent.Agent;21function createAgent(22callback: createAgent.AgentCallback,23opts?: createAgent.AgentOptions24): createAgent.Agent;25function createAgent(26callback?: createAgent.AgentCallback | createAgent.AgentOptions,27opts?: createAgent.AgentOptions28) {29return new createAgent.Agent(callback, opts);30}3132namespace createAgent {33export interface ClientRequest extends http.ClientRequest {34_last?: boolean;35_hadError?: boolean;36method: string;37}3839export interface AgentRequestOptions {40host?: string;41path?: string;42// `port` on `http.RequestOptions` can be a string or undefined,43// but `net.TcpNetConnectOpts` expects only a number44port: number;45}4647export interface HttpRequestOptions48extends AgentRequestOptions,49Omit<http.RequestOptions, keyof AgentRequestOptions> {50secureEndpoint: false;51}5253export interface HttpsRequestOptions54extends AgentRequestOptions,55Omit<https.RequestOptions, keyof AgentRequestOptions> {56secureEndpoint: true;57}5859export type RequestOptions = HttpRequestOptions | HttpsRequestOptions;6061export type AgentLike = Pick<createAgent.Agent, 'addRequest'> | http.Agent;6263export type AgentCallbackReturn = Duplex | AgentLike;6465export type AgentCallbackCallback = (66err?: Error | null,67socket?: createAgent.AgentCallbackReturn68) => void;6970export type AgentCallbackPromise = (71req: createAgent.ClientRequest,72opts: createAgent.RequestOptions73) =>74| createAgent.AgentCallbackReturn75| Promise<createAgent.AgentCallbackReturn>;7677export type AgentCallback = typeof Agent.prototype.callback;7879export type AgentOptions = {80timeout?: number;81};8283/**84* Base `http.Agent` implementation.85* No pooling/keep-alive is implemented by default.86*87* @param {Function} callback88* @api public89*/90export class Agent extends EventEmitter {91public timeout: number | null;92public maxFreeSockets: number;93public maxTotalSockets: number;94public maxSockets: number;95public sockets: {96[key: string]: net.Socket[];97};98public freeSockets: {99[key: string]: net.Socket[];100};101public requests: {102[key: string]: http.IncomingMessage[];103};104public options: https.AgentOptions;105private promisifiedCallback?: createAgent.AgentCallbackPromise;106private explicitDefaultPort?: number;107private explicitProtocol?: string;108109constructor(110callback?: createAgent.AgentCallback | createAgent.AgentOptions,111_opts?: createAgent.AgentOptions112) {113super();114115let opts = _opts;116if (typeof callback === 'function') {117this.callback = callback;118} else if (callback) {119opts = callback;120}121122// Timeout for the socket to be returned from the callback123this.timeout = null;124if (opts && typeof opts.timeout === 'number') {125this.timeout = opts.timeout;126}127128// These aren't actually used by `agent-base`, but are required129// for the TypeScript definition files in `@types/node` :/130this.maxFreeSockets = 1;131this.maxSockets = 1;132this.maxTotalSockets = Infinity;133this.sockets = {};134this.freeSockets = {};135this.requests = {};136this.options = {};137}138139get defaultPort(): number {140if (typeof this.explicitDefaultPort === 'number') {141return this.explicitDefaultPort;142}143return isSecureEndpoint() ? 443 : 80;144}145146set defaultPort(v: number) {147this.explicitDefaultPort = v;148}149150get protocol(): string {151if (typeof this.explicitProtocol === 'string') {152return this.explicitProtocol;153}154return isSecureEndpoint() ? 'https:' : 'http:';155}156157set protocol(v: string) {158this.explicitProtocol = v;159}160161callback(162req: createAgent.ClientRequest,163opts: createAgent.RequestOptions,164fn: createAgent.AgentCallbackCallback165): void;166callback(167req: createAgent.ClientRequest,168opts: createAgent.RequestOptions169):170| createAgent.AgentCallbackReturn171| Promise<createAgent.AgentCallbackReturn>;172callback(173req: createAgent.ClientRequest,174opts: createAgent.AgentOptions,175fn?: createAgent.AgentCallbackCallback176):177| createAgent.AgentCallbackReturn178| Promise<createAgent.AgentCallbackReturn>179| void {180throw new Error(181'"agent-base" has no default implementation, you must subclass and override `callback()`'182);183}184185/**186* Called by node-core's "_http_client.js" module when creating187* a new HTTP request with this Agent instance.188*189* @api public190*/191addRequest(req: ClientRequest, _opts: RequestOptions): void {192const opts: RequestOptions = { ..._opts };193194if (typeof opts.secureEndpoint !== 'boolean') {195opts.secureEndpoint = isSecureEndpoint();196}197198if (opts.host == null) {199opts.host = 'localhost';200}201202if (opts.port == null) {203opts.port = opts.secureEndpoint ? 443 : 80;204}205206if (opts.protocol == null) {207opts.protocol = opts.secureEndpoint ? 'https:' : 'http:';208}209210if (opts.host && opts.path) {211// If both a `host` and `path` are specified then it's most212// likely the result of a `url.parse()` call... we need to213// remove the `path` portion so that `net.connect()` doesn't214// attempt to open that as a unix socket file.215delete opts.path;216}217218delete opts.agent;219delete opts.hostname;220delete opts._defaultAgent;221delete opts.defaultPort;222delete opts.createConnection;223224// Hint to use "Connection: close"225// XXX: non-documented `http` module API :(226req._last = true;227req.shouldKeepAlive = false;228229let timedOut = false;230let timeoutId: ReturnType<typeof setTimeout> | null = null;231const timeoutMs = opts.timeout || this.timeout;232233const onerror = (err: NodeJS.ErrnoException) => {234if (req._hadError) return;235req.emit('error', err);236// For Safety. Some additional errors might fire later on237// and we need to make sure we don't double-fire the error event.238req._hadError = true;239};240241const ontimeout = () => {242timeoutId = null;243timedOut = true;244const err: NodeJS.ErrnoException = new Error(245`A "socket" was not created for HTTP request before ${timeoutMs}ms`246);247err.code = 'ETIMEOUT';248onerror(err);249};250251const callbackError = (err: NodeJS.ErrnoException) => {252if (timedOut) return;253if (timeoutId !== null) {254clearTimeout(timeoutId);255timeoutId = null;256}257onerror(err);258};259260const onsocket = (socket: AgentCallbackReturn) => {261if (timedOut) return;262if (timeoutId != null) {263clearTimeout(timeoutId);264timeoutId = null;265}266267if (isAgent(socket)) {268// `socket` is actually an `http.Agent` instance, so269// relinquish responsibility for this `req` to the Agent270// from here on271debug(272'Callback returned another Agent instance %o',273socket.constructor.name274);275(socket as createAgent.Agent).addRequest(req, opts);276return;277}278279if (socket) {280socket.once('free', () => {281this.freeSocket(socket as net.Socket, opts);282});283req.onSocket(socket as net.Socket);284return;285}286287const err = new Error(288`no Duplex stream was returned to agent-base for \`${req.method} ${req.path}\``289);290onerror(err);291};292293if (typeof this.callback !== 'function') {294onerror(new Error('`callback` is not defined'));295return;296}297298if (!this.promisifiedCallback) {299if (this.callback.length >= 3) {300debug('Converting legacy callback function to promise');301this.promisifiedCallback = promisify(this.callback);302} else {303this.promisifiedCallback = this.callback;304}305}306307if (typeof timeoutMs === 'number' && timeoutMs > 0) {308timeoutId = setTimeout(ontimeout, timeoutMs);309}310311if ('port' in opts && typeof opts.port !== 'number') {312opts.port = Number(opts.port);313}314315try {316debug(317'Resolving socket for %o request: %o',318opts.protocol,319`${req.method} ${req.path}`320);321Promise.resolve(this.promisifiedCallback(req, opts)).then(322onsocket,323callbackError324);325} catch (err) {326Promise.reject(err).catch(callbackError);327}328}329330freeSocket(socket: net.Socket, opts: AgentOptions) {331debug('Freeing socket %o %o', socket.constructor.name, opts);332socket.destroy();333}334335destroy() {336debug('Destroying agent %o', this.constructor.name);337}338}339340// So that `instanceof` works correctly341createAgent.prototype = createAgent.Agent.prototype;342}343344export = createAgent;345346347