/** * 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