229 lines
10 KiB
JavaScript
229 lines
10 KiB
JavaScript
|
"use strict";
|
||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||
|
}
|
||
|
Object.defineProperty(o, k2, desc);
|
||
|
}) : (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
o[k2] = m[k];
|
||
|
}));
|
||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
|
}) : function(o, v) {
|
||
|
o["default"] = v;
|
||
|
});
|
||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||
|
if (mod && mod.__esModule) return mod;
|
||
|
var result = {};
|
||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||
|
__setModuleDefault(result, mod);
|
||
|
return result;
|
||
|
};
|
||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
const http_1 = __importDefault(require("http"));
|
||
|
const https_1 = __importDefault(require("https"));
|
||
|
const lodash_1 = require("lodash");
|
||
|
const requestAgent = __importStar(require("./agent"));
|
||
|
const events_1 = require("events");
|
||
|
const webauth_1 = require("webauth");
|
||
|
const connection_reset_guard_1 = require("../connection-reset-guard");
|
||
|
const messages_1 = require("../../messages");
|
||
|
const logger_1 = __importDefault(require("../../utils/logger"));
|
||
|
const requestCache = __importStar(require("../cache"));
|
||
|
const http2_1 = require("./http2");
|
||
|
const default_request_timeout_1 = __importDefault(require("./default-request-timeout"));
|
||
|
const TUNNELING_SOCKET_ERR_RE = /tunneling socket could not be established/i;
|
||
|
const TUNNELING_AUTHORIZE_ERR_RE = /statusCode=407/i;
|
||
|
const SOCKET_HANG_UP_ERR_RE = /socket hang up/i;
|
||
|
const IS_DNS_ERR_MSG_RE = /ECONNREFUSED|ENOTFOUND|EPROTO/;
|
||
|
const IS_DNS_ERR_CODE_RE = /ECONNRESET/;
|
||
|
class DestinationRequest extends events_1.EventEmitter {
|
||
|
constructor(opts, cache = false) {
|
||
|
super();
|
||
|
this.opts = opts;
|
||
|
this.cache = cache;
|
||
|
this.hasResponse = false;
|
||
|
this.credentialsSent = false;
|
||
|
this.aborted = false;
|
||
|
this.protocolInterface = this.opts.isHttps ? https_1.default : http_1.default;
|
||
|
this.timeout = this._getTimeout();
|
||
|
if (this.opts.isHttps)
|
||
|
opts.ignoreSSLAuth();
|
||
|
requestAgent.assign(this.opts);
|
||
|
this._send();
|
||
|
}
|
||
|
_getTimeout() {
|
||
|
return this.opts.isAjax
|
||
|
? this.opts.requestTimeout && this.opts.requestTimeout.ajax || default_request_timeout_1.default.ajax
|
||
|
: this.opts.requestTimeout && this.opts.requestTimeout.page || default_request_timeout_1.default.page;
|
||
|
}
|
||
|
static _isHttp2ProtocolError(err) {
|
||
|
return err.code === 'ERR_HTTP2_STREAM_ERROR' &&
|
||
|
(err.message.includes('NGHTTP2_PROTOCOL_ERROR') || err.message.includes('NGHTTP2_HTTP_1_1_REQUIRED'));
|
||
|
}
|
||
|
_sendRealThroughHttp2(session) {
|
||
|
const reqHeaders = (0, http2_1.formatRequestHttp2Headers)(this.opts);
|
||
|
const endStream = !this.opts.body.length;
|
||
|
const stream = session.request(reqHeaders, { endStream });
|
||
|
stream.setTimeout(this.timeout, () => this._onTimeout());
|
||
|
stream.on('error', (err) => {
|
||
|
if (DestinationRequest._isHttp2ProtocolError(err)) {
|
||
|
session.destroy();
|
||
|
this._sendReal();
|
||
|
}
|
||
|
else
|
||
|
this._onError(err);
|
||
|
});
|
||
|
stream.on('response', headers => {
|
||
|
const http2res = (0, http2_1.createResponseLike)(stream, headers);
|
||
|
this._onResponse(http2res);
|
||
|
});
|
||
|
if (!endStream) {
|
||
|
stream.write(this.opts.body);
|
||
|
stream.end();
|
||
|
}
|
||
|
this.req = stream;
|
||
|
logger_1.default.destination.onHttp2Stream(this.opts.requestId, reqHeaders);
|
||
|
}
|
||
|
_sendReal(waitForData) {
|
||
|
(0, connection_reset_guard_1.connectionResetGuard)(() => {
|
||
|
const preparedOpts = this.opts.prepare();
|
||
|
this.req = this.protocolInterface.request(preparedOpts, res => {
|
||
|
if (waitForData) {
|
||
|
res.on('data', lodash_1.noop);
|
||
|
res.once('end', () => this._onResponse(res));
|
||
|
}
|
||
|
});
|
||
|
if (logger_1.default.destinationSocket.enabled) {
|
||
|
this.req.on('socket', socket => {
|
||
|
socket.once('data', data => logger_1.default.destinationSocket.onFirstChunk(this.opts, data));
|
||
|
socket.once('error', err => logger_1.default.destinationSocket.onError(this.opts, err));
|
||
|
});
|
||
|
}
|
||
|
if (!waitForData)
|
||
|
this.req.on('response', (res) => this._onResponse(res));
|
||
|
this.req.on('upgrade', (res, socket, head) => this._onUpgrade(res, socket, head));
|
||
|
this.req.on('error', (err) => this._onError(err));
|
||
|
this.req.setTimeout(this.timeout, () => this._onTimeout());
|
||
|
this.req.write(this.opts.body);
|
||
|
this.req.end();
|
||
|
logger_1.default.destination.onRequest(this.opts);
|
||
|
});
|
||
|
}
|
||
|
async _send(waitForData) {
|
||
|
if (this.cache) {
|
||
|
const cachedResponse = requestCache.getResponse(this.opts);
|
||
|
if (cachedResponse) {
|
||
|
// NOTE: To store async order of the 'response' event
|
||
|
setImmediate(() => this._emitOnResponse(cachedResponse.res));
|
||
|
logger_1.default.destination.onCachedRequest(this.opts, cachedResponse.hitCount);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
const http2Session = !this.opts.disableHttp2 && this.opts.isHttps && !this.opts.isWebSocket && !this.opts.proxy &&
|
||
|
await (0, http2_1.getHttp2Session)(this.opts.requestId, this.opts.protocol + '//' + this.opts.host);
|
||
|
if (http2Session && !http2Session.closed)
|
||
|
this._sendRealThroughHttp2(http2Session);
|
||
|
else
|
||
|
this._sendReal(waitForData);
|
||
|
}
|
||
|
_shouldResendWithCredentials(res) {
|
||
|
if (res.statusCode === 401 && this.opts.credentials) {
|
||
|
const authInfo = (0, webauth_1.getAuthInfo)(res);
|
||
|
// NOTE: If we get 401 status code after credentials are sent, we should stop trying to authenticate.
|
||
|
if (!authInfo.isChallengeMessage && this.credentialsSent)
|
||
|
return false;
|
||
|
return authInfo.canAuthorize;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
_onResponse(res) {
|
||
|
logger_1.default.destination.onResponse(this.opts, res);
|
||
|
if (this._shouldResendWithCredentials(res))
|
||
|
this._resendWithCredentials(res);
|
||
|
else if (!this.opts.isHttps && this.opts.proxy && res.statusCode === 407) {
|
||
|
logger_1.default.destination.onProxyAuthenticationError(this.opts);
|
||
|
this._fatalError(messages_1.MESSAGE.cantAuthorizeToProxy, this.opts.proxy.host);
|
||
|
}
|
||
|
else
|
||
|
this._emitOnResponse(res);
|
||
|
}
|
||
|
_emitOnResponse(res) {
|
||
|
this.hasResponse = true;
|
||
|
this.emit('response', res);
|
||
|
}
|
||
|
_onUpgrade(res, socket, head) {
|
||
|
logger_1.default.destination.onUpgradeRequest(this.opts, res);
|
||
|
if (head && head.length)
|
||
|
socket.unshift(head);
|
||
|
this._onResponse(res);
|
||
|
}
|
||
|
_resendWithCredentials(res) {
|
||
|
logger_1.default.destination.onResendWithCredentials(this.opts);
|
||
|
(0, webauth_1.addCredentials)(this.opts.credentials, this.opts, res, this.protocolInterface);
|
||
|
this.credentialsSent = true;
|
||
|
// NOTE: NTLM authentication requires using the same socket for the "negotiate" and "authenticate" requests.
|
||
|
// So, before sending the "authenticate" message, we should wait for data from the "challenge" response. It
|
||
|
// will mean that the socket is free.
|
||
|
this._send((0, webauth_1.requiresResBody)(res));
|
||
|
}
|
||
|
_fatalError(msg, url) {
|
||
|
if (!this.aborted) {
|
||
|
this.aborted = true;
|
||
|
this.req.destroy();
|
||
|
this.emit('fatalError', (0, messages_1.getText)(msg, { url: url || this.opts.url }));
|
||
|
}
|
||
|
}
|
||
|
_isDNSErr(err) {
|
||
|
return err.message && IS_DNS_ERR_MSG_RE.test(err.message) ||
|
||
|
!this.aborted && !this.hasResponse && err.code && IS_DNS_ERR_CODE_RE.test(err.code);
|
||
|
}
|
||
|
_isTunnelingErr(err) {
|
||
|
return this.opts.isHttps && err.message && TUNNELING_SOCKET_ERR_RE.test(err.message);
|
||
|
}
|
||
|
_isSocketHangUpErr(err) {
|
||
|
return err.message && SOCKET_HANG_UP_ERR_RE.test(err.message) &&
|
||
|
// NOTE: At this moment, we determinate the socket hand up error by internal stack trace.
|
||
|
// TODO: After what we will change minimal node.js version up to 8 need to rethink this code.
|
||
|
err.stack && (err.stack.includes('createHangUpError') || err.stack.includes('connResetException'));
|
||
|
}
|
||
|
_onTimeout() {
|
||
|
logger_1.default.destination.onTimeoutError(this.opts, this.timeout);
|
||
|
// NOTE: this handler is also called if we get an error response (for example, 404). So, we should check
|
||
|
// for the response presence before raising the timeout error.
|
||
|
if (!this.hasResponse)
|
||
|
this._fatalError(messages_1.MESSAGE.destRequestTimeout);
|
||
|
}
|
||
|
_onError(err) {
|
||
|
logger_1.default.destination.onError(this.opts, err);
|
||
|
if (this._isSocketHangUpErr(err))
|
||
|
this.emit('socketHangUp');
|
||
|
else if (requestAgent.shouldRegressHttps(err, this.opts)) {
|
||
|
requestAgent.regressHttps(this.opts);
|
||
|
this._send();
|
||
|
}
|
||
|
else if (this.opts.proxy && this._isTunnelingErr(err)) {
|
||
|
if (TUNNELING_AUTHORIZE_ERR_RE.test(err.message))
|
||
|
this._fatalError(messages_1.MESSAGE.cantAuthorizeToProxy, this.opts.proxy.host);
|
||
|
else
|
||
|
this._fatalError(messages_1.MESSAGE.cantEstablishTunnelingConnection, this.opts.proxy.host);
|
||
|
}
|
||
|
else if (this._isDNSErr(err)) {
|
||
|
if (!this.opts.isHttps && this.opts.proxy)
|
||
|
this._fatalError(messages_1.MESSAGE.cantEstablishProxyConnection, this.opts.proxy.host);
|
||
|
else
|
||
|
this._fatalError(messages_1.MESSAGE.cantResolveUrl);
|
||
|
}
|
||
|
else
|
||
|
this.emit('error', err);
|
||
|
}
|
||
|
}
|
||
|
exports.default = DestinationRequest;module.exports = exports.default;
|
||
|
|