123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- '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;
|