mirror of https://github.com/jkjoy/sunpeiwen.git
186 lines
5.3 KiB
JavaScript
186 lines
5.3 KiB
JavaScript
|
'use strict'
|
||
|
|
||
|
const net = require('net')
|
||
|
const assert = require('assert')
|
||
|
const util = require('./util')
|
||
|
const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
|
||
|
|
||
|
let tls // include tls conditionally since it is not always available
|
||
|
|
||
|
// TODO: session re-use does not wait for the first
|
||
|
// connection to resolve the session and might therefore
|
||
|
// resolve the same servername multiple times even when
|
||
|
// re-use is enabled.
|
||
|
|
||
|
let SessionCache
|
||
|
if (global.FinalizationRegistry) {
|
||
|
SessionCache = class WeakSessionCache {
|
||
|
constructor (maxCachedSessions) {
|
||
|
this._maxCachedSessions = maxCachedSessions
|
||
|
this._sessionCache = new Map()
|
||
|
this._sessionRegistry = new global.FinalizationRegistry((key) => {
|
||
|
if (this._sessionCache.size < this._maxCachedSessions) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
const ref = this._sessionCache.get(key)
|
||
|
if (ref !== undefined && ref.deref() === undefined) {
|
||
|
this._sessionCache.delete(key)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
get (sessionKey) {
|
||
|
const ref = this._sessionCache.get(sessionKey)
|
||
|
return ref ? ref.deref() : null
|
||
|
}
|
||
|
|
||
|
set (sessionKey, session) {
|
||
|
if (this._maxCachedSessions === 0) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
this._sessionCache.set(sessionKey, new WeakRef(session))
|
||
|
this._sessionRegistry.register(session, sessionKey)
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
SessionCache = class SimpleSessionCache {
|
||
|
constructor (maxCachedSessions) {
|
||
|
this._maxCachedSessions = maxCachedSessions
|
||
|
this._sessionCache = new Map()
|
||
|
}
|
||
|
|
||
|
get (sessionKey) {
|
||
|
return this._sessionCache.get(sessionKey)
|
||
|
}
|
||
|
|
||
|
set (sessionKey, session) {
|
||
|
if (this._maxCachedSessions === 0) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if (this._sessionCache.size >= this._maxCachedSessions) {
|
||
|
// remove the oldest session
|
||
|
const { value: oldestKey } = this._sessionCache.keys().next()
|
||
|
this._sessionCache.delete(oldestKey)
|
||
|
}
|
||
|
|
||
|
this._sessionCache.set(sessionKey, session)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
|
||
|
if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) {
|
||
|
throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero')
|
||
|
}
|
||
|
|
||
|
const options = { path: socketPath, ...opts }
|
||
|
const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions)
|
||
|
timeout = timeout == null ? 10e3 : timeout
|
||
|
|
||
|
return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) {
|
||
|
let socket
|
||
|
if (protocol === 'https:') {
|
||
|
if (!tls) {
|
||
|
tls = require('tls')
|
||
|
}
|
||
|
servername = servername || options.servername || util.getServerName(host) || null
|
||
|
|
||
|
const sessionKey = servername || hostname
|
||
|
const session = sessionCache.get(sessionKey) || null
|
||
|
|
||
|
assert(sessionKey)
|
||
|
|
||
|
socket = tls.connect({
|
||
|
highWaterMark: 16384, // TLS in node can't have bigger HWM anyway...
|
||
|
...options,
|
||
|
servername,
|
||
|
session,
|
||
|
localAddress,
|
||
|
socket: httpSocket, // upgrade socket connection
|
||
|
port: port || 443,
|
||
|
host: hostname
|
||
|
})
|
||
|
|
||
|
socket
|
||
|
.on('session', function (session) {
|
||
|
// TODO (fix): Can a session become invalid once established? Don't think so?
|
||
|
sessionCache.set(sessionKey, session)
|
||
|
})
|
||
|
} else {
|
||
|
assert(!httpSocket, 'httpSocket can only be sent on TLS update')
|
||
|
socket = net.connect({
|
||
|
highWaterMark: 64 * 1024, // Same as nodejs fs streams.
|
||
|
...options,
|
||
|
localAddress,
|
||
|
port: port || 80,
|
||
|
host: hostname
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Set TCP keep alive options on the socket here instead of in connect() for the case of assigning the socket
|
||
|
if (options.keepAlive == null || options.keepAlive) {
|
||
|
const keepAliveInitialDelay = options.keepAliveInitialDelay === undefined ? 60e3 : options.keepAliveInitialDelay
|
||
|
socket.setKeepAlive(true, keepAliveInitialDelay)
|
||
|
}
|
||
|
|
||
|
const cancelTimeout = setupTimeout(() => onConnectTimeout(socket), timeout)
|
||
|
|
||
|
socket
|
||
|
.setNoDelay(true)
|
||
|
.once(protocol === 'https:' ? 'secureConnect' : 'connect', function () {
|
||
|
cancelTimeout()
|
||
|
|
||
|
if (callback) {
|
||
|
const cb = callback
|
||
|
callback = null
|
||
|
cb(null, this)
|
||
|
}
|
||
|
})
|
||
|
.on('error', function (err) {
|
||
|
cancelTimeout()
|
||
|
|
||
|
if (callback) {
|
||
|
const cb = callback
|
||
|
callback = null
|
||
|
cb(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
return socket
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function setupTimeout (onConnectTimeout, timeout) {
|
||
|
if (!timeout) {
|
||
|
return () => {}
|
||
|
}
|
||
|
|
||
|
let s1 = null
|
||
|
let s2 = null
|
||
|
const timeoutId = setTimeout(() => {
|
||
|
// setImmediate is added to make sure that we priotorise socket error events over timeouts
|
||
|
s1 = setImmediate(() => {
|
||
|
if (process.platform === 'win32') {
|
||
|
// Windows needs an extra setImmediate probably due to implementation differences in the socket logic
|
||
|
s2 = setImmediate(() => onConnectTimeout())
|
||
|
} else {
|
||
|
onConnectTimeout()
|
||
|
}
|
||
|
})
|
||
|
}, timeout)
|
||
|
return () => {
|
||
|
clearTimeout(timeoutId)
|
||
|
clearImmediate(s1)
|
||
|
clearImmediate(s2)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onConnectTimeout (socket) {
|
||
|
util.destroy(socket, new ConnectTimeoutError())
|
||
|
}
|
||
|
|
||
|
module.exports = buildConnector
|