connect.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. 'use strict'
  2. const net = require('node:net')
  3. const assert = require('node:assert')
  4. const util = require('./util')
  5. const { InvalidArgumentError } = require('./errors')
  6. let tls // include tls conditionally since it is not always available
  7. // TODO: session re-use does not wait for the first
  8. // connection to resolve the session and might therefore
  9. // resolve the same servername multiple times even when
  10. // re-use is enabled.
  11. const SessionCache = class WeakSessionCache {
  12. constructor (maxCachedSessions) {
  13. this._maxCachedSessions = maxCachedSessions
  14. this._sessionCache = new Map()
  15. this._sessionRegistry = new FinalizationRegistry((key) => {
  16. if (this._sessionCache.size < this._maxCachedSessions) {
  17. return
  18. }
  19. const ref = this._sessionCache.get(key)
  20. if (ref !== undefined && ref.deref() === undefined) {
  21. this._sessionCache.delete(key)
  22. }
  23. })
  24. }
  25. get (sessionKey) {
  26. const ref = this._sessionCache.get(sessionKey)
  27. return ref ? ref.deref() : null
  28. }
  29. set (sessionKey, session) {
  30. if (this._maxCachedSessions === 0) {
  31. return
  32. }
  33. this._sessionCache.set(sessionKey, new WeakRef(session))
  34. this._sessionRegistry.register(session, sessionKey)
  35. }
  36. }
  37. function buildConnector ({ allowH2, useH2c, maxCachedSessions, socketPath, timeout, session: customSession, ...opts }) {
  38. if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
  39. throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
  40. }
  41. const options = { path: socketPath, ...opts }
  42. const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions)
  43. timeout = timeout == null ? 10e3 : timeout
  44. allowH2 = allowH2 != null ? allowH2 : false
  45. return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) {
  46. let socket
  47. if (protocol === 'https:') {
  48. if (!tls) {
  49. tls = require('node:tls')
  50. }
  51. servername = servername || options.servername || util.getServerName(host) || null
  52. const sessionKey = servername || hostname
  53. assert(sessionKey)
  54. const session = customSession || sessionCache.get(sessionKey) || null
  55. port = port || 443
  56. socket = tls.connect({
  57. highWaterMark: 16384, // TLS in node can't have bigger HWM anyway...
  58. ...options,
  59. servername,
  60. session,
  61. localAddress,
  62. ALPNProtocols: allowH2 ? ['http/1.1', 'h2'] : ['http/1.1'],
  63. socket: httpSocket, // upgrade socket connection
  64. port,
  65. host: hostname
  66. })
  67. socket
  68. .on('session', function (session) {
  69. // TODO (fix): Can a session become invalid once established? Don't think so?
  70. sessionCache.set(sessionKey, session)
  71. })
  72. } else {
  73. assert(!httpSocket, 'httpSocket can only be sent on TLS update')
  74. port = port || 80
  75. socket = net.connect({
  76. highWaterMark: 64 * 1024, // Same as nodejs fs streams.
  77. ...options,
  78. localAddress,
  79. port,
  80. host: hostname
  81. })
  82. if (useH2c === true) {
  83. socket.alpnProtocol = 'h2'
  84. }
  85. }
  86. // Set TCP keep alive options on the socket here instead of in connect() for the case of assigning the socket
  87. if (options.keepAlive == null || options.keepAlive) {
  88. const keepAliveInitialDelay = options.keepAliveInitialDelay === undefined ? 60e3 : options.keepAliveInitialDelay
  89. socket.setKeepAlive(true, keepAliveInitialDelay)
  90. }
  91. const clearConnectTimeout = util.setupConnectTimeout(new WeakRef(socket), { timeout, hostname, port })
  92. socket
  93. .setNoDelay(true)
  94. .once(protocol === 'https:' ? 'secureConnect' : 'connect', function () {
  95. queueMicrotask(clearConnectTimeout)
  96. if (callback) {
  97. const cb = callback
  98. callback = null
  99. cb(null, this)
  100. }
  101. })
  102. .on('error', function (err) {
  103. queueMicrotask(clearConnectTimeout)
  104. if (callback) {
  105. const cb = callback
  106. callback = null
  107. cb(err)
  108. }
  109. })
  110. return socket
  111. }
  112. }
  113. module.exports = buildConnector