Release rolling / release (push) Successful in 9m45s
bare-pipe sockets lack TCP setKeepAlive; bare-http1 Agent calls it when recycling connections and crashed peardock-server after Docker connected. No-op keep-alive/unref on IPC sockets and safe-keepSocketAlive on Agent.
144 lines
3.9 KiB
JavaScript
144 lines
3.9 KiB
JavaScript
/**
|
|
* Node-compatible http for Bare — adds Unix socket support.
|
|
*
|
|
* bare-http1's Agent always uses bare-tcp and ignores `socketPath`.
|
|
* dockerode/docker-modem requires:
|
|
* http.request({ socketPath: '/var/run/docker.sock', path: '/version', ... })
|
|
*
|
|
* Without this, requests go to TCP localhost:80 → reverse-proxy 301 to HTTPS.
|
|
*
|
|
* Unix/IPC sockets (bare-pipe) do not implement TCP keep-alive; bare-http1's
|
|
* global Agent calls socket.setKeepAlive on free and crashes with:
|
|
* TypeError: this._socket.setKeepAlive is not a function
|
|
*/
|
|
'use strict'
|
|
|
|
const http = require('bare-http1')
|
|
const net = require('bare-net')
|
|
|
|
/**
|
|
* bare-net Socket.setKeepAlive delegates to the underlying handle.
|
|
* bare-pipe has no setKeepAlive — wrap so Agent keep-alive is a no-op.
|
|
*/
|
|
function hardenSocket(socket) {
|
|
if (!socket || socket.__peardockHardened) return socket
|
|
|
|
const wrap = (name) => {
|
|
const orig = typeof socket[name] === 'function' ? socket[name].bind(socket) : null
|
|
socket[name] = function (...args) {
|
|
if (!orig) return this
|
|
try {
|
|
return orig(...args)
|
|
} catch {
|
|
return this
|
|
}
|
|
}
|
|
}
|
|
|
|
wrap('setKeepAlive')
|
|
wrap('setNoDelay')
|
|
wrap('setTimeout')
|
|
wrap('ref')
|
|
wrap('unref')
|
|
|
|
// Ensure methods always exist even if missing on the object
|
|
if (typeof socket.setKeepAlive !== 'function') {
|
|
socket.setKeepAlive = function () {
|
|
return this
|
|
}
|
|
}
|
|
if (typeof socket.setNoDelay !== 'function') {
|
|
socket.setNoDelay = function () {
|
|
return this
|
|
}
|
|
}
|
|
if (typeof socket.unref !== 'function') {
|
|
socket.unref = function () {
|
|
return this
|
|
}
|
|
}
|
|
if (typeof socket.ref !== 'function') {
|
|
socket.ref = function () {
|
|
return this
|
|
}
|
|
}
|
|
|
|
socket.__peardockHardened = true
|
|
return socket
|
|
}
|
|
|
|
function patchAgent(Agent) {
|
|
if (!Agent || Agent.prototype.__peardockSocketPathPatched) return Agent
|
|
|
|
const origCreate = Agent.prototype.createConnection
|
|
const origGetName = Agent.prototype.getName
|
|
const origKeepAlive = Agent.prototype.keepSocketAlive
|
|
|
|
Agent.prototype.createConnection = function createConnection(opts, callback) {
|
|
const o = opts || {}
|
|
const sock =
|
|
o.socketPath ||
|
|
(typeof o.path === 'string' && o.path.startsWith('/') ? o.path : null)
|
|
|
|
if (sock) {
|
|
const socket = hardenSocket(net.createConnection({ path: sock }))
|
|
if (typeof callback === 'function') {
|
|
socket.once('connect', () => callback(null, socket))
|
|
}
|
|
return socket
|
|
}
|
|
|
|
let socket
|
|
if (typeof origCreate === 'function') {
|
|
socket = origCreate.call(this, o, callback)
|
|
} else {
|
|
socket = net.createConnection(o, callback)
|
|
}
|
|
return hardenSocket(socket)
|
|
}
|
|
|
|
Agent.prototype.getName = function getName(opts) {
|
|
const o = opts || {}
|
|
if (o.socketPath) return `unix:${o.socketPath}:`
|
|
if (typeof origGetName === 'function') return origGetName.call(this, o)
|
|
return `${o.host || 'localhost'}:${o.port || 80}`
|
|
}
|
|
|
|
Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
|
|
// Prefer original implementation when safe
|
|
try {
|
|
if (typeof origKeepAlive === 'function') {
|
|
return origKeepAlive.call(this, socket)
|
|
}
|
|
} catch {
|
|
// fall through
|
|
}
|
|
// Unix sockets / bare-pipe: no TCP keep-alive; still unref for pooling
|
|
try {
|
|
if (typeof socket?.setKeepAlive === 'function') {
|
|
socket.setKeepAlive(true, this._keepAlive > 0 ? this._keepAlive : 1000)
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
try {
|
|
if (typeof socket?.unref === 'function') socket.unref()
|
|
} catch {
|
|
// ignore
|
|
}
|
|
// Return true so free sockets can be reused (docker.sock benefits from pooling)
|
|
return this._keepAlive !== -1
|
|
}
|
|
|
|
Agent.prototype.__peardockSocketPathPatched = true
|
|
return Agent
|
|
}
|
|
|
|
patchAgent(http.Agent)
|
|
if (http.globalAgent?.constructor) {
|
|
patchAgent(http.globalAgent.constructor)
|
|
}
|
|
|
|
module.exports = http
|
|
module.exports.default = http
|