123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- 'use strict';
- const net = require('net');
- class TimeoutError extends Error {
- constructor(threshold, event) {
- super(`Timeout awaiting '${event}' for ${threshold}ms`);
- this.name = 'TimeoutError';
- this.code = 'ETIMEDOUT';
- this.event = event;
- }
- }
- const reentry = Symbol('reentry');
- const noop = () => {};
- module.exports = (request, delays, options) => {
- /* istanbul ignore next: this makes sure timed-out isn't called twice */
- if (request[reentry]) {
- return;
- }
- request[reentry] = true;
- let stopNewTimeouts = false;
- const addTimeout = (delay, callback, ...args) => {
- // An error had been thrown before. Going further would result in uncaught errors.
- // See https://github.com/sindresorhus/got/issues/631#issuecomment-435675051
- if (stopNewTimeouts) {
- return noop;
- }
- // Event loop order is timers, poll, immediates.
- // The timed event may emit during the current tick poll phase, so
- // defer calling the handler until the poll phase completes.
- let immediate;
- const timeout = setTimeout(() => {
- immediate = setImmediate(callback, delay, ...args);
- /* istanbul ignore next: added in node v9.7.0 */
- if (immediate.unref) {
- immediate.unref();
- }
- }, delay);
- /* istanbul ignore next: in order to support electron renderer */
- if (timeout.unref) {
- timeout.unref();
- }
- const cancel = () => {
- clearTimeout(timeout);
- clearImmediate(immediate);
- };
- cancelers.push(cancel);
- return cancel;
- };
- const {host, hostname} = options;
- const timeoutHandler = (delay, event) => {
- request.emit('error', new TimeoutError(delay, event));
- request.once('error', () => {}); // Ignore the `socket hung up` error made by request.abort()
- request.abort();
- };
- const cancelers = [];
- const cancelTimeouts = () => {
- stopNewTimeouts = true;
- cancelers.forEach(cancelTimeout => cancelTimeout());
- };
- request.once('error', cancelTimeouts);
- request.once('response', response => {
- response.once('end', cancelTimeouts);
- });
- if (delays.request !== undefined) {
- addTimeout(delays.request, timeoutHandler, 'request');
- }
- if (delays.socket !== undefined) {
- const socketTimeoutHandler = () => {
- timeoutHandler(delays.socket, 'socket');
- };
- request.setTimeout(delays.socket, socketTimeoutHandler);
- // `request.setTimeout(0)` causes a memory leak.
- // We can just remove the listener and forget about the timer - it's unreffed.
- // See https://github.com/sindresorhus/got/issues/690
- cancelers.push(() => request.removeListener('timeout', socketTimeoutHandler));
- }
- if (delays.lookup !== undefined && !request.socketPath && !net.isIP(hostname || host)) {
- request.once('socket', socket => {
- /* istanbul ignore next: hard to test */
- if (socket.connecting) {
- const cancelTimeout = addTimeout(delays.lookup, timeoutHandler, 'lookup');
- socket.once('lookup', cancelTimeout);
- }
- });
- }
- if (delays.connect !== undefined) {
- request.once('socket', socket => {
- /* istanbul ignore next: hard to test */
- if (socket.connecting) {
- const timeConnect = () => addTimeout(delays.connect, timeoutHandler, 'connect');
- if (request.socketPath || net.isIP(hostname || host)) {
- socket.once('connect', timeConnect());
- } else {
- socket.once('lookup', error => {
- if (error === null) {
- socket.once('connect', timeConnect());
- }
- });
- }
- }
- });
- }
- if (delays.secureConnect !== undefined && options.protocol === 'https:') {
- request.once('socket', socket => {
- /* istanbul ignore next: hard to test */
- if (socket.connecting) {
- socket.once('connect', () => {
- const cancelTimeout = addTimeout(delays.secureConnect, timeoutHandler, 'secureConnect');
- socket.once('secureConnect', cancelTimeout);
- });
- }
- });
- }
- if (delays.send !== undefined) {
- request.once('socket', socket => {
- const timeRequest = () => addTimeout(delays.send, timeoutHandler, 'send');
- /* istanbul ignore next: hard to test */
- if (socket.connecting) {
- socket.once('connect', () => {
- request.once('upload-complete', timeRequest());
- });
- } else {
- request.once('upload-complete', timeRequest());
- }
- });
- }
- if (delays.response !== undefined) {
- request.once('upload-complete', () => {
- const cancelTimeout = addTimeout(delays.response, timeoutHandler, 'response');
- request.once('response', cancelTimeout);
- });
- }
- };
- module.exports.TimeoutError = TimeoutError;
|