Files
peardock/build/shims/node-http.cjs
T
Raven Scott 5a15b84504 Harden Bare HTTP unix sockets against setKeepAlive crash
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.
2026-07-11 16:00:33 -04:00

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