const { URL } = require('url'); const {EventEmitter} = require('events'); const _extend = require('util')._extend; const {DebounceTimers , assertIsObject , ERR_INVALID_ARG_TYPE} = require('./utils'); const {initializeTLSOptions } = require('./request-options'); const http = require('http'); const https = require('https'); const {Stream} = require('stream'); function addFunctions(container , obj){ const proto = obj.prototype; Object.keys(proto).forEach((name)=>{ if (container.indexOf(name)!=-1) return; if (name.indexOf('_')!=0 && typeof proto[name] == 'function'){ container.push(name); } }) } const STUBBED_METHODS_NAME = [ ] //We need to proxy all v1 function addFunctions(STUBBED_METHODS_NAME, http.ClientRequest); addFunctions(STUBBED_METHODS_NAME, http.OutgoingMessage); addFunctions(STUBBED_METHODS_NAME, EventEmitter); addFunctions(STUBBED_METHODS_NAME, Stream); const PROPERTIES_TO_PROXY = [ 'httpVersionMajor', 'httpVersionMinor', 'httpVersion', ]; const HEADERS_TO_REMOVE = ['host' , 'connection'] const $stubs = Symbol('stubs'); function ClientRequest(){ this.http2Mimic = true; this[$stubs] = []; for (var i=0;i { if ((method === 'on' || method === 'once') && args[0] === 'error') { args[1](error); } }); } else return this.emit('error', error); }, take(stream){ //We forward all functions to the stream for (var i=0;i{ if (HEADERS_TO_REMOVE.indexOf( (key+'').toLowerCase() ) !=-1){ delete headers[key]; } }) requestOptions.headers = headers; var req = http2Client.request( headers ); inStream.emit('socket' , requestOptions.createConnection()); let maxContentLength; let currentContent = 0; req.on('data' , function onData(data){ currentContent+=data.length; if (currentContent>= maxContentLength) http2Debouncer.unpauseAndTime(clientKey); }) inStream.take(req); function onResponse(headers){ maxContentLength = parseInt(headers['content-length']); if (maxContentLength < 0 ) this.http2Debouncer.unpauseAndTime(clientKey); HttpRequestManager.httpCompatibleResponse(req , requestOptions , headers); inStream.emit('http1.response' , req); if (cb) cb(req); } onResponse.http2Safe = true; req.once('response' , onResponse.bind(this)); } static httpCompatibleResponse(res , requestOptions , headers){ res.httpVersion = '2.0'; res.rawHeaders = headers; res.headers = headers; res.statusCode = headers[':status']; delete headers[':status']; } identifyConnection(requestOptions , cb){ var socket = this.connect( requestOptions, { allowHTTP1: true }, function onConnect(){ socket.removeListener('error', cb); if (socket.alpnProtocol == 'h2'){ cb(null, 'h2') } else{ //close http1.1 connection is it cannot be reused socket.end(); cb(null, 'h1') } }); socket.on('error', cb); return socket; } connect(authority, options, listener) { if (typeof options === 'function') { listener = options; options = undefined; } assertIsObject(options, 'options'); options = Object.assign({}, options); if (typeof authority === 'string') authority = new URL(authority); assertIsObject(authority, 'authority', ['string', 'Object', 'URL']); var protocol = authority.protocol || options.protocol || (this.enforceProtocol != 'detect' ? this.enforceProtocol : null) || 'http:'; var port = '' + (authority.port !== '' ? authority.port : (authority.protocol === 'http:' ? 80 : 443)); var host = authority.hostname || authority.host || 'localhost'; var socket; if (typeof options.createConnection === 'function') { socket = options.createConnection(authority, options); } else { switch (protocol) { case 'http:': socket = this.net.connect(port, host , listener); break; case 'https:': socket = this.tls.connect(port, host, initializeTLSOptions.call(this , options, host) , listener); break; default: throw new Error('Not supprted' + protocol); } } return socket; } } function urlToOptions(url) { var options = { protocol: url.protocol, hostname: url.hostname, hash: url.hash, search: url.search, pathname: url.pathname, path: `${url.pathname}${url.search}`, href: url.href }; if (url.port !== '') { options.port = Number(url.port); } if (url.username || url.password) { options.auth = `${url.username}:${url.password}`; } return options; } class RequestInternalEnforce{ constructor(args){ if (args[0] instanceof RequestInternalEnforce){ return args[0]; } this.args = args; this.method = null; this.protocol = null; } } class HttpsRequest extends HttpRequestManager{ constructor(){ super(...arguments); this.Agent = https.Agent; this.globalAgent = https.globalAgent; this.enforceProtocol = 'https:'; } } const httpsRequestSinglton = new HttpsRequest; HttpsRequest.globalManager = httpsRequestSinglton; HttpsRequest.Manager = HttpsRequest; class HttpRequest extends HttpRequestManager{ constructor(){ super(...arguments); this.Agent = http.Agent; this.globalAgent = http.globalAgent; this.enforceProtocol = 'http:'; } } const httpRequestSinglton = new HttpRequest; HttpRequest.globalManager = httpRequestSinglton; HttpRequest.Manager = HttpRequest; const singeltonHttpManager = new HttpRequestManager(); singeltonHttpManager.enforceProtocol = 'detect'; HttpRequestManager.globalManager = singeltonHttpManager; module.exports = { HttpRequest, HttpsRequest, HTTP2OutgoingMessage : ClientRequest, ClientRequest, HttpRequestManager }