source: node_modules/undici/lib/core/connect.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 5.6 KB
RevLine 
[d24f17c]1'use strict'
2
3const net = require('net')
4const assert = require('assert')
5const util = require('./util')
6const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
7
8let tls // include tls conditionally since it is not always available
9
10// TODO: session re-use does not wait for the first
11// connection to resolve the session and might therefore
12// resolve the same servername multiple times even when
13// re-use is enabled.
14
15let SessionCache
16// FIXME: remove workaround when the Node bug is fixed
17// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308
18if (global.FinalizationRegistry && !process.env.NODE_V8_COVERAGE) {
19 SessionCache = class WeakSessionCache {
20 constructor (maxCachedSessions) {
21 this._maxCachedSessions = maxCachedSessions
22 this._sessionCache = new Map()
23 this._sessionRegistry = new global.FinalizationRegistry((key) => {
24 if (this._sessionCache.size < this._maxCachedSessions) {
25 return
26 }
27
28 const ref = this._sessionCache.get(key)
29 if (ref !== undefined && ref.deref() === undefined) {
30 this._sessionCache.delete(key)
31 }
32 })
33 }
34
35 get (sessionKey) {
36 const ref = this._sessionCache.get(sessionKey)
37 return ref ? ref.deref() : null
38 }
39
40 set (sessionKey, session) {
41 if (this._maxCachedSessions === 0) {
42 return
43 }
44
45 this._sessionCache.set(sessionKey, new WeakRef(session))
46 this._sessionRegistry.register(session, sessionKey)
47 }
48 }
49} else {
50 SessionCache = class SimpleSessionCache {
51 constructor (maxCachedSessions) {
52 this._maxCachedSessions = maxCachedSessions
53 this._sessionCache = new Map()
54 }
55
56 get (sessionKey) {
57 return this._sessionCache.get(sessionKey)
58 }
59
60 set (sessionKey, session) {
61 if (this._maxCachedSessions === 0) {
62 return
63 }
64
65 if (this._sessionCache.size >= this._maxCachedSessions) {
66 // remove the oldest session
67 const { value: oldestKey } = this._sessionCache.keys().next()
68 this._sessionCache.delete(oldestKey)
69 }
70
71 this._sessionCache.set(sessionKey, session)
72 }
73 }
74}
75
76function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...opts }) {
77 if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
78 throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
79 }
80
81 const options = { path: socketPath, ...opts }
82 const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions)
83 timeout = timeout == null ? 10e3 : timeout
84 allowH2 = allowH2 != null ? allowH2 : false
85 return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) {
86 let socket
87 if (protocol === 'https:') {
88 if (!tls) {
89 tls = require('tls')
90 }
91 servername = servername || options.servername || util.getServerName(host) || null
92
93 const sessionKey = servername || hostname
94 const session = sessionCache.get(sessionKey) || null
95
96 assert(sessionKey)
97
98 socket = tls.connect({
99 highWaterMark: 16384, // TLS in node can't have bigger HWM anyway...
100 ...options,
101 servername,
102 session,
103 localAddress,
104 // TODO(HTTP/2): Add support for h2c
105 ALPNProtocols: allowH2 ? ['http/1.1', 'h2'] : ['http/1.1'],
106 socket: httpSocket, // upgrade socket connection
107 port: port || 443,
108 host: hostname
109 })
110
111 socket
112 .on('session', function (session) {
113 // TODO (fix): Can a session become invalid once established? Don't think so?
114 sessionCache.set(sessionKey, session)
115 })
116 } else {
117 assert(!httpSocket, 'httpSocket can only be sent on TLS update')
118 socket = net.connect({
119 highWaterMark: 64 * 1024, // Same as nodejs fs streams.
120 ...options,
121 localAddress,
122 port: port || 80,
123 host: hostname
124 })
125 }
126
127 // Set TCP keep alive options on the socket here instead of in connect() for the case of assigning the socket
128 if (options.keepAlive == null || options.keepAlive) {
129 const keepAliveInitialDelay = options.keepAliveInitialDelay === undefined ? 60e3 : options.keepAliveInitialDelay
130 socket.setKeepAlive(true, keepAliveInitialDelay)
131 }
132
133 const cancelTimeout = setupTimeout(() => onConnectTimeout(socket), timeout)
134
135 socket
136 .setNoDelay(true)
137 .once(protocol === 'https:' ? 'secureConnect' : 'connect', function () {
138 cancelTimeout()
139
140 if (callback) {
141 const cb = callback
142 callback = null
143 cb(null, this)
144 }
145 })
146 .on('error', function (err) {
147 cancelTimeout()
148
149 if (callback) {
150 const cb = callback
151 callback = null
152 cb(err)
153 }
154 })
155
156 return socket
157 }
158}
159
160function setupTimeout (onConnectTimeout, timeout) {
161 if (!timeout) {
162 return () => {}
163 }
164
165 let s1 = null
166 let s2 = null
167 const timeoutId = setTimeout(() => {
168 // setImmediate is added to make sure that we priotorise socket error events over timeouts
169 s1 = setImmediate(() => {
170 if (process.platform === 'win32') {
171 // Windows needs an extra setImmediate probably due to implementation differences in the socket logic
172 s2 = setImmediate(() => onConnectTimeout())
173 } else {
174 onConnectTimeout()
175 }
176 })
177 }, timeout)
178 return () => {
179 clearTimeout(timeoutId)
180 clearImmediate(s1)
181 clearImmediate(s2)
182 }
183}
184
185function onConnectTimeout (socket) {
186 util.destroy(socket, new ConnectTimeoutError())
187}
188
189module.exports = buildConnector
Note: See TracBrowser for help on using the repository browser.