'use strict'; const http = require('http'); const https = require('https'); const resolveALPN = require('resolve-alpn'); const QuickLRU = require('quick-lru'); const Http2ClientRequest = require('./client-request'); const calculateServerName = require('./utils/calculate-server-name'); const urlToOptions = require('./utils/url-to-options'); const cache = new QuickLRU({maxSize: 100}); const queue = new Map(); const installSocket = (agent, socket, options) => { socket._httpMessage = {shouldKeepAlive: true}; const onFree = () => { agent.emit('free', socket, options); }; socket.on('free', onFree); const onClose = () => { agent.removeSocket(socket, options); }; socket.on('close', onClose); const onRemove = () => { agent.removeSocket(socket, options); socket.off('close', onClose); socket.off('free', onFree); socket.off('agentRemove', onRemove); }; socket.on('agentRemove', onRemove); agent.emit('free', socket, options); }; const resolveProtocol = async options => { const name = `${options.host}:${options.port}:${options.ALPNProtocols.sort()}`; if (!cache.has(name)) { if (queue.has(name)) { const result = await queue.get(name); return result.alpnProtocol; } const {path, agent} = options; options.path = options.socketPath; const resultPromise = resolveALPN(options); queue.set(name, resultPromise); try { const {socket, alpnProtocol} = await resultPromise; cache.set(name, alpnProtocol); options.path = path; if (alpnProtocol === 'h2') { // https://github.com/nodejs/node/issues/33343 socket.destroy(); } else { const {globalAgent} = https; const defaultCreateConnection = https.Agent.prototype.createConnection; if (agent) { if (agent.createConnection === defaultCreateConnection) { installSocket(agent, socket, options); } else { socket.destroy(); } } else if (globalAgent.createConnection === defaultCreateConnection) { installSocket(globalAgent, socket, options); } else { socket.destroy(); } } queue.delete(name); return alpnProtocol; } catch (error) { queue.delete(name); throw error; } } return cache.get(name); }; module.exports = async (input, options, callback) => { if (typeof input === 'string' || input instanceof URL) { input = urlToOptions(new URL(input)); } if (typeof options === 'function') { callback = options; options = undefined; } options = { ALPNProtocols: ['h2', 'http/1.1'], ...input, ...options, resolveSocket: true }; if (!Array.isArray(options.ALPNProtocols) || options.ALPNProtocols.length === 0) { throw new Error('The `ALPNProtocols` option must be an Array with at least one entry'); } options.protocol = options.protocol || 'https:'; const isHttps = options.protocol === 'https:'; options.host = options.hostname || options.host || 'localhost'; options.session = options.tlsSession; options.servername = options.servername || calculateServerName(options); options.port = options.port || (isHttps ? 443 : 80); options._defaultAgent = isHttps ? https.globalAgent : http.globalAgent; const agents = options.agent; if (agents) { if (agents.addRequest) { throw new Error('The `options.agent` object can contain only `http`, `https` or `http2` properties'); } options.agent = agents[isHttps ? 'https' : 'http']; } if (isHttps) { const protocol = await resolveProtocol(options); if (protocol === 'h2') { if (agents) { options.agent = agents.http2; } return new Http2ClientRequest(options, callback); } } return http.request(options, callback); }; module.exports.protocolCache = cache;