pool.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. 'use strict'
  2. const {
  3. PoolBase,
  4. kClients,
  5. kNeedDrain,
  6. kAddClient,
  7. kGetDispatcher,
  8. kRemoveClient
  9. } = require('./pool-base')
  10. const Client = require('./client')
  11. const {
  12. InvalidArgumentError
  13. } = require('../core/errors')
  14. const util = require('../core/util')
  15. const { kUrl } = require('../core/symbols')
  16. const buildConnector = require('../core/connect')
  17. const kOptions = Symbol('options')
  18. const kConnections = Symbol('connections')
  19. const kFactory = Symbol('factory')
  20. function defaultFactory (origin, opts) {
  21. return new Client(origin, opts)
  22. }
  23. class Pool extends PoolBase {
  24. constructor (origin, {
  25. connections,
  26. factory = defaultFactory,
  27. connect,
  28. connectTimeout,
  29. tls,
  30. maxCachedSessions,
  31. socketPath,
  32. autoSelectFamily,
  33. autoSelectFamilyAttemptTimeout,
  34. allowH2,
  35. clientTtl,
  36. ...options
  37. } = {}) {
  38. if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
  39. throw new InvalidArgumentError('invalid connections')
  40. }
  41. if (typeof factory !== 'function') {
  42. throw new InvalidArgumentError('factory must be a function.')
  43. }
  44. if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') {
  45. throw new InvalidArgumentError('connect must be a function or an object')
  46. }
  47. super()
  48. if (typeof connect !== 'function') {
  49. connect = buildConnector({
  50. ...tls,
  51. maxCachedSessions,
  52. allowH2,
  53. socketPath,
  54. timeout: connectTimeout,
  55. ...(typeof autoSelectFamily === 'boolean' ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
  56. ...connect
  57. })
  58. }
  59. this[kConnections] = connections || null
  60. this[kUrl] = util.parseOrigin(origin)
  61. this[kOptions] = { ...util.deepClone(options), connect, allowH2, clientTtl }
  62. this[kOptions].interceptors = options.interceptors
  63. ? { ...options.interceptors }
  64. : undefined
  65. this[kFactory] = factory
  66. this.on('connect', (origin, targets) => {
  67. if (clientTtl != null && clientTtl > 0) {
  68. for (const target of targets) {
  69. Object.assign(target, { ttl: Date.now() })
  70. }
  71. }
  72. })
  73. this.on('connectionError', (origin, targets, error) => {
  74. // If a connection error occurs, we remove the client from the pool,
  75. // and emit a connectionError event. They will not be re-used.
  76. // Fixes https://github.com/nodejs/undici/issues/3895
  77. for (const target of targets) {
  78. // Do not use kRemoveClient here, as it will close the client,
  79. // but the client cannot be closed in this state.
  80. const idx = this[kClients].indexOf(target)
  81. if (idx !== -1) {
  82. this[kClients].splice(idx, 1)
  83. }
  84. }
  85. })
  86. }
  87. [kGetDispatcher] () {
  88. const clientTtlOption = this[kOptions].clientTtl
  89. for (const client of this[kClients]) {
  90. // check ttl of client and if it's stale, remove it from the pool
  91. if (clientTtlOption != null && clientTtlOption > 0 && client.ttl && ((Date.now() - client.ttl) > clientTtlOption)) {
  92. this[kRemoveClient](client)
  93. } else if (!client[kNeedDrain]) {
  94. return client
  95. }
  96. }
  97. if (!this[kConnections] || this[kClients].length < this[kConnections]) {
  98. const dispatcher = this[kFactory](this[kUrl], this[kOptions])
  99. this[kAddClient](dispatcher)
  100. return dispatcher
  101. }
  102. }
  103. }
  104. module.exports = Pool