postman-request
Version:
Simplified HTTP request client.
93 lines (78 loc) • 2.81 kB
JavaScript
const { EventEmitter } = require('events')
const http2 = require('http2')
const { getName: getConnectionName } = require('../autohttp/requestName')
class Http2Agent extends EventEmitter {
constructor (options) {
super()
this.options = options
this.connections = {}
}
createConnection (req, uri, options, socket) {
const _options = {
...options,
...this.options
}
const name = getConnectionName(_options)
let connection = this.connections[name]
// Force create a new connection if the connection is destroyed or closed or a new socket object is supplied
if (!connection || connection.destroyed || connection.closed || socket) {
const connectionOptions = {
..._options,
port: _options.port || 443,
settings: {
enablePush: false
}
}
// check if a socket is supplied
if (socket) {
connectionOptions.createConnection = () => socket
}
connection = http2.connect(uri, connectionOptions)
// Connection is created in an unreferenced state and is referenced when a stream is created
// This is to prevent the connection from keeping the event loop alive
connection.unref()
// Counting semaphore, but since node is single-threaded, this is just a counter
// Multiple streams can be active on a connection
// Each stream refs the connection at the start, and unrefs it on end
// The connection should terminate if no streams are active on it
// Could be refactored into something prettier
const oldRef = connection.ref
const oldUnref = connection.unref
const timeoutHandler = () => {
delete connectionsMap[name]
connection.close()
}
connection.refCount = 0
connection.ref = function () {
this.refCount++
oldRef.call(this)
connection.off('timeout', timeoutHandler)
connection.setTimeout(0)
}
const connectionsMap = this.connections
connection.unref = function () {
this.refCount--
if (this.refCount === 0) {
oldUnref.call(this)
if (_options.timeout) {
connection.setTimeout(_options.timeout, timeoutHandler)
}
}
}
// Add a default error listener to HTTP2 session object to transparently swallow errors incase no streams are active
// Remove the connection from the connections map if the connection has errored out
connection.on('error', () => {
delete this.connections[name]
})
connection.once('close', () => {
delete this.connections[name]
})
this.connections[name] = connection
}
return connection
}
}
module.exports = {
Http2Agent,
globalAgent: new Http2Agent({})
}